> ## Documentation Index
> Fetch the complete documentation index at: https://e2b.manicule.dev/llms.txt
> Use this file to discover all available pages before exploring further.
> For every page in one file, fetch https://e2b.manicule.dev/llms-full.txt (large).
> To search and read these docs as tools, connect the MCP server at https://e2b.manicule.dev/api/mcp.
> Every agent surface: https://e2b.manicule.dev/.well-known/llms-manifest.json

# Claude Managed Agents (/docs/agents/claude-managed-agents)

<!-- agent-signals: reading_time_min: 5 · est_tokens: 2675 · updated: 2026-07-30 -->
Related: [Amp](/docs/agents/amp.md), [Claude Code](/docs/agents/claude-code.md), [Codex](/docs/agents/codex.md), [Crabbox with E2B](/docs/agents/crabbox.md), [Devin](/docs/agents/devin.md), [Devin Outposts](/docs/agents/devin-outposts.md)

> Use E2B as the sandbox runtime for Claude Managed Agents self-hosted environments.

[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) can use a self-hosted environment when you want tool calls to run in your own infrastructure. Claude runs the agentic loop and reasoning process; E2B provides isolated sandbox infrastructure for the environment where tool calls execute. The reusable `E2B/claude-managed-agents-webhooks` template receives webhooks and routes each Claude Managed Agents session to a persistent E2B worker sandbox.

<Info>This separation is intentional for security: Claude decides what work to do, while the sandbox contains the execution environment, filesystem, tools, network access, and runtime logs.</Info>

<Info>For the full source, local setup scripts, and app-owned routing examples, see the [Claude Managed Agents cookbook](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-managed-agents).</Info>

## Install dependencies

<CodeGroup>
```bash title="JavaScript & TypeScript"
npm install e2b @anthropic-ai/sdk
```

```bash title="Python"
pip install e2b anthropic
```
</CodeGroup>

Export the values you will pass to the webhook sandbox:

```bash
# E2B dashboard API keys: https://e2b.dev/dashboard?tab=keys
export E2B_API_KEY="..."

# Claude Console API keys: https://console.anthropic.com/settings/keys
export ANTHROPIC_API_KEY="..."

# Claude Console environments: https://platform.claude.com/workspaces/default/environments
export ANTHROPIC_ENVIRONMENT_ID="..."

# Open the self-hosted environment in the Claude Console and click Generate environment key.
export ANTHROPIC_ENVIRONMENT_KEY="..."
```

## Start the webhook sandbox

Start the public template with auto-resume enabled. The template starts a webhook server on port `8000`.
Because the signing key only appears after you register a webhook endpoint, write the router config now and add the signing key to the same sandbox after registration.

<CodeGroup>
```typescript title="JavaScript & TypeScript"
import { Sandbox } from 'e2b'

const sandbox = await Sandbox.create('E2B/claude-managed-agents-webhooks', {
  lifecycle: { onTimeout: 'pause', autoResume: true },
})

await sandbox.files.write([
  {
    path: '/opt/anthropic-managed-agents-js/config/e2b-api-key',
    data: `${process.env.E2B_API_KEY}\n`,
  },
  {
    path: '/opt/anthropic-managed-agents-js/config/anthropic-api-key',
    data: `${process.env.ANTHROPIC_API_KEY}\n`,
  },
  {
    path: '/opt/anthropic-managed-agents-js/config/anthropic-environment-id',
    data: `${process.env.ANTHROPIC_ENVIRONMENT_ID}\n`,
  },
  {
    path: '/opt/anthropic-managed-agents-js/config/anthropic-environment-key',
    data: `${process.env.ANTHROPIC_ENVIRONMENT_KEY}\n`,
  },
])

console.log(`Webhook URL: https://${sandbox.getHost(8000)}/webhook`)
```

```python title="Python"
import os
from e2b import Sandbox

sandbox = Sandbox.create(
    "E2B/claude-managed-agents-webhooks",
    lifecycle={"on_timeout": "pause", "auto_resume": True},
)

