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.
| Path | When |
|---|---|
| Integrated terminal | claude, opencode run …, codex exec … — KeyHive keys or Gateway tokens per mode |
| VS Code Tasks | Terminal → Run Task |
| Agent attach (L4) | POST /sandbox/agent/v1/runs with sandbox_id — orchestration from product UI |
| Harbor / CodeEdit API | curl 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 appBind 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.
| Field | Default | Notes |
|---|---|---|
port | auto-detected | Required when more than one app is listening |
path | / | Path and query on the app |
url | none | Only the sandbox itself: localhost, 127.0.0.0/8, ::1, or the pod IP |
viewport | 1280×800 | 320–3840 × 240–2160 |
full_page | false | Capture the whole scrollable page |
wait_ms | 1500 | Settle time after load, from 0 to 30000 |
| Status | Meaning |
|---|---|
409 | Nothing is listening on the port, the sandbox is not running, or the image has no headless browser |
422 | Invalid target: an external URL, a redirect out of the sandbox, ambiguous auto-detection, or a bad path |
502 | The app did not load (navigation error or timeout) |
413 | The 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-sandboxesservice — 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:ideWarmballoon 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 thanideWarm.replicasstill cold-starts. See Warm nodes for ide-ubuntu.
Warm nodes for ide-ubuntu
| Piece | What 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 service | After 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.shRelated
- Desktop sandboxes
- Agent runs
- Repo:
docs/sandbox/desktop/CLOUD_IDE.md