Self-hosted sandboxes support the models exposed by your OMA deployment. The model is configured on the agent, not the environment.
How it differs from cloud environments
Self-hosting is a good fit when the agent needs to operate on data that cannot leave your network boundary, reach internal services that are not publicly routable, or run under your organization’s own compliance and audit controls.
For Zero Data Retention and HIPAA BAA eligibility, see API and data retention.
When to combine with MCP tunnels
Self-hosting controls where the agent’s code executes. MCP tunnels control how OMA reaches MCP servers in your network. They are independent: a session running in OMA’s cloud sandboxes can still reach private MCP servers through a tunnel, and a self-hosted session can use either tunneled or public MCP servers. Use both when you want execution and tool access to stay inside your boundary. To give the agent tools from an MCP server inside your network without running a tunnel, you can also wrap the server as custom tools served by your worker.Environment worker
An environment worker is a process you run on your own infrastructure. It receives tool execution requests from OMA and runs them locally. Theself_hosted environment acts as a work queue: when a session is assigned to it, OMA enqueues the session as a work item. Your worker claims work items from that queue, spawns an execution context for each one, downloads the agent’s skills (reusable, filesystem-based resources that give the agent domain-specific expertise), runs the tool calls, and posts the results back.
Work items are claimed by polling the environment’s queue: either by an always-on worker that polls continuously, or a webhook-triggered handler that wakes on session.status_run_started and starts polling.
The CLI and SDK both ship pre-built workers. The ant CLI supports the always-on pattern only; the SDK supports both always-on and webhook-triggered. Both are configurable: see Self-hosted worker in the reference for CLI flags, and SDK helpers on this page for the SDK options. For more control, call the Environments Work endpoints directly and implement your own worker.
Sandbox filesystem
/workspace: the system default working directory for tool execution and skill download. The CLI’s--workdirflag defaults to the current directory; pass--workdir /workspaceto match the system default. Skills are downloaded to<workdir>/skills/<name>/. If you use a different working directory, update your agent’s system prompt so the agent can locate the skill files.- Outputs: on self-hosted environments the session’s system prompt omits the
/mnt/session/outputsinstruction used on OMA-managed sandboxes, so final deliverables land wherever the agent writes them in your sandbox filesystem, typically under the working directory.
Before you begin
You need:- An existing agent. If you don’t have one, complete the Quickstart first and note its agent ID.
- A Linux host with
/bin/bashat that exact path. The worker’s bash tool invokes it directly, without consultingPATH. The TypeScript SDK additionally requiresunzipandtaron thePATHand Node.js 22 or later; the Python and Go SDKs use their standard libraries for archive extraction and have no additional binary requirements. - The
antCLI or an OMA SDK (Python, TypeScript, or Go) on the worker host. - Two credentials: an environment key (generated in the Console in the steps that follow) authenticates the worker to its queue; your OMA API key creates sessions and reads queue stats from outside the worker host. Key generation is Console-only.
Environment workers authenticate to the queue with an environment key generated by OMA. When a worker runs on AWS or another cloud, continue to protect the host with your deployment’s IAM, secret manager, and network policies; OMA does not depend on third-party managed policies.
1
Create a self-hosted environment
In the Console: Workspace > Environments > New > Self-hostedOr through the API:
2
Generate an environment key
In the Console, open the environment and click Generate environment key. Key generation is Console-only, regardless of whether you created the environment through the Console or the API. Then export the environment ID and key on the worker host:
Skills can include executables that the agent may run directly. The CLI and SDK workers preserve the executable permissions recorded in the skill bundle when they extract it. If you implement skills download manually, you are responsible for setting executable permissions.
Run a worker
Choose always-on for the simplest setup: a long-running process polls the queue continuously and needs only outbound HTTPS. Choose webhook-triggered to avoid running an idle poller; it requires a webhook endpoint that OMA can reach (see Webhooks for endpoint setup and signature verification).- Always-on (ant CLI)
- Always-on (SDK)
- Webhook-triggered (SDK)
1
Install the ant CLI
Run this on the worker host.
- curl (Linux/WSL)
- Homebrew (macOS)
For Linux environments, download the release binary directly.You can find all releases on the GitHub releases page.
2
Run the worker
In-processThe worker exits cleanly on SIGTERM or SIGINT: it cancels any in-flight tool call, posts its error result, and releases the work item before stopping.Sandbox per sessionIf you need stronger isolation (a fresh filesystem, resource limits, or per-session network controls), run each session in its own sandbox. Build an image with Then write a spawn script that forwards session details into a fresh sandbox. The poller injects Start the poller pointing at the script:
ant beta:worker poll claims work items assigned to the environment, downloads skills, executes tool calls in the working directory, and posts results back. It reads ANTHROPIC_ENVIRONMENT_KEY and ANTHROPIC_ENVIRONMENT_ID from the environment.ant installed and ant beta:worker run as the entrypoint. The base image must provide /bin/bash; curl is only used at build time. When a sandbox starts, it reads session details from environment variables, handles that session, and exits:ANTHROPIC_SESSION_ID, ANTHROPIC_WORK_ID, ANTHROPIC_ENVIRONMENT_ID, and ANTHROPIC_ENVIRONMENT_KEY into the script’s environment. ANTHROPIC_BASE_URL is optional and is passed through only if it was set on the poller host; it overrides the default API endpoint. In the example, /host/outputs is a host directory you choose; it is bind-mounted to the sandbox’s working directory (/workspace) so you can retrieve session deliverables after the sandbox exits. On self-hosted environments the agent writes deliverables under the working directory rather than /mnt/session/outputs (see Sandbox filesystem), so mounting the working directory is what captures them; the mount also picks up the downloaded skills/ tree and any intermediate files the agent creates.SDK helpers
The SDK provides three helpers at different levels of control.EnvironmentWorker covers most use cases; drop to the lower-level helpers when you need to launch your own per-session process or run tools against an already-claimed session.
-
EnvironmentWorker: the out-of-the-box worker. Handles polling, setup, and execution end to end..run(): runs indefinitely, picking up sessions as they arrive..handle_item(): handles a single claimed work item and exits. Pass the work, session, and environment identifiers explicitly, or let it read theANTHROPIC_*variables thatant beta:worker poll --on-worksets for the process it spawns.
-
work.poller(): polls the work queue on your behalf and gives you each claimed session. Use this when you want to decide what happens for each session, for example launching a sandbox rather than running tools in-process.drain: whether to stop polling once the queue is empty rather than waiting for new work.block_ms: how long to wait for work to arrive before returning, in milliseconds. Must be between 1 and 999 (per-poll wait; the helper re-polls automatically). Passnull(Nonein Python,param.Null[int64]()in Go) for a non-blocking check; omitting the parameter uses the default 999 ms long-poll.reclaim_older_than_ms: re-claim work items that were claimed but never acknowledged within this many milliseconds.auto_stop: whether to post a stop signal for each work item once your loop body finishes with it. The Go poller has no opt-out and always posts the stop signal, so block in the loop body until the session completes rather than detaching.
-
client.beta.sessions.events.tool_runner(): runs tool calls for a single session, given the session ID and a tool list. Use when you’ve already claimed the work and only need the execution layer.
AgentToolContext is the execution context for tool calls. It defines the working directory and path policy, and can download the session’s skills. beta_agent_toolset_20260401(env) takes an AgentToolContext and returns the standard tool implementations (bash, read, write, edit, glob, grep).
With EnvironmentWorker: both are managed automatically. Pass a tools factory to customize the tool list:
work.poller() and tool_runner(): pass a tool list as tools to client.beta.sessions.events.tool_runner(). To build that list, set up AgentToolContext yourself and call beta_agent_toolset_20260401(env):
Verify the worker is connected
From a separate shell, withOMA_API_KEY set to your OMA API key (not the environment key), confirm workers_polling is at least 1:
workers_polling stays at 0, the worker isn’t reaching the queue: confirm ANTHROPIC_ENVIRONMENT_KEY and ANTHROPIC_ENVIRONMENT_ID are set on the worker host. See Read queue depth for the full stats response and other language examples.
Start a session
Once your worker is running, create a session that targets the environment. SetAGENT_ID to the agent ID you noted in Before you begin. The session enters the environment’s work queue and waits there until a worker claims it; if no worker is connected, the session stays queued rather than failing.
OMA doesn’t mount files or GitHub repositories into self-hosted sandboxes. To make session-specific files available, pass file references (such as an S3 path or commit SHA) in the session metadata field. The claimed work item doesn’t carry the session’s metadata, but it does carry the session ID: your spawn script or --on-work handler retrieves the session (GET /v1/sessions/{session_id}) to read the metadata field, then stages the files into the working directory before tool execution begins.
Self-hosted sandboxes don’t support
resources entries; a session that includes any resource on a self-hosted environment is rejected.Serve custom tools from your sandbox
Custom tools are tools your own code executes: the agent emits anagent.custom_tool_use event and waits for a matching user.custom_tool_result. The worker can be that code, and because it runs inside your sandbox, the tool reaches the internal services, credentials, and network egress you configured for the sandbox, and nothing more. The environment key authorizes posting custom tool results, so your OMA API key stays off the worker host.
Serving custom tools requires the SDK worker: the
ant CLI worker has no way to register a custom tool implementation. In the sandbox-per-session pattern, run EnvironmentWorker inside the sandbox with handle_item() (handleItem in TypeScript, HandleItem in Go) in place of ant beta:worker run.1
Declare the tool on the agent
Add a
custom entry to the agent’s tools whose name matches the tool your worker registers. See Custom tools for the full declaration shape.2
Register the implementation with the worker
Pass the tool through the worker’s
tools factory (see SDK helpers), alongside the built-in toolset:requires_action stop reason until something posts its result; see Handling custom tool calls for the event flow.
Wrap an MCP server as custom tools
The MCP connector connects to MCP servers from OMA’s side, so a server must expose an HTTP endpoint that OMA can reach, directly or through an MCP tunnel. To use a server that only your network can reach, make the worker the MCP client instead and declare the server’s tools as custom tools. The MCP server needs no inbound connectivity from outside your network; OMA receives the tool definitions you declare on the agent, each call’s input, and the result your worker posts back. At runtime the model calls a wrapped tool like any other custom tool:- The agent emits an
agent.custom_tool_useevent. - The worker, inside your sandbox, forwards the call over its open MCP session to the server on your network.
- The worker posts the server’s response as the
user.custom_tool_result.
pip install "anthropic[mcp]" "mcp>=1.24", npm install @modelcontextprotocol/sdk, go get github.com/modelcontextprotocol/go-sdk). The examples connect without authentication; to send credentials, configure the HTTP client or request options you hand to the MCP transport (http_client in Python, requestInit in TypeScript, HTTPClient in Go).
1
Declare the server's tools on the agent
List the MCP server’s tools and declare each one as a
custom tool; the MCP name, description, and inputSchema map one to one onto the custom tool’s fields. If the server paginates its tool list, declare every page; the worker must list the same pages.2
Serve the tools from the worker
Connect to the same MCP server at startup, convert its tools with the MCP helpers, and register them alongside the built-in toolset. Keep one MCP session open for the life of the worker.
- Tools are declared, not discovered at runtime. The worker lists the MCP server’s tools once at startup and cannot add tools to a running session. When the server’s tools change, declare them again, on the agent or on an idle session through Updating the agent configuration, and restart the worker.
- Names and descriptions must fit the Managed Agents API. Custom tool names are unique per agent and use letters, digits, underscores, and hyphens (1–128 characters); a non-empty description is required; and an agent’s
toolsarray takes at most 128 entries (each wrapped tool is one entry, and the built-in toolset is one more). The API rejects a declaration that reuses a tool name, names a custom tool after a built-in agent tool such asbashorread, or uses the reservedmcp__prefix. The MCP helpers keep the server’s names and descriptions, so rename or trim where needed. When two servers expose the same tool name, define the wrapper yourself under a prefixed name and have it call the server’s original tool name. - Most schemas pass through unchanged. The API accepts the JSON Schema keywords MCP servers commonly emit, such as
additionalPropertiesandtitle. It rejects reference keywords such as$refanywhere in a custom tool’sinput_schema, so inline the schemas that generators such as pydantic factor into$defs. It also rejects top-leveloneOf,anyOf, andallOf, and property names outside letters, digits, underscores, dots, and hyphens (1–64 characters). - Tool failures surface as error tool results. When the MCP server reports a tool error, the worker posts an error tool result the model can react to. MCP content with no tool result equivalent, such as audio blocks and resource links, also surfaces as an error. Set a timeout on the MCP client for a faster and clearer failure, as the Python worker example does with
read_timeout_seconds. Without one, a hung call becomes an error result only when the TypeScript MCP SDK’s default request timeout fires (about a minute) or when the worker’s own backstop does: about two and a half minutes in Python, and two minutes in Go, where the worker cancels a tool call that outlives its 120-second default and posts an error result. - Wrap servers you operate or trust. A wrapped tool’s name, description, and results enter the model’s context like any other tool’s: untrusted input that can influence what the agent does with its other tools, including
bashon the worker host. Declare only the tools you intend the agent to use. - Permission policies do not apply to custom tools. Permission policies govern the built-in and MCP toolsets; the worker executes every wrapped tool call the model makes, so put any approval step in your own tool code.
Monitoring and operations
These calls run from your monitoring or operations tooling, authenticated with your OMA API key, to observe and manage the worker fleet. The claim and keep-alive loop is handled inside the worker helpers, so you don’t call those endpoints directly.Read queue depth
work.stats returns the queue state for an environment:
depthis the number of items waiting to be claimed. Scale your worker fleet or alert on backlog based on this value.pendingis the number of items claimed by a worker but not yet acknowledged. The worker helpers acknowledge each item before processing it, so this value stays near zero in normal operation; a sustained non-zero value means a worker stalled between claiming and acknowledging.oldest_queued_atis the timestamp of the oldest item still in the queue, waiting to be claimed or claimed but not yet acknowledged, ornullwhen there is none.workers_pollingis the number of workers that have polled in the last 30 seconds. Use this for liveness alerting.
Stop a session gracefully
Usework.stop to ask the worker handling a specific session to shut it down. By default the work item moves to stopping: the worker notices on its next lease heartbeat, cancels the session’s in-flight tool call, and confirms the shutdown, at which point the work item becomes stopped. Pass force: true in the request body (with the CLI, pass --force) to mark the work item stopped immediately instead of waiting for the worker’s confirmation.
Because these calls run from your operations tooling rather than the worker host, ANTHROPIC_WORK_ID isn’t set automatically. Set it to the target work item’s ID before running the following examples. To find a work item’s ID, list the environment’s work items through the Environments Work endpoints.
Next steps
Security model
Shared responsibility model for self-hosted sandbox environments.
Start a session
Create a session to run your agent and begin executing tasks.
MCP tunnels
Securely connect agents to MCP servers running in your private network without opening inbound ports or exposing services to the public internet.