Python SDK
The whole API from Python, for plain code and for asyncio. It waits for a machine to be running, sends a request again where that is safe, and raises every refusal as an error with the API's code.
For your agent
agental.io/docs/python.mdpip install agental-sdk
- Installed as
agental-sdk, imported asagental:from agental import Agental. - Python 3.10 or later, with
httpxand nothing else. - Typed: it carries
py.typed. - Names are the API's in snake case, such as
exit_codeandidempotency_key. One that is a Python keyword ends in_: a message's sender isfrom_.
Make a client
Make an API key in the console, under API keys, and put it in AGENTAL_API_KEY. A client made with nothing reads it from there.
from agental import Agental
agental = Agental() # reads AGENTAL_API_KEY
sandbox = agental.sandboxes.create(size="small")
result = sandbox.exec("uname -a")
print(result.exit_code, result.stdout)
sandbox.files.write("app/main.py", "print('hello')\n")
print(sandbox.exec(["python3", "/opt/agental/app/main.py"]).stdout)
sandbox.stop() # the disk is keptcreateanswers when the machine is running. A sandbox you have already isagental.sandboxes.get(id), andagental.sandboxes.list()gives all of them.- A command given as a string is a line of shell, run by
sh -c. Given as a list, its arguments are passed exactly, with no shell. - A command gets 60 seconds unless you pass
timeout, in seconds, up to 90, since a proxy in front of the API stops waiting after 100 seconds. One that runs out is killed and exits124. Longer work goes tosandbox.commands.start, which takes up to 600 and answers at once. - File paths are relative to the work folder,
/opt/agental.files.readgives text, andfiles.read_bytesbytes. - A time can be a
datetime, which must say its time zone, or a string in ISO 8601.
Output as it arrives
for event in sandbox.exec_stream("npm test"):
if event.type == "exit":
print("exited", event.exit_code)
else:
print(event.data, end="")For a command that takes more than a minute, sandbox.commands.run runs it without holding a request open, and commands.start starts it to be looked at later. See Running commands.
With asyncio
AsyncAgental is the same client, awaited.
import asyncio
from agental import AsyncAgental
async def main() -> None:
async with AsyncAgental() as agental:
sandbox = await agental.sandboxes.create()
print((await sandbox.exec("uname -a")).stdout)
await sandbox.stop()
asyncio.run(main())async with closes the client's connections at the end; without it, call await agental.aclose(). Agental does the same with with, or agental.close(). exec_stream is iterated with async for.
Where the key comes from
api_key, when you pass one.- Otherwise
AGENTAL_API_KEY. - Otherwise the key the agental command keeps on this computer, in
~/.config/agental/credentials.json, for the API it signed in to and no other.
The API is base_url, otherwise AGENTAL_API_URL, otherwise the API the command signed in to, otherwise https://agental.io.
Options
| Option | What it does |
|---|---|
api_key | The API key. Left out, as above. |
base_url | Where the API answers. Left out, as above. |
max_retries | How many times a request that is safe to send again is sent again. 3 unless you say. |
retry_delay | The shortest wait before it is sent again, in seconds. 1.0 unless you say. |
poll_interval | How often something waited for, such as a machine starting, is looked at, in seconds. 2.0 unless you say. |
http_client | An httpx.Client to send requests with, or an httpx.AsyncClient for AsyncAgental. Left out, one of its own. |
What is in it
Each guide shows its calls in Python. Where to find them:
| Calls | Guide |
|---|---|
agental.sandboxes.create, get, list; sandbox.stop, start, update, delete | Sandbox lifecycle |
sandbox.exec, exec_stream, commands.run, commands.start | Running commands |
sandbox.files.read, read_bytes, write | Files |
sandbox.browser | Browser |
sandbox.desktop | Desktop |
sandbox.snapshot, restore; agental.snapshots | Snapshots |
sandbox.fork | Forking |
agental.templates, sandbox.secrets | Templates and secrets |
template.set_network, sandbox.set_network | Outbound rules |
sandbox.expose, routes | Public URLs |
sandbox.email, agental.email | |
agental.create_webhook, list_webhooks; verify_webhook, imported on its own | Webhooks |
sandbox.metrics, agental.metrics, running_over_time | Metrics |
agental.usage, sandbox.usage | Usage |
agental.set_telemetry, test_telemetry | OpenTelemetry |
agental.billing, prices, checkout, burst, set_burst_budget | Billing |
agental.api_keys | API keys |
agental.export_account | Your account and data |
What it does for you
- A create or a fork is sent with an
Idempotency-Key. If its answer is lost, or the machine is not confirmed yet, the same request is sent again with the same key, which gives the same sandbox back and never a second one. Passidempotency_keyyourself to be able to ask again after your process has died. - Reads, file writes, starts and stops are sent again when a proxy answers in the API's place, when the connection drops, and when the sandbox was moved to another server during the request.
- A command is never sent twice. One that was cut off may have done part of its work, so whether to run it again is yours to decide.
- A delete carries the confirm header the API asks for.
Errors
from agental import ApiError
try:
sandbox.exec("make")
except ApiError as error:
if error.code == "sandbox_not_running":
sandbox.start()
else:
raise| Error | What it means |
|---|---|
ApiError | The API's own refusal, with status, code and message. Decide from code: Errors lists them. |
ProxyError | A 502, a 504 or any answer with no code in it: something in front of the API answered, and the request may still be running. |
AgentalConnectionError | The API was not reached. |
AgentalTimeoutError | A wait ran out, such as for a machine to start. |
All of them are AgentalError. A command that exits non-zero is not an error: read exit_code.
The full machine-readable reference is OpenAPI 3.1. Every page as Markdown, for an agent: llms.txt. Questions: [email protected]