Sandbox API

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.

NeedUse
Interactive agent/dev loopLong-lived sandbox (sandboxes-default)
Re-run tests in the same workspaceLong-lived sandbox verify
GUI desktop in the browserDesktop sandboxes (desktop-*)
One short snippetCodeEdit
Benchmark + verifierHarbor
Free-form one-shot agentAgent 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:

ProfileSubstrateGUI?
sandboxes-defaultKubernetes pod + PVCNo — exec/files API
desktop-ubuntuKubernetes pod + noVNCYes
desktop-windowsEC2 VM + GuacamoleYes
desktop-macosExternal Mac cloudYes

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

PoolHoldsBenefit
Session warm poolReady pod + PVC for a profileFaster create/claim
Node warm placeholdersEmpty gVisor node capacityAvoids 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.