sandbox.files.write(
    "/opt/anthropic-managed-agents-js/config/e2b-api-key",
    f"{os.environ['E2B_API_KEY']}\n",
)
sandbox.files.write(
    "/opt/anthropic-managed-agents-js/config/anthropic-api-key",
    f"{os.environ['ANTHROPIC_API_KEY']}\n",
)
sandbox.files.write(
    "/opt/anthropic-managed-agents-js/config/anthropic-environment-id",
    f"{os.environ['ANTHROPIC_ENVIRONMENT_ID']}\n",
)
sandbox.files.write(
    "/opt/anthropic-managed-agents-js/config/anthropic-environment-key",
    f"{os.environ['ANTHROPIC_ENVIRONMENT_KEY']}\n",
)

print(f"Webhook URL: https://{sandbox.get_host(8000)}/webhook")
```
</CodeGroup>

<Info>At this point `/health` returns `200`, but `/webhook` returns `503` until the signing key is configured. Keep this sandbox running or paused; registering a different sandbox URL means writing the signing key into that sandbox instead.</Info>

## Register the webhook

In the [Claude Console webhooks settings](https://platform.claude.com/settings/workspaces/default/webhooks), create a webhook endpoint using the printed URL:

```text
https://<sandbox-host>/webhook
```

Subscribe it to:

```text
session.status_run_started
```

Save the generated signing key, export it locally, then write it into the same webhook sandbox.

```bash
export ANTHROPIC_WEBHOOK_SIGNING_KEY="whsec_..."
```

<CodeGroup>
```typescript title="JavaScript & TypeScript"
await sandbox.files.write(
  '/opt/anthropic-managed-agents-js/config/anthropic-webhook-signing-key',
  `${process.env.ANTHROPIC_WEBHOOK_SIGNING_KEY}\n`,
)
```

```python title="Python"
sandbox.files.write(
    "/opt/anthropic-managed-agents-js/config/anthropic-webhook-signing-key",
    f"{os.environ['ANTHROPIC_WEBHOOK_SIGNING_KEY']}\n",
)
```
</CodeGroup>

## Run an end-to-end smoke test

Create or select a Claude Managed Agents agent in the [Claude Console](https://platform.claude.com/workspaces/default/agents), then create a session with that agent and the same `ANTHROPIC_ENVIRONMENT_ID` you wrote into the webhook sandbox. Creating the session does not start work; the `user.message` event does.

```bash
export ANTHROPIC_AGENT_ID="agent_..."
```

Use a small shell task for the first smoke test:

```text
Use bash to echo webhook-smoke-ok. Answer only with that text.
```

<CodeGroup>
```typescript title="JavaScript & TypeScript"
import Claude from '@anthropic-ai/sdk'

const client = new Claude({
  apiKey: process.env.ANTHROPIC_API_KEY,
})

const session = await client.beta.sessions.create({
  agent: process.env.ANTHROPIC_AGENT_ID!,
  environment_id: process.env.ANTHROPIC_ENVIRONMENT_ID!,
  title: 'E2B webhook smoke',
})

await client.beta.sessions.events.send(session.id, {
  events: [
    {
      type: 'user.message',
      content: [
        {
          type: 'text',
          text: 'Use bash to echo webhook-smoke-ok. Answer only with that text.',
        },
      ],
    },
  ],
})

console.log(`Session ID: ${session.id}`)
```

```python title="Python"
import os
from anthropic import Anthropic as Claude

client = Claude(api_key=os.environ["ANTHROPIC_API_KEY"])

session = client.beta.sessions.create(
    agent=os.environ["ANTHROPIC_AGENT_ID"],
    environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],
    title="E2B webhook smoke",
)

client.beta.sessions.events.send(
    session.id,
    events=[
        {
            "type": "user.message",
            "content": [
                {
                    "type": "text",
                    "text": "Use bash to echo webhook-smoke-ok. Answer only with that text.",
                },
            ],
        },
    ],
)

