Long-lived sandboxes
Create one isolated workspace, run many commands, checkpoint it — pod or desktop substrate.
Use a long-lived sandbox when one workflow needs the same filesystem across many commands. Create the box once instead of paying setup on every turn.
| Need | Use |
|---|---|
| Interactive agent/dev loop | Long-lived sandbox (sandboxes-default) |
| Re-run tests in the same workspace | Long-lived sandbox verify |
| GUI desktop in the browser | Desktop sandboxes (desktop-*) |
| One short snippet | CodeEdit |
| Benchmark + verifier | Harbor |
| Free-form one-shot agent | Agent runs |
Not for pass@k
Pass@k attempts must be independent. Do not attach multiple attempts to the same sandbox or carry files, conversation state, or answers between them.
Profiles and substrates
The same POST /sandbox/sandboxes/v1 API serves headless workspaces and desktops.
The profile picks the substrate:
| Profile | Substrate | GUI? |
|---|---|---|
sandboxes-default | Kubernetes pod + PVC | No — exec/files API |
desktop-ubuntu | Kubernetes pod + noVNC | Yes |
desktop-windows | EC2 VM + Guacamole | Yes |
desktop-macos | External Mac cloud | Yes |
This page covers the headless path (sandboxes-default). For GUI profiles see
Desktop sandboxes.
Lifecycle
For sandboxes-default, the service runs one pod plus a PVC in the trial
namespace. Kubernetes objects are runtime truth; object storage holds durable
workspace checkpoints. Redis is a lookup and quota store, not the durable
workspace.
Create
curl -sS -X POST \
-H "X-Api-Key: $SANDBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile": "sandboxes-default",
"autostop_sec": 1800,
"ttl_sec": 14400
}' \
"$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1"The response contains sandbox_id. Keep it for every later call. A create or
start consumes one concurrency slot for the authenticated tenant:project; when
the project is full, the API returns 429 and Retry-After.
You may bootstrap from an HTTPS archive, a permitted GCS prefix, or a prior snapshot:
{
"profile": "sandboxes-default",
"source": {
"archive_url": "https://storage.example/task.zip"
}
}The source is fetched once. Later exec calls use the PVC; they do not download the archive again.
Exec in the same workspace
curl -sS -X POST \
-H "X-Api-Key: $SANDBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"command":["bash","-lc","python -m pytest -q"],"timeout_sec":300}' \
"$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1/$SANDBOX_ID/exec"Each exec uses the same filesystem until the sandbox is stopped, destroyed, expires, or is reaped for idleness.
Continue with an agent
Attach a catalog agent with integration: sandbox (for example opencode) to the
running session:
{
"agent_id": "opencode",
"model": "wandb/moonshotai/Kimi-K2.5",
"instruction": "Continue from the current files and fix the next failure",
"sandbox_id": "your-sandbox-id"
}Submit to POST /sandbox/agent/v1/runs with the same tenancy. The session image
must already contain that agent CLI. sandbox_id with pass_at_k > 1 is
rejected. Agent list: Supported agents.
Stop, start, and destroy
curl -sS -X POST -H "X-Api-Key: $SANDBOX_API_KEY" \
"$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1/$SANDBOX_ID/stop"
curl -sS -X POST -H "X-Api-Key: $SANDBOX_API_KEY" \
"$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1/$SANDBOX_ID/start"
curl -sS -X DELETE -H "X-Api-Key: $SANDBOX_API_KEY" \
"$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1/$SANDBOX_ID"Default auto-stop is 30 minutes on dev. Destroy sandboxes you no longer need — idle sessions hold storage and, while running, a concurrency slot.
Warm sessions vs warm nodes
| Pool | Holds | Benefit |
|---|---|---|
| Session warm pool | Ready pod + PVC for a profile | Faster create/claim |
| Node warm placeholders | Empty gVisor node capacity | Avoids node scale-up |
Neither speeds up model reasoning.
Tenancy
Ownership comes from the authenticated principal. List/get/exec only expose
sandboxes in the same tenant:project.
See Caching and reuse and Architecture.