Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

runner

Sandboxes are isolated Linux microVMs behind an HTTP API. You create one, run commands and move files in it, then delete it. Full detail is in docs/reference/ (start at README.md); this file is the part to get right.

Pick the surface

  • runner CLI: from a shell. JSON on stdout, typed errors on stderr.
  • runner-mcp: when you are an MCP client; tools sandbox_create, exec, file_read, file_write, ... (see reference/mcp.md).
  • HTTP API: for anything the other two lack (leases, processes, previews).
  • SDKs (Python, TypeScript): coming soon. Do not import one; none exists yet.
CLI setup:
export RUNNER_API_URL=https://api.example.com export RUNNER_API_KEY=rk_... # scoped to what you need

The loop

ID=$(runner create -template default | jq -r .id) # waits until running runner cp ./job.py "$ID:/workspace/job.py" runner exec -timeout 60s "$ID" python3 /workspace/job.py # exits with job.py's status runner cp "$ID:/workspace/result.json" - runner delete "$ID" # always: it holds a slot

Rules that matter

  1. Always delete. A running sandbox holds one of the account's slots until deleted or its lease ends. Delete in a finally/trap.
  2. Always pass -template. The API requires a template and has no default; the CLI and MCP add none. Shipped templates: default (Python, Node, git), coding-agents. Your own: name or name:tag.
  3. exec runs a program, not a shell line. Pipes and redirects need runner exec ID sh -c 'a | b > c'. Put exec flags before ID.
  4. Exit status. runner exec exits with the command's own status; runner's own failures exit 125. Other commands exit 1 on failure, 2 on misuse.
  5. Branch on error.code, not the message. retry remedy: back off and retry (unavailable, capacity_exhausted, sandbox_not_ready). fix_input: change the request. raise_limits: more -timeout, -memory or -disk. Codes: reference/errors.md.
  6. Leases. A sandbox lives 1 hour by default; running commands does not extend it. Set the length at create with -lease-seconds N (MCP lease_seconds), and renew with runner renew ID N (MCP sandbox_renew; API POST /v1/sandboxes/{id}/lease {"seconds": N}). After expiry the sandbox is gone (lease_expired). See api/leases.md.
  7. Secrets go in -env at create time, never into a template or Dockerfile.
  8. Lock down egress with repeated -allow-domain HOST: then nothing else is reachable over HTTP(S). Without it egress is open.
  9. Text vs bytes. Exec output is text (non-UTF-8 is replaced). Move binary data with runner cp, not by printing it.
  10. Retries. Use -idempotency-key on create so a retry cannot make a second sandbox.
  11. Scopes. A 403 forbidden means the key lacks a scope (sandboxes:read, sandboxes:write, exec, files, templates, previews, ssh, keys). Use a different key; retrying cannot help.

Custom templates

runner templates build -f Dockerfile -ready 'python3 --version' mytool runner create -template mytool
The Dockerfile must start FROM image@sha256:<digest> (a tag is refused). Only RUN ENV WORKDIR USER CMD ENTRYPOINT work (LABEL EXPOSE MAINTAINER are ignored); COPY and multi-stage are refused. CMD/ENTRYPOINT require -ready. Roll back with runner templates tag NAME latest VERSION.

Reaching a sandbox from outside

  • Shell: runner ssh -o StrictHostKeyChecking=accept-new ID [cmd...] (no PTY).
  • A web port: preview tokens via the HTTP API (reference/api.md#previews-scope-previews), or -public-port N at create (MCP public_ports), where the deployment enables them.

Not available yet

SDKs, COPY in templates, PTY over SSH, pause/resume. Never assume them; see reference/coming-soon.md.