print(f"Session ID: {session.id}")
```
</CodeGroup>

Claude should answer:

```text
webhook-smoke-ok
```

Then check the webhook sandbox:

<CodeGroup>
```typescript title="JavaScript & TypeScript"
const health = await fetch(`https://${sandbox.getHost(8000)}/health`)
console.log(await health.json())

const logs = await sandbox.commands.run(
  'tail -200 /opt/anthropic-managed-agents-js/webhook.log || true',
)
console.log(logs.stdout)

const assignments = await sandbox.commands.run(
  'cat /opt/anthropic-managed-agents-js/.managed-agent-sandbox-store.json || true',
)
console.log(assignments.stdout)
```

```python title="Python"
import json
import urllib.request

with urllib.request.urlopen(f"https://{sandbox.get_host(8000)}/health") as response:
    print(json.loads(response.read()))

logs = sandbox.commands.run(
    "tail -200 /opt/anthropic-managed-agents-js/webhook.log || true",
)
print(logs.stdout)

assignments = sandbox.commands.run(
    "cat /opt/anthropic-managed-agents-js/.managed-agent-sandbox-store.json || true",
)
print(assignments.stdout)
```
</CodeGroup>

A healthy run has:

* `/health` returning `ok: true`.
* Router logs showing `routing work` and `assigned session`.
* A session-to-sandbox assignment in the router's assignment store.
* The assigned worker sandbox's `worker.log` showing the shell tool execution and successful results posted back to Claude.
* The Claude Managed Agents session returning to `session.status_idle`.

The assignment store records the worker sandbox ID for each routed session. Connect to that sandbox and check `/opt/anthropic-managed-agents-js/worker.log` when you need the tool execution logs.

If the session stays at `requires_action`, check `/opt/anthropic-managed-agents-js/webhook.log` in the router sandbox first, then check `/opt/anthropic-managed-agents-js/worker.log` in the assigned worker sandbox. Stale environment keys, missing signing keys, archived sessions, and failed tool-result posts show up there.

## Runtime behavior

The worker sandbox runs tool calls with `/mnt/session` as its workdir. File tools are constrained to that workdir, skills are downloaded under `/mnt/session/skills/<name>/`, and generated artifacts should be written under `/mnt/session/outputs`.

The webhook sandbox is the router. It keeps a session-to-sandbox assignment store and starts worker sandboxes with E2B auto-resume and pause-on-timeout settings.

## Session-scoped sandboxes

By default, the public webhook template gives each Claude Managed Agents session its own E2B worker sandbox. Follow-up turns for the same session reconnect to the same worker and reuse its `/mnt/session` filesystem. The sandbox is the isolated execution environment, not the place where Claude's reasoning loop runs.

Use [cloud buckets](/docs/storage/cloud-buckets), [Archil](/docs/storage/archil), or [volumes](/docs/volumes) when files need to outlive a sandbox or be shared across many sandboxes.

If you want to run that router in your own service instead of in E2B, use the cookbook's [`app-webhooks/` example](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-managed-agents/javascript/app-webhooks). It receives webhooks in your app, claims work there, and routes each session to its own E2B sandbox by default.

## Clean up

Remove the webhook endpoint in the Claude Console before deleting the router sandbox. Then kill the router sandbox and any assigned worker sandboxes you no longer need.

## Related guides

<CardGroup cols="3">
<Card title="Cookbook example" icon="book-open" href="https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-managed-agents">Full JavaScript and Python examples for polling workers, sandbox-hosted webhooks, and app-owned routing.</Card>

<Card title="Sandbox persistence" icon="floppy-disk" href="/docs/sandbox/persistence">Pause and resume sandboxes to preserve filesystem state between runs.</Card>

<Card title="Cloud buckets" icon="bucket" href="/docs/storage/cloud-buckets">Store generated files outside the sandbox filesystem.</Card>
</CardGroup>
