Use this flow when your application should receive Anthropic webhooks and decide which E2B sandbox should handle the work. Anthropic calls your app, your app verifies the webhook, then your app starts or reconnects the E2B worker sandbox.
flowchart LR
session["Managed Agents session"] --> webhook["Anthropic webhook"]
webhook --> app["your app webhook endpoint"]
app --> store["app-owned sandbox store"]
store --> sandbox["E2B worker sandbox"]
app --> sandbox
sandbox --> env["Anthropic self-hosted environment"]
sandbox --> workdir["/mnt/session"]
From the parent python/ directory:
uv sync
cp .env.template .envFill in .env:
| Variable | Notes |
|---|---|
E2B_API_KEY |
Required to start worker sandboxes. |
E2B_ACCESS_TOKEN |
Required to build the E2B template. |
ANTHROPIC_API_KEY |
Used to verify webhooks, read environment metadata, and update sandbox metadata. |
ANTHROPIC_ENVIRONMENT_ID |
Anthropic self-hosted environment id. |
ANTHROPIC_ENVIRONMENT_KEY |
Anthropic self-hosted environment key from the Anthropic Environments workspace. |
ANTHROPIC_WEBHOOK_SIGNING_KEY |
Required for real webhook deliveries. |
APP_WEBHOOK_ADMIN_TOKEN |
Bearer token for app-owned debug endpoints such as GET /sandboxes. |
APP_SANDBOX_STORE_PATH |
Optional path for the app-owned session-to-sandbox JSON store. Defaults to ../.managed-agent-sandbox-store.json. |
APP_SANDBOX_ROUTING_SCOPE |
Optional sandbox reuse scope: session (default), agent, or environment. |
make build-templateExpose this app endpoint from your own deployment or a tunnel while testing:
make start-app-webhook-serverRegister https://<your-app-host>/webhook in the
Anthropic Agents workspace and subscribe it
to session.status_run_started.
When Anthropic sends a run-started webhook, the app:
- Verifies the raw payload with
ANTHROPIC_WEBHOOK_SIGNING_KEY. - Wakes an app-side drain of Anthropic's self-hosted environment work queue.
- Claims queued work with the environment key.
- Computes the sandbox routing key from the claimed work item's session id and
APP_SANDBOX_ROUTING_SCOPE. - Reconnects to that route's sandbox or creates a fresh E2B sandbox, writes the store assignment,
and starts the worker with the claimed
ANTHROPIC_WORK_IDandANTHROPIC_SESSION_ID.
Routing scopes:
| Scope | Sandbox key | Use when |
|---|---|---|
session |
environment_id + session_id |
You want the strongest isolation and separate filesystem state per Managed Agents session. |
agent |
environment_id + agent.id |
You want sessions for the same agent to reuse a warm sandbox. The app retrieves the session to read agent.id. |
environment |
environment_id |
You want one shared worker sandbox for the whole self-hosted environment. |
The JSON store is a local example store. For a multi-instance app, use a database with a transactional session assignment so duplicate webhook deliveries cannot create duplicate workers. SQLite is enough for a single-node deployment; Postgres or Redis is a better fit once multiple app replicas can receive the same webhook. An in-memory catalog is only useful for a toy demo because a process restart loses the session-to-sandbox mapping needed for follow-up work.
Worker sandboxes are created with E2B auto-resume and pause-on-timeout lifecycle settings. The app does not need to manually pause them: the app claims work, the sandbox handles that claimed item, the worker exits after idle, and E2B pauses the sandbox after its timeout. A follow-up event for the same session reconnects to the same sandbox.
The app drains the environment queue because the queue, not the webhook payload, is the source of truth for which work item has been claimed. That prevents a session-owned sandbox from accidentally polling and claiming a different queued session.
Inspect the app-owned assignments:
curl -H "Authorization: Bearer $APP_WEBHOOK_ADMIN_TOKEN" http://127.0.0.1:8000/sandboxesmake stop-worker SANDBOX_ID=<E2B_WORKER_SANDBOX_ID>For the complete code-level implementation, see IMPLEMENTATION.md.