Claude Managed Agents
Run Anthropic's Claude Managed Agents in your own PandaStack microVMs — Claude drives the agent loop, your sandboxes execute the tool calls.
Claude Managed Agents let Anthropic run the agent loop — the model, the reasoning, the multi-turn session state — while the agent's tool calls execute in infrastructure you control. Anthropic calls this a self-hosted sandbox, and a PandaStack microVM is an ideal one: every session gets a fresh, isolated Firecracker VM that boots from a snapshot in ~49 ms and can hibernate between turns.
This guide wires the two together. By the end you'll have an agent whose bash, file, and code tool calls run inside PandaStack, with the agent's filesystem, processes, and network egress never leaving your environment.
How it fits together
Anthropic's self-hosted environment is a work queue. When you create a session that targets it, Anthropic enqueues the session as a work item. A small orchestrator you run claims the item, spins up a PandaStack sandbox, and launches Anthropic's pre-built in-guest runner (ant beta:worker run) inside it. The runner executes the agent's tool calls locally and streams results back.
Anthropic (agent loop) ──work queue──▶ your orchestrator ──create + exec──▶ PandaStack microVM
▲ │ │
└────────────── tool results ───────────┴────────── stdout / outputs ──────────┘Only tool execution is yours. Tool inputs and outputs still flow to Anthropic so Claude can reason over results and decide the next step — see Anthropic's security model for the exact data boundary.
Why PandaStack is a good host for this:
- Per-session isolation, instantly. Each session is its own microVM, created on the snapshot-restore fast path (p50 ~49 ms) — no warm pool, no shared kernel.
- Stateful multi-turn for free. Hibernate the sandbox between turns; the next turn auto-wakes it with memory and filesystem intact.
- Real egress control. Each sandbox runs in its own network namespace, so you decide what the agent can reach.
A concrete example
Maya builds a Claude agent that reads messy patient spreadsheets and writes Python to clean them up. Her constraint: patient data legally cannot leave the hospital's network, so the agent's code can't run on a vendor's cloud — it has to run on infrastructure she controls.
That's exactly what a self-hosted environment is for. Maya runs PandaStack in her own VPC and starts one command:
npm install -g @pandastack/sdk
pandastack cma --agent-id agent_01...Now whenever her agent decides to run code, that code executes inside a fresh Firecracker microVM on her servers — booted in ~49 ms, isolated by its own kernel and network namespace, and torn down (or hibernated) when the turn ends. Claude still drives the reasoning from Anthropic's side; only the execution is Maya's. Patient data never leaves her network, and she didn't have to write or copy any orchestration code to get there.
The rest of this guide is the full version of those two lines.
Prerequisites
- A PandaStack API token (
pds_...) and theclaude-agenttemplate available on your hosts (it ships in the first-party catalog — checkGET /v1/templates). - An Anthropic account with Managed Agents access (the
managed-agents-2026-04-01beta). - An agent defined in Anthropic's Console or API.
The template
PandaStack ships a first-party claude-agent template: Ubuntu 24.04 with the ant CLI (Anthropic's pre-built environment worker) plus mise-managed Node 22 and Python 3.12 so agent-authored code runs immediately. It exposes the working directory Anthropic expects:
/workspace— where the runner downloads the agent's skills and where tools read and write files./mnt/session/outputs— where the agent is told to write final deliverables.
You don't normally build this yourself; it's part of the catalog. If you want to customize it (extra system packages, a different runtime), copy templates/claude-agent/Dockerfile, add what you need, and bake it like any other custom template.
1. Create a self-hosted environment
A self_hosted environment is the work queue your orchestrator drains.
curl https://api.anthropic.com/v1/environments \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{"name": "pandastack-self-hosted", "config": {"type": "self_hosted"}}'
# { "id": "env_...", ... }Then generate an environment key in the Console (this step is Console-only): Workspace → Environments → your environment → Generate environment key. It looks like sk-ant-oat01-....
The orchestrator host only ever needs the environment key, never your organization API key. The environment key authenticates polling for this one environment and nothing else in your account — keep your sk-ant-api03-... key off the worker host.
2. Run the worker
The worker drains the queue: it polls for work items, creates (or reuses) a sandbox per session, launches the in-guest runner, collects outputs, and hibernates or deletes the sandbox on idle. It ships in the PandaStack CLI (both the Python and TypeScript SDKs provide it), so running it is one command:
npm install -g @pandastack/sdk # or: pip install pandastack
export ANTHROPIC_ENVIRONMENT_ID=env_...
export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat01-...
export PANDASTACK_API_KEY=pds_...
pandastack cma
# [hh:mm:ss] PandaStack worker 'pandastack-…' polling environment env_…With the Python package the same worker runs as python -m pandastack cma — pip installs the SDK library only; the pandastack command belongs to the npm CLI.
Every setting also has a flag (pandastack cma --help): --environment-id, --environment-key, --token, --api, --template, --on-idle {hibernate,running,delete}, --outputs-dir, --worker-id, --ttl-seconds, --max-session-seconds. Flags override the matching environment variables.
To run the worker inside your own Python process (a service, a function, a custom poller) instead of as a CLI, use the same Worker the CLI does:
from pandastack.cma import Worker, CmaConfig
# CmaConfig.from_env() reads ANTHROPIC_ENVIRONMENT_ID / _KEY, PANDASTACK_API_KEY,
# and the CMA_* tunables; pass keyword overrides to set them explicitly.
Worker(CmaConfig.from_env(on_idle="hibernate")).run()A complete, runnable example — plus a run_demo.py that drives a session end to end — lives in examples/claude-managed-agents.
What the worker does under the hood
Per work item, in ~50 lines of equivalent logic:
import os, shlex, requests
from pandastack import Sandbox
ENV_ID = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
ENV_KEY = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
ANTHROPIC = "https://api.anthropic.com"
H = {
"Authorization": f"Bearer {ENV_KEY}",
"anthropic-version": "2023-06-01",
"anthropic-beta": "managed-agents-2026-04-01",
"Anthropic-Worker-ID": "pandastack-worker-1",
}
def get_or_create_sandbox(session_id: str) -> Sandbox:
for sb in Sandbox.list():
if (sb.metadata or {}).get("cma.session_id") == session_id:
return sb # returning turn: reuse (auto-wakes on first exec)
return Sandbox.create(
template="claude-agent",
metadata={"cma.session_id": session_id},
)
def launch_runner(sb: Sandbox, session_id: str, work_id: str) -> None:
# Write the env + launch script via the filesystem API (keeps the
# environment key out of command logs), then start the runner detached.
script = f"""#!/bin/sh
export ANTHROPIC_ENVIRONMENT_ID={shlex.quote(ENV_ID)}
export ANTHROPIC_ENVIRONMENT_KEY={shlex.quote(ENV_KEY)}
export ANTHROPIC_SESSION_ID={shlex.quote(session_id)}
export ANTHROPIC_WORK_ID={shlex.quote(work_id)}
rm -f /tmp/cma.exit
{{ setsid sh -c 'ant beta:worker run --workdir /workspace; echo $? > /tmp/cma.exit' </dev/null >/var/log/cma.log 2>&1 & }}
echo launched
"""
sb.filesystem.write("/tmp/launch.sh", script)
sb.exec("sh /tmp/launch.sh; rm -f /tmp/launch.sh", timeout_seconds=30)
while True:
r = requests.get(f"{ANTHROPIC}/v1/environments/{ENV_ID}/work/poll",
params={"block_ms": 999}, headers=H, timeout=30)
work = r.json() if r.status_code == 200 and r.content else None
if not work or not work.get("id"):
continue
work_id = work["id"]
session_id = work["data"]["id"]
requests.post(f"{ANTHROPIC}/v1/environments/{ENV_ID}/work/{work_id}/ack",
headers=H, timeout=30)
sb = get_or_create_sandbox(session_id)
launch_runner(sb, session_id, work_id)
# then watch /tmp/cma.exit, pull /mnt/session/outputs, hibernate or deletepandastack cma adds the parts trimmed here for clarity: a watcher that polls the runner's exit sentinel, output download via the filesystem API, hibernate-vs-delete on idle, graceful shutdown, and per-session bookkeeping.
3. Start a session
With the orchestrator polling, create a session that targets the environment and send it a task. This uses your organization API key (session creation is an operator-side call):
SESSION=$(curl -s https://api.anthropic.com/v1/sessions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d "{\"agent\": \"$AGENT_ID\", \"environment_id\": \"$ENV_ID\"}")
SESSION_ID=$(echo "$SESSION" | jq -r .id)
curl -s "https://api.anthropic.com/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{"events":[{"type":"user.message","content":[{"type":"text","text":"Write a haiku about microVMs to /mnt/session/outputs/haiku.txt"}]}]}'The session enters the work queue; the worker claims it, creates a claude-agent sandbox, and the agent's tool calls run there. Stream the session events to watch the agent work in real time. Anything the agent writes to /mnt/session/outputs is pulled back by the worker when the session goes idle.
Multi-turn sessions
Managed Agents sessions are multi-turn: after the agent goes idle (end_turn), sending another user.message to the same SESSION_ID re-queues work, and the worker routes it back to the same sandbox. What happens to that sandbox between turns is controlled by --on-idle (or CMA_ON_IDLE):
--on-idle | Between turns | Next-turn latency | /workspace state | Best for |
|---|---|---|---|---|
hibernate (default) | Snapshot + stop; frees CPU/RAM | Sub-second auto-wake | Preserved | Stateful multi-turn at scale |
running | Left up, holds CPU/RAM | Instant (VM already up) | Preserved | Low-concurrency / latency-sensitive |
delete | Torn down | ~49 ms recreate | Lost | One-shot / stateless agents |
hibernate is the default because it's the only mode that is both stateful and resource-efficient at rest: the sandbox isn't holding a VM's CPU/RAM while the user thinks, yet the agent resumes — process memory and /workspace files intact — in under a second. This is the payoff of running on PandaStack: stateful multi-turn agent sessions with sub-second resume, instead of rebuilding a fresh container and re-cloning state on every turn.
The worker self-heals across all modes: if a hibernated sandbox can't be woken on a later turn, it recreates a fresh one rather than stranding the session, and a safety-net TTL reaps abandoned sandboxes.
Monitoring and scaling
Read the queue depth and worker liveness (uses your organization API key, from outside the worker host):
curl -s "https://api.anthropic.com/v1/environments/$ENV_ID/work/stats" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" | jq
# { "depth": 0, "pending": 0, "workers_polling": 1, "oldest_queued_at": null }depth— items waiting to be claimed. Scale workers (run morepandastack cmaprocesses, each with a distinct--worker-id) on backlog.pending— items claimed and in flight.workers_polling— workers that polled in the last 30 s; alert if this drops to 0.
Production notes
- Webhook instead of polling.
pandastack cmais an always-on poller (only needs outbound HTTPS). To skip the idle poller, drive the same create-and-launch logic —pandastack.cma.Workerexposes it — from a webhook handler that fires onsession.status_run_started; a PandaStack function is a good home for it. - Staging files into a session. Anthropic doesn't mount files or repos into self-hosted sandboxes. Pass references (an S3 path, a commit SHA) in the session
metadata; a custom worker reads them off the claimed work item and stages the files into/workspacebefore launching the runner. - Outputs at rest. The worker pulls
/mnt/session/outputsto local disk (--outputs-dir). For durable storage, push to a bucket. (Mounting a volume at that path would force a cold boot and forfeit the snapshot-restore fast path, so copy via the filesystem API instead.) - Stopping a session. To end a session from your ops tooling, call
work/stopon its work item (POST /v1/environments/{env}/work/{work_id}/stop, org API key). The runner finishes its in-flight tool call, posts a final status, and exits; pass{"force": true}to interrupt immediately. - Customizing the template. If you build your own image instead of the catalog
claude-agent, the worker harness requires a Linux guest with/bin/bashat that exact path.antis a single static binary needing only bash; agent skills may ship executables, which theantrunner marks executable automatically on download.
Limitations
- Memory. Managed Agents memory is not currently supported with self-hosted environments.
- No file/repo mounting. Anthropic does not mount files or GitHub repositories into self-hosted sandboxes — stage them yourself via session
metadata(see above). - Empty tool output. The current
antworker (1.12.1, onanthropic-sdk-gov1.50.1) posts an empty-text tool result when abashcommand succeeds with no stdout (e.g.cd, a redirect, a file write), which Anthropic's API rejects (minimum string length is 1) and which stalls the session. This is an upstream SDK bug pending a fix. The demo agent works around it with a system-prompt instruction to append a confirmation (&& echo done) to commands that would print nothing; add the same line to your own agent's system prompt.
See also
- Sandbox lifecycle — create, hibernate, wake.
- Templates — customize the
claude-agentimage. - AI agents — the inverse pattern, where you run the agent loop and give it a sandbox tool.
- Anthropic's self-hosted sandboxes guide.