Sandbox API

Cloud IDE (ide-ubuntu)

OpenVSCode browser IDE with baked agent CLIs; LLM via KeyHive or LLM Gateway per SANDBOX_LLM_EGRESS.

The ide-ubuntu profile is a Cloud IDE for trainers — not a bare remote editor.

Create and open

curl -fsS -H "X-Api-Key: $SANDBOX_API_KEY" \
  -H "X-Sandbox-Tenant-Id: $TENANT" \
  -H "X-Sandbox-Project-Id: $PROJECT" \
  -H "Content-Type: application/json" \
  -d '{"profile":"ide-ubuntu","autostop_sec":7200,"ttl_sec":28800}' \
  "$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1"

Dev testing: use ctp / sandbox headers. LLM credentials follow SANDBOX_LLM_EGRESS: KeyHive may inject provider keys when direct; Gateway mode uses URL hints and trainer→Gateway auth.

Poll until status=running, then open viewer.url (viewer.mode=openvscode).

Workspace and durability

Only /workspace is durable (PVC + GCS). Home and system paths are ephemeral. Autosave checkpoints run every 15 minutes when the workspace changed (SANDBOX_WORKSPACE_AUTOSAVE_SEC).

Agents and the integrated terminal

Cloud IDE is one remote environment (like Codespaces). No baked platform CLI — the integrated terminal is the default surface for trainers.

Terminal CLIs (claude, opencode, codex) plus pre-installed Claude Code and Codex sidebar extensions. Built-in OpenVSCode agent chat stays off.

The terminal also has vi/vim, less, tmux, jq, tree, file, bc, ps/top, git, curl, Node 22 and Python 3.10 with pytest, pip and venv.

PathWhen
Integrated terminalclaude, opencode run …, codex exec … — KeyHive keys or Gateway tokens per mode
VS Code TasksTerminal → Run Task
Agent attach (L4)POST /sandbox/agent/v1/runs with sandbox_id — orchestration from product UI
Harbor / CodeEdit APIcurl from terminal only if you export gateway URL + API key; trials run on separate pods

Interactive work stays in workspace mode (one session slot). Full Harbor eval is eval mode — same HTTP API as from CI or Postman, not a special IDE integration. The platform does not inject gateway API keys into IDE pods.

Repo detail: CLOUD_IDE.md.

Run, preview and screenshot the app

Coding agents build apps that have to stay up and be checked visually. ide-ubuntu supervises the app, lists what is listening, and screenshots it with a headless browser inside the pod.

1. Supervised start script

Write the start command to /workspace/.ctp/app/start.sh. supervisord's [program:app] runs it with bash from /workspace, appends output to /workspace/.ctp/app/app.log, restarts it with backoff when it exits, and restarts it within about 2 seconds when the file is edited. The script is on the PVC, so the app comes back after stop/start.

mkdir -p /workspace/.ctp/app
cat > /workspace/.ctp/app/start.sh <<'SH'
cd /workspace/web
exec npm run dev -- --host 0.0.0.0 --port 5173
SH
supervisorctl status app     # restart with: supervisorctl restart app

Bind 0.0.0.0 so the browser preview can reach the app. The preview proxy connects to the pod IP. Screenshots work with loopback-only binds too.

2. Listening ports

curl -fsS -H "Authorization: Bearer $TOKEN" \
  "$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1/$SANDBOX_ID/ports"
{
  "sandbox_id": "…",
  "ports": [
    {"port": 5173, "address": "0.0.0.0", "family": "ipv4", "pid": 412,
     "command": "node /workspace/web/node_modules/.bin/vite --host 0.0.0.0",
     "internal": false, "loopback_only": false}
  ]
}

OpenVSCode (:8080) and IDE helper processes are hidden unless you pass ?include_internal=true. Polling /ports does not count as activity, so it never keeps an idle session from autostopping.

3. Preview in the browser

Open {viewer.url base}/viewer/proxy/{port}/. It uses the same viewer token as the editor.

