10 KiB
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/bashat that exact path. The TypeScript SDK additionally requiresunzip,tar, and Node.js 22+. These are resolved at fixed paths and do not respectPATHoverrides.
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
--workdiris the directory tools operate in (default.); tool calls are sandboxed to it.--environment-keyoverrides 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(default60s),--log-format— seeant 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 1–999 (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.