The model: brain vs. hands
Claude Managed Agents splits an agent into two halves. Claude is the brain — the LLM, the agent loop, session state, and the work queue all live on Anthropic’s infrastructure. It decides which tool to call but never executes one. The sandbox is the hands — everybash, read, write, edit, glob, grep call actually runs inside an execution environment you own.
A Claude Environment with hosting type Self-hosted doesn’t run tools itself. Instead it places a work item on a queue. Your orchestrator consumes that queue and turns each session into a Tensorlake sandbox. The sandbox runs a thin worker that attaches back to Anthropic and executes tool calls for the life of the session.
Why Tensorlake for this role
- Fast boot / sub-second wake. An agent loop is a tight decide→execute→decide cycle: many short tool calls (
bash,read,grep) separated by model think-time. A suspended sandbox resumes from its memory snapshot in ~0.6s — a restore, not a cold app boot — so the hands are ready the instant the brain calls a tool. Low per-tool-call latency without keeping a sandbox warm between turns, which is what makes Tensorlake a good home for executing the agent loop’s tool calls. - Snapshots & fork-from-snapshot.
sandbox.checkpoint()captures filesystem (or full memory) state;Sandbox.create(snapshot_id=...)boots a fresh sandbox from it. Run it N times → N children exploring in parallel from one known-good state. Basis for best-of-N tool execution, parallel sub-agents, and retry-with-divergence. - Suspend / resume. Named sandboxes suspend when idle and resume with state intact — no cold-start on every turn, and the basis for the wake-on-request trick below.
- Public port exposure.
expose_ports(...)serves a process inside the sandbox athttps://{port}-{id}.sandbox.tensorlake.aiwith TLS terminated by Tensorlake’s proxy — no reverse proxy of your own.
Three orchestrator modes
The orchestrator’s job is identical in all three (orchestrator_lib.py: get-or-create a sandbox per session, drain the queue). Only where it runs differs:
| Mode | Where it runs | Spawn latency | Needs |
|---|---|---|---|
| Webhook-in-sandbox (recommended) | Inside a Tensorlake sandbox, port exposed publicly | Sub-second, scale-to-zero | Nothing running on your side — wakes on request |
| Polling | Your machine / server | Seconds | A long-running host process |
| Webhook | Your machine / server | ~Instant | A public HTTPS endpoint + TLS |
Webhook-in-sandbox: scale-to-zero push
In the recommended mode the FastAPI receiver runs inside a Tensorlake sandbox, with port 5051 exposed at a public HTTPS URL. Anthropic pushes webhooks directly to Tensorlake — no host process, no TLS of yours. The sandbox is created with a short idle timeout, so when no inbound traffic arrives it suspends (memory and the running uvicorn process preserved). The next inbound webhook resumes it automatically. Measured: a confirmed-suspended sandbox served GET /healthz in ~0.6s and flipped to running — a memory-snapshot restore, not a cold app boot. Outbound polling from inside the sandbox does not keep it awake, so it still suspends on schedule. The result is push latency with nothing running and nothing billed while idle, which suits bursty workloads better than a long-running host process.
Long-running sessions: resume instead of recreate
The same suspend/resume primitive applies one layer down, to the per-session sandbox. A long-running agent is rarely busy throughout — it’s bursts of work separated by idle gaps (waiting on a human approval, a slow CI job, or think-time between turns). AtSANDBOX_TIMEOUT_SECONDS an idle per-session sandbox auto-suspends: filesystem and processes frozen to a snapshot, nothing billed.
The question on the next burst is whether you rebuild or restore:
| Recreate from base (default) | Resume the suspended sandbox | |
|---|---|---|
| Idle cost | Zero (suspended) | Zero (suspended) — identical |
| Resume latency | Full create + image pull + setup | Memory-snapshot restore — sub-second |
| Session working state | Lost — re-clone repo, re-install deps, redo setup | Intact — /workspace, deps, warm caches as left |
| Best for | Independent, cheap-to-setup bursts | A session whose accumulated state is the work |
orchestrator_lib.py: _find_live_sandbox treats a suspended sandbox as not-live and recreates it. Set RESUME_SUSPENDED_SESSIONS=true and it instead connects to the suspended sandbox; the runner relaunch is the inbound op that resumes it (the relaunch path already exists for the max-idle case). The clean-slate default stays available because a fresh sandbox can’t inherit a half-broken state from a previous burst — resume is opt-in for when continuity matters more than a guaranteed clean slate.
Quickstart
The full, working integration — with the exact credentials, the Console-only steps, mode-by-mode setup, verification, and troubleshooting — lives in the reference repo’sexamples/managed-agent README. That README is the source of truth; this section is just the shape of it so you know what you’re signing up for.
The setup has four stages:
- Configure —
cd examples/managed-agent, copy.env.example→.envand.env.local.example→.env.local, thenuv sync. - Tensorlake — set
TENSORLAKE_API_KEYin.env,uv run tl login, thenmake buildto build the per-session sandbox image. (Keep the SDK key and thetl loginsession pointed at the same project — see the gotchas below.) - Claude Platform — in a non-default workspace: create an Agent (
make agent), create a Self-hosted Environment, and generate its environment key (Console-only). This populatesANTHROPIC_API_KEY+ANTHROPIC_AGENT_IDin.env.localandANTHROPIC_ENVIRONMENT_ID+ANTHROPIC_ENVIRONMENT_KEYin.env. - Orchestrator — pick one mode. For webhook-in-sandbox:
make build-webhook, register the printed URL as a Webhook (Session lifecycle → Run started) with its signing secret inANTHROPIC_WEBHOOK_SIGNING_KEY, thenmake webhook-sandbox(the secret is baked in at launch, so it must be set first).
running / thinking / tool calls (→ write, → read) ending in · done. If a session never streams, the README’s work.stats check and troubleshooting section diagnose the common causes (import-order 401, mismatched Tensorlake projects, workers_polling: 0 in webhook modes).
Injecting credentials and naming sandboxes
Two Tensorlake SDK facts shape how the orchestrator passes secrets and names sandboxes:- **Inject env vars per command, not on create. Pass every credential and per-session var (
ANTHROPIC_ENVIRONMENT_KEY, and the session, work, and environment IDs) viastart_process(env={...}), which merges on top of the sandbox base environment. - Sandbox names must be slugs — lowercase letters, digits, and hyphens only. Derive a sandbox name from a session id by slugifying it (e.g.
agent-<slug>).
.env precedence, the order in which you import the SDK relative to loading credentials, and keeping the Python SDK key and the tl CLI session on the same project — are covered in the repo README’s troubleshooting section.
When you want parallelism
The parallel sub-agents example forks N sandboxes from one snapshot so a single agent session can explore N candidate solutions at once —parent.checkpoint() then Sandbox.create(snapshot_id=...) × N. Expose it to the agent as a bundled CLI helper (callable via bash), an MCP tool, or an orchestrator-side action gated by session metadata.
Next steps
Reference code
The full integration: image build, in-sandbox worker, and one orchestrator in three runnable modes.
Claude Managed Agents
Anthropic’s docs for the agent harness, sessions, and self-hosted environments.
Sandbox lifecycle
The suspend/resume and snapshot model that powers wake-on-request.
Parallel sub-agents
Fork N sandboxes from one snapshot for best-of-N tool execution.