claude-code-system-prompts/system-prompts/data-managed-agents-self-hosted-sandboxes.md
2026-05-20 09:45:59 -06:00

10 KiB
Raw Blame History

Managed Agents — Self-Hosted Sandboxes

With config.type: "self_hosted", the agent loop stays on Anthropic's orchestration layer but tool execution moves to infrastructure you control — bash, file ops, and code run inside your container, so filesystem contents and network egress never leave your environment. Contrast with config.type: "cloud", where Anthropic runs the container. Connectivity is outbound-only: your worker long-polls Anthropic's work queue; Anthropic never dials into your network.

Flow

1. Create environment:      config: {type: "self_hosted"}        → env_...
2. Generate environment key (Console, on the environment page)   → sk-ant-oat01-...  as ANTHROPIC_ENVIRONMENT_KEY
3. Run a worker:            EnvironmentWorker.run()  or  ant beta:worker poll
4. Sessions reference       environment_id=env_... exactly as for cloud

Create the environment

client = anthropic.Anthropic()

environment = client.beta.environments.create(
    name="self-hosted", config={"type": "self_hosted"}
)

{"type": "self_hosted"} is the entire config — there are no pool, capacity, or networking sub-fields; you control those on your side.

Run a worker — SDK (primary path)

EnvironmentWorker wraps the poll → dispatch → tool-execute loop. .run() is the always-on loop; .run_one() / .runOne() handles one work item (for webhook-driven wake).

Python — always-on:

import asyncio
import os
from anthropic import AsyncAnthropic
from anthropic.lib.environments import EnvironmentWorker


async def main() -> None:
    environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
    environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
    async with AsyncAnthropic(auth_token=environment_key) as client:
        await EnvironmentWorker(
            client,
            environment_id=environment_id,
            environment_key=environment_key,
            workdir="/workspace",
        ).run()


asyncio.run(main())

TypeScript — always-on:

import Anthropic from "@anthropic-ai/sdk";
import { EnvironmentWorker } from "@anthropic-ai/sdk/helpers/beta/environments";

const environmentKey = process.env.ANTHROPIC_ENVIRONMENT_KEY!;
const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!;
const client = new Anthropic({ authToken: environmentKey });
const ctrl = new AbortController();
process.once("SIGTERM", () => ctrl.abort());

await new EnvironmentWorker({
  client,
  environmentId,
  environmentKey,
  workdir: "/workspace",
  signal: ctrl.signal
}).run();

Customizing tools. EnvironmentWorker runs the built-in toolset by default. To add or replace tools, use AgentToolContext(workdir=, client=, session_id=) with beta_agent_toolset(env) / betaAgentToolset(env) and pass the resulting tools to the lower-level tool_runner(). Skills attached to the agent are downloaded into {workdir}/skills/<name>/ before tool calls begin (AgentToolContext handles this when given client and session_id). Downloaded skill files are marked executable automatically by the CLI and SDK; if you implement skills download yourself, you set permissions.

Runtime deps: the SDK helpers require /bin/bash at that exact path. The TypeScript SDK additionally requires unzip, tar, and Node.js 22+. These are resolved at fixed paths and do not respect PATH overrides.

Run a worker — ant CLI (fixed tools)

The ant CLI ships a worker with the fixed built-in toolset (bash, read, write, edit, glob, grep). Install per shared/anthropic-cli.md, then:

export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat01-...
ant beta:worker poll --environment-id env_... --workdir /workspace
  • --workdir is the directory tools operate in (default .); tool calls are sandboxed to it.
  • --environment-key overrides the env var.
  • --on-work <script> runs your script per work item (e.g. to spin a fresh container per session — see Container orchestration below).
  • --unrestricted-paths, --max-idle (default 60s), --log-format — see ant beta:worker poll --help.
  • Flags fall back to env vars (ANTHROPIC_ENVIRONMENT_ID, ANTHROPIC_ENVIRONMENT_KEY).
  • Exits cleanly on SIGTERM/SIGINT after draining in-flight work.
  • Fixed toolset — for custom tools, use the SDK worker above.

Inside an --on-work container, run ant beta:worker run --workdir <dir> as the entrypoint.

Webhook-driven wake (instead of always-on)

Register a webhook for session.status_run_started (see shared/managed-agents-webhooks.md), verify the delivery, then drain one work item with .run_one():

import os
import anthropic
from anthropic.lib.environments import EnvironmentWorker

environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"]
environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
client = anthropic.AsyncAnthropic(
    auth_token=environment_key,
)  # reads ANTHROPIC_WEBHOOK_SIGNING_KEY from env for webhooks.unwrap()


async def handle(raw: bytes, headers: dict[str, str]) -> dict:
    event = client.beta.webhooks.unwrap(raw.decode(), headers=headers)
    if event.data.type != "session.status_run_started":
        return {"status": "ignored"}
    await EnvironmentWorker(
        client,
        environment_id=environment_id,
        environment_key=environment_key,
        workdir="/workspace",
    ).run_one()
    return {"status": "ok"}

TypeScript: same shape with client.beta.webhooks.unwrap(body, {headers}) and new EnvironmentWorker({...}).runOne().

Container orchestration (mid-level)

EnvironmentWorker.run() polls and executes tools in the same process. To run each session in its own container, use the mid-level poller in a thin orchestrator — Python client.beta.environments.work.poller(environment_id=, environment_key=, drain=, block_ms=, reclaim_older_than_ms=, auto_stop=); TypeScript new WorkPoller({client, environmentId, environmentKey, autoStop}) from @anthropic-ai/sdk/helpers/beta/environments — and, for each yielded work item, start a fresh container with these env vars injected, whose entrypoint runs ant beta:worker run or an EnvironmentWorker(...).run_one(). block_ms is 1999 (or None for non-blocking); reclaim_older_than_ms re-claims items leased to a dead worker; drain stops once the queue is empty; auto_stop posts a stop signal after the iterator exits (set False when the launched container owns the stop call). Go's poller has no auto_stop opt-out — it calls work.Stop when the handler returns, so block in the handler until the session completes rather than detaching.

Env var Value
ANTHROPIC_SESSION_ID work.data.id
ANTHROPIC_WORK_ID work.id
ANTHROPIC_ENVIRONMENT_ID work.environment_id
ANTHROPIC_ENVIRONMENT_KEY pass through
ANTHROPIC_BASE_URL pass through

Skip items where work.data.type != "session".

Monitoring & control

These are control-plane calls — authenticate with x-api-key (not the environment key); managed-agents-2026-04-01 beta header. Call them from outside the worker host — setting ANTHROPIC_API_KEY on the worker host exposes an organization-scoped credential to agent tool calls.

SDK (client.beta.environments.work.*) REST CLI Returns
stats(environment_id) GET /v1/environments/{id}/work/stats ant beta:environments:work stats {type:"work_queue_stats", depth, pending, oldest_queued_at, workers_polling}
stop(work_id, environment_id=) POST /v1/environments/{id}/work/{work_id}/stop ant beta:environments:work stop work.state

What changes vs cloud

Concern cloud self_hosted
Container lifecycle, hardening, networking Anthropic You — run non-root, read-only rootfs, drop caps; egress is whatever your VPC/firewall allows
file / github_repository resource mounting Anthropic mounts into the container You — pass pointers via sessions.create(metadata={...}) and have your orchestrator fetch/clone before dispatch
memory_store resources Supported Not yet supported
Built-in tools Via agent_toolset_20260401 Supplied by your worker (EnvironmentWorker default / beta_agent_toolset(env) / ant CLI fixed set)
Skills download Automatic EnvironmentWorker / AgentToolContext fetch into {workdir}/skills/ (needs client + session_id)
Claude Platform on AWS Supported Not available
SDK worker helpers All SDKs Python, TypeScript, Go only (EnvironmentWorker / poller not in Java, Ruby, PHP, or C#) — use one of those three or the ant CLI

Credentials

Credential Format Scope
ANTHROPIC_ENVIRONMENT_KEY sk-ant-oat01-... One environment's work queue. Generate in Console ("Generate environment key"). Pass as auth_token= / authToken on the client and as environment_key= / environmentKey on EnvironmentWorker. Store in a secrets manager; rotate on exposure.
ANTHROPIC_WEBHOOK_SIGNING_KEY whsec_... Webhook signature verification (if using webhook-driven wake). The SDK reads this env var automatically for client.beta.webhooks.unwrap().

Security — what you own

Container hardening; egress restriction (there is no default); ANTHROPIC_ENVIRONMENT_KEY custody and rotation; one workspace + environment per trust boundary when running untrusted code; least-privilege for the tool process; log retention and redaction. Anthropic cannot: fast-revoke a leaked environment key, verify your image or supply chain, sandbox tool execution inside your container, or enforce retention after tool output reaches your infrastructure. See the Self-Hosted Sandboxes Security page in shared/live-sources.md for the full checklist.