Sandbox client
Run agent-generated code in an isolated VM from the TypeScript SDK
Sandboxes are experimental. The API may change without a deprecation period.
SandboxClient gives your agent a sandbox: a virtual machine with its own kernel and filesystem, apart from your agent’s container and the cluster it runs on. Your agent writes files there, runs the code it generated, and reads back the results. Remote Sandbox explains how the pieces fit together.
Get started
Declare a sandbox
Add a sandbox section to astropods.yml that lists what your agent’s code needs. A sandbox starts with core utilities only, so declare every language and package the code uses.
toolchain is the only required field. The spec’s sandbox section lists the rest. Redeploy after you change it.
Configure the client
Every option has a default that works in a deployed agent. Set one only to change how long the client waits.
The token that identifies your deployment. Astropods sets it for every deployed agent.
The Astropods server to call. The client reads it from the token.
How long one request, including one exec, may take.
How long the first attach waits for a new sandbox to finish setting up.
The fetch the client sends requests with.
Name your sandboxes
Every method takes a sandbox name as its first argument. The same name always reaches the same filesystem, and two names never share one.
Use the conversation or thread id as the name. Each conversation then gets its own files, and a resumed conversation finds the files it left. Commands run in /workspace unless you pass another cwd.
Names match ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$.
Attach to a sandbox
attach creates a sandbox on the first call, reuses a running one, and resumes a suspended one. Calls in parallel with the same name share one sandbox, so it is safe to call on every turn.
You rarely call it yourself. exec, run, and spawn attach on first use, and the client keeps the handle and renews its credentials.
sandboxClass accepts only default. Any other value returns 409.
The first attach
The first attach for a name installs what you declared, so your first command finds everything ready. A large declaration can take a few minutes, and attach waits until it finishes. Later attaches to the same name return quickly.
- Setup takes too long. When setup outlasts
prepareTimeoutSeconds,attachthrowsSandboxPreparingError. Raise the option for a large declaration. - The install fails.
attachthrows an error carrying the package manager’s message. Fix the declaration and redeploy, and the next attach tries again.
SandboxHandle fields
default.running.headers stop working, in RFC 3339.Run commands
Pick a method by how long the command runs and whether the turn should wait for it.
A non-zero exitCode is a result, not an exception, so check the field.
exec
exec runs a command and returns when it exits. A command can run for at most timeoutSeconds, so use run for anything longer.
The argv to run. It is not a shell line.
The working directory.
Variables added to the sandbox’s environment. They override entries with the same name.
A shorter limit for this command, bounded by timeoutSeconds.
ExecResult fields
execCombined
execCombined runs a shell line and folds stderr into stdout, the way a terminal shows it. Use it when the order of output matters more than which stream it came from.
run
run starts the command and polls it until it exits. Neither the output limit nor the request timeout applies, which suits installs, builds, and test suites. It takes the same request as spawn.
How often to poll for output.
Called with each chunk of new output.
Kill the command after this long. No limit by default.
Kill the command when this signal aborts.
RunResult has every ProcessOutput field, plus timedOut when timeoutMs elapsed and killed when timeoutMs or signal stopped the command.
spawn
spawn starts a command and returns at once. The process keeps running after the call, and after the turn. Save its processId and check on it later with poll.
A SpawnRequest takes command, cwd, and env, the same as exec.
ProcessStatus fields
poll, signal, and kill.poll
poll reads a process’s status and any output after the offsets you pass. Send back the stdoutNext and stderrNext it returns, and each poll picks up where the last one stopped.
This loop is what run does for you. Reach for poll only when the process should outlive the turn.
A sandbox keeps the newest 1 MiB of each stream for each process. A reader that falls further behind gets a non-zero dropped count: that output was lost, not delayed.
ProcessOutput fields
ProcessOutput has every ProcessStatus field, plus these:
stdoutFrom.stderrFrom.stdoutFrom.stderrFrom.Manage processes
processeslists every process in the sandbox, with status but no output.signalsendsTERMby default, to the whole process group, so a shell’s children receive it too.Signalis"TERM" | "KILL" | "INT" | "HUP" | "QUIT" | "USR1" | "USR2".killstops the process if it is still running, then forgets it.
A sandbox runs at most 64 processes, so clean up what you spawn:
Work with files
Use the string methods for text and the Bytes methods for anything else, such as an image or an upload. listDir and grep search . when you pass no path.
- A
DirEntryis{ name: string; isDirectory: boolean }. - A
GrepMatchis{ path: string; line: number; text: string }, andgrepreturns an empty array when nothing matches. readFileandreadFileBytesthrowSandboxRequestErrorwith status 404 when the path cannot be read.
Large files are slow to read and write.
Inspect and end a sandbox
get returns one sandbox and list returns every sandbox for the deployment. Neither returns credentials. stop suspends a sandbox before it would go idle, and delete removes it with its files.
SandboxRecord fields
default.creating while it is set up, then running, suspended, or stopped.Lifecycle
The client handles every transition. Your code only needs to know what survives each one.
After a removal, the next attach succeeds with your declared software installed and an empty /workspace. Treat /workspace as scratch space, and store anything that must outlast the conversation elsewhere.
Suspend and resume restore the latest state only. There are no checkpoints to return to.
Handle errors
SandboxRequestError.status is 402 when a limit is reached, 404 for an unknown sandbox or unreadable path, and 502 when the sandbox could not start.
Check SandboxNotEnabledError before SandboxRequestError, because it extends it.
The client renews expired credentials on its own. It does not retry a command after a network failure, because the command may already have run.
Limits
Local development
Run your agent with ast project start, after signing in with ast login. Your local agent gets a real sandbox, the same kind a deployed agent gets. It uses the declaration in your local astropods.yml, so a change applies on the next start without a deploy.
ast project stop deletes the sandboxes your local agent created.
Framework adapters
The Mastra and LangChain adapters wrap SandboxClient for their frameworks. Both take { name } plus any SandboxClient option, and accept a client to share one instance.
Mastra adapter covers the Mastra workspace in more detail.
FAQ
How does the client find the server and its credentials?
Astropods gives every deployed agent an ASTRO_AUTHZ_TOKEN environment variable. The client reads it and takes the server’s address from the token, so there is nothing to configure. ast project start sets the same variable for an agent running locally.
Why is the first attach slow, and will every sandbox be?
The first attach installs what your sandbox section declares. Astropods keeps the result, so later sandboxes with the same declaration usually start faster. Changing the declaration installs it once more.
Can I reach a server running inside a sandbox?
No. A web server or other service your agent starts inside a sandbox cannot be reached from outside it.
Do the framework adapters add their own tools?
No. Mastra and Deep Agents each build their own file and shell tools on top of the sandbox, so the adapters add nothing of their own.