4. Screenshot

curl -fsS -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"port":5173,"path":"/","viewport":{"width":1280,"height":800},"wait_ms":1500}' \
  "$SANDBOX_GATEWAY_URL/sandbox/sandboxes/v1/$SANDBOX_ID/screenshot?inline=1"

Headless Chromium (Playwright chromium-headless-shell, baked into the image) loads http://127.0.0.1:{port}{path} inside the pod. The PNG is stored as a session screenshot artifact, the same store that POST …/capture uses, and it shows up in GET …/artifacts. The response carries artifact_id, a signed artifact_url, content_type, width, height, and page.status/page.title. Add ?inline=1 to get base64 as well. A screenshot counts as session activity.

FieldDefaultNotes
portauto-detectedRequired when more than one app is listening
path/Path and query on the app
urlnoneOnly the sandbox itself: localhost, 127.0.0.0/8, ::1, or the pod IP
viewport1280×800320–3840 × 240–2160
full_pagefalseCapture the whole scrollable page
wait_ms1500Settle time after load, from 0 to 30000
StatusMeaning
409Nothing is listening on the port, the sandbox is not running, or the image has no headless browser
422Invalid target: an external URL, a redirect out of the sandbox, ambiguous auto-detection, or a bad path
502The app did not load (navigation error or timeout)
413The PNG is larger than the screenshot size limit

Quota

Each running IDE session uses one slot on the project concurrency cap (GET /sandbox/v1/quota). Terminal agents do not add slots; Harbor jobs from curl do. Stop the sandbox to free capacity. See Errors and limits.

Autoscaling

  • sandbox-sandboxes service — CPU autoscaling (same HPA pattern as gateway) for create/viewer API load.
  • Session pods — one pod per user in sandbox-jobs; bounded by namespace ResourceQuota and per-project L2 admit, not by HPA.
  • ide-ubuntu — no claimable warm pool (sessions are tenant-scoped), but warm nodes on dev: ideWarm balloon pods keep nodes provisioned with the IDE image cached. A new session that cannot schedule takes one, so it skips node scale-up (~2.5 min) and the 2.3 GB image pull (~2 min). A burst larger than ideWarm.replicas still cold-starts. See Warm nodes for ide-ubuntu.

Warm nodes for ide-ubuntu

PieceWhat it does
sandbox-ide-warm (Deployment, sandbox-jobs)Balloon pods with the same requests, image and safe-to-evict: "false" annotation as a session pod. They run sleep infinity at PriorityClass sandbox-balloon (−10).
sandbox-ide-prepull (DaemonSet)Pulls the pinned IDE image on every eligible node, so a new release tag is cached before a session needs it.
Sandboxes serviceAfter creating a session pod that the scheduler reports Unschedulable, it deletes one balloon. The session takes the space and the balloon's replacement provisions the next warm node.

Sessions keep preemptionPolicy: Never, so they never preempt Harbor trial pods. That is also why sessions cannot preempt balloons, and why the service releases a balloon itself. Expected first-sandbox time with a free balloon is about 60–90 s (PVC provisioning plus OpenVSCode startup). The feature is on in dev only; preprod and prod are unchanged. Runbook: docs/sandbox/runbooks/ide-warm-nodes.md.

Repo: CLOUD_IDE.md — Autoscaling and capacity.

LLM credentials (ADR 0020)

Sandbox does not inject ANTHROPIC_API_KEY / OPENAI_API_KEY. User → LLM Gateway for BYOK, budgets, and usage. Sandbox /admin/credential-plane/* and /admin/keyhive/* return HTTP 410.

Clipboard

Default paste-in-only: trainers can paste instructions in; copy/cut out is blocked (best-effort).

Verify

SANDBOX_TENANT_ID=ctp SANDBOX_PROJECT_ID=sandbox \
  SANDBOX_GATEWAY_URL=... SANDBOX_API_KEY=... \
  bash scripts/verify-viewer-ide-dev.sh