> ## Documentation Index
> Fetch the complete documentation index at: https://e2b-mishushakov-disable-sdk-ref-cron.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Claude Managed Agents

> 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 JavaScript & TypeScript theme={null}
  npm install e2b @anthropic-ai/sdk
  ```

  ```bash Python theme={null}
  pip install e2b anthropic
  ```
</CodeGroup>

Export the values you will pass to the webhook sandbox:

```bash theme={null}
# 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 JavaScript & TypeScript theme={null}
  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 Python theme={null}
  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 theme={null}
https://<sandbox-host>/webhook
```

Subscribe it to:

```text theme={null}
session.status_run_started
```

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

```bash theme={null}
export ANTHROPIC_WEBHOOK_SIGNING_KEY="whsec_..."
```

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

  ```python Python theme={null}
  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 theme={null}
export ANTHROPIC_AGENT_ID="agent_..."
```

Use a small shell task for the first smoke test:

```text theme={null}
Use bash to echo webhook-smoke-ok. Answer only with that text.
```

<CodeGroup>
  ```typescript JavaScript & TypeScript theme={null}
  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 Python theme={null}
  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 theme={null}
webhook-smoke-ok
```

Then check the webhook sandbox:

<CodeGroup>
  ```typescript JavaScript & TypeScript theme={null}
  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 Python theme={null}
  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>
