NodeOps
UK

Run Claude Managed Agents on CreateOS Sandbox

With Claude Managed Agents, Anthropic runs the agent loop — the model, the context, the decision about which tool to call next. It does not run the tool calls. Those need a machine, and that machine is yours.

CreateOS Sandbox is that machine: a hardware-isolated sandbox that boots in about a second, holds state for as long as you want it to, and whose network egress you control from the outside. Anthropic keeps the orchestration; you keep the execution boundary — its filesystem, its network, and its logs.

Two ways to wire it up

Most sandbox vendors document exactly one of these. CreateOS supports both, and the choice is a real one, so pick deliberately.

Self-hosted environmentSandbox as a tool
Who runs the agent's basha worker inside your sandboxyour orchestrator, in a sandbox it owns
Anthropic environment typeself_hostedcloud
Agent's toolsthe built-in agent toolset (bash, file edit, …)one custom tool you define
Credentials on your hostorg key + environment keyorg key only
Who reaches out to whomyour worker polls Anthropic's work queueAnthropic streams you a tool call; you answer it
Best forfull agentic coding — the agent wants a real filesystem it can live ina narrow, auditable execution surface: the agent may run this, and nothing else

Both keep the agent's execution inside CreateOS. They differ in how much of the agent's world lives there.

Self-hosted environment

The agent's own toolset runs in your sandbox. Anthropic hands work to a queue; a worker process inside the sandbox claims it, runs the tool call locally, and posts the result back. The agent gets a real machine — a filesystem it can build up over a session, a shell, a network — and none of it leaves the sandbox.

Claude Managed Agent (Anthropic)   ──▶  work queue  ◀── worker polls
                                                          │
                                          ┌───────────────┴───────────────┐
                                          │  CreateOS sandbox             │
                                          │  ant beta:worker poll         │
                                          │  → the agent's bash runs here │
                                          └───────────────────────────────┘

This is the topology Anthropic's self-hosted sandboxes guide describes, and the one every other sandbox vendor's integration page documents.

Sandbox as a tool

Anthropic hosts the agent loop (a cloud environment), and CreateOS is a discrete tool the agent reaches for. The agent emits a run_command tool call; your orchestrator runs it in a sandbox it owns and hands the output back.

Claude Managed Agent (Anthropic)
        │  agent.custom_tool_use  { command: "…" }
        ▼
your orchestrator  (holds the CreateOS API key — the agent never sees it)
        │  sandbox.runCommand("bash", ["-lc", command])
        ▼
CreateOS sandbox   ──── output ────▶  user.custom_tool_result

Give the agent only the custom tool — omit the built-in agent toolset — and CreateOS becomes its single execution path. There is no second shell for it to fall back to. The control-plane key stays on your host, and every command the agent runs passes through code you wrote, where you can log it, rewrite it, or refuse it.

Prerequisites

  • A CreateOS Sandbox API key and control-plane URL.
  • An Anthropic organization API key with the Managed Agents beta enabled (Console → API keys).
  • For a self-hosted environment only: an environment and an environment key (Console → Environments → Generate environment key). The environment key authenticates the worker to its queue and is a different credential — and a different auth scheme — from the org key. A cloud environment needs neither: nothing of yours polls the queue.

Set up a self-hosted environment

On the Anthropic side. Create a self_hosted environment and generate an environment key for it.

TypeScript
1const environment = await anthropic.beta.environments.create({
2 name: "createos-workers",
3 config: { type: "self_hosted" },
4});

On the CreateOS side. Boot a sandbox with the environment credentials in its environment, install the ant CLI, and start the worker as a daemon. It polls for work until you tear the sandbox down.

TypeScript
1const sandbox = await Sandbox.create({
2 shape: "s-4vcpu-4gb",
3 rootfs: "devbox:1",
4 envs: {
5 ANTHROPIC_BASE_URL: "https://api.anthropic.com",
6 ANTHROPIC_ENVIRONMENT_ID: environment.id,
7 ANTHROPIC_ENVIRONMENT_KEY: environmentKey, // never the org key
8 },
9});
10
11await sandbox.sh(`curl -fsSL "$ANT_RELEASE_URL" | tar -xz -C /usr/local/bin ant`);
12await sandbox.sh(
13 "nohup setsid ant beta:worker poll --workdir /workspace > /tmp/worker.log 2>&1 &",
14);

Every session you point at that environment now executes inside the sandbox.

TypeScript
1const agent = await anthropic.beta.agents.create({
2 name: "createos-worker",
3 model: "claude-haiku-4-5",
4 tools: [{ type: "agent_toolset_20260401" }],
5});
6const session = await anthropic.beta.sessions.create({
7 agent: agent.id,
8 environment_id: environment.id,
9});

Only the environment key is inside the sandbox. The org key stays on your host — so a prompt injection that fully compromises the agent still cannot spend your organization's credits or read your other sessions.

Set up sandbox-as-a-tool

Create a cloud environment and an agent whose only tool is yours.

TypeScript
1const environment = await anthropic.beta.environments.create({
2 name: "createos-tool",
3 config: { type: "cloud", networking: { type: "unrestricted" } },
4});
5
6const agent = await anthropic.beta.agents.create({
7 name: "createos-tool-agent",
8 model: "claude-haiku-4-5",
9 system:
10 "Your only way to run anything is the run_command tool, which runs a shell " +
11 "command on a remote Linux machine. Report its output verbatim.",
12 // No agent_toolset: the agent has no bash of its own, so every command it
13 // wants to run must go through CreateOS.
14 tools: [
15 {
16 type: "custom",
17 name: "run_command",
18 description: "Run a shell command on the remote Linux machine.",
19 input_schema: {
20 type: "object",
21 properties: { command: { type: "string" } },
22 required: ["command"],
23 },
24 },
25 ],
26});

Then run the loop. Open the event stream before sending the task — events emitted in between would otherwise be lost — and answer every tool call.

TypeScript
1const session = await anthropic.beta.sessions.create({
2 agent: agent.id,
3 environment_id: environment.id,
4});
5
6const stream = await anthropic.beta.sessions.events.stream(session.id);
7await anthropic.beta.sessions.events.send(session.id, {
8 events: [{ type: "user.message", content: [{ type: "text", text: task }] }],
9});
10
11for await (const event of stream) {
12 if (event.type === "agent.custom_tool_use") {
13 const output = await runInSandbox(String(event.input.command));
14 await anthropic.beta.sessions.events.send(session.id, {
15 events: [
16 {
17 type: "user.custom_tool_result",
18 custom_tool_use_id: event.id,
19 content: [{ type: "text", text: output }],
20 },
21 ],
22 });
23 } else if (event.type === "session.status_idle") {
24 // `requires_action` means the agent is idle *waiting on your tool result* —
25 // the run is not over. Only a real stop reason ends it.
26 if (event.stop_reason?.type === "requires_action") continue;
27 break;
28 }
29}

Sandbox lifecycle: per session, or per call

runInSandbox is where you choose what the agent's world looks like.

One sandbox per session — create it before the session starts, reuse it for every tool call, destroy it at the end. State persists: a file the agent writes in one call is there in the next, and the agent can build something up over a conversation.

A fresh sandbox per call — create, run, destroy, every time. Nothing persists, so nothing carries over: no cross-call contamination, no machine to clean up, and the blast radius of any single command is a sandbox that is already gone. The cost is a cold start per call, which on CreateOS measures 0.7–1.0 s end to end (create, run, destroy).

Pick per-call for untrusted or one-shot work, per-session for anything that needs to accumulate.

Why CreateOS

  • Egress you control from outside the sandbox. setEgress locks a sandbox to an allowlist; the rules are enforced in-kernel on the host, so code inside the sandbox cannot bypass them. An agent can reach your private service and still be unable to exfiltrate to the public internet — enforced, not merely prompted.
  • Hardware isolation. Every sandbox is its own kernel, not a shared-kernel container. That is the boundary you want between a language model's output and your infrastructure.
  • ~1 s cold start. Fast enough that a fresh sandbox per tool call is a real option, not a theoretical one.
  • State when you want it. Pause and resume a sandbox, snapshot it, or reattach a disk — a session can survive an idle user without burning compute.
  • Your keys stay yours. In both topologies the CreateOS API key lives on your host. In the sandbox-as-a-tool topology the agent's every command passes through your code first.

Gotchas

An empty tool result is rejected. Managed Agents rejects a tool result with no content. Always send something — (no output) is fine.

Report command failures, do not throw them. Sandbox.sh throws on a non-zero exit. Inside a tool body that is wrong: a failing command is a result the agent must see and reason about, not an orchestrator crash. Use runCommand and hand the exit code back with the output.

TypeScript
1const { result } = await sandbox.runCommand("bash", ["-lc", command]);
2const output = `${result.stdout}${result.stderr}`.trim() || "(no output)";
3return result.exit_code === 0 ? output : `exit ${result.exit_code}\n${output}`;

Hold the stream open. In the sandbox-as-a-tool topology the agent blocks on your tool result. If your orchestrator drops the stream mid-call, the session strands. A single-shot script is fine as-is; a long-lived service needs reconnect handling.

session.status_idle does not mean the run is over. It fires with stop_reason.type: "requires_action" while the agent waits on you. Break only on a real stop reason.

Anthropic does not stage files for you. A self-hosted sandbox starts empty — no repo, no session files. Pass what the agent needs through session.metadata and have your worker fetch it.

Working examples

All five run end to end against a live control plane.

ExampleTopologyWhat it shows
36 — self-hosted agent workerself-hostedone persistent sandbox running the worker
37 — sandbox per sessionself-hosteda fresh sandbox and worker per session
49 — egress-locked workerself-hostedthe agent reaches a private service but cannot exfiltrate
50 — sandbox as a tool, per sessionsandbox-as-a-toolone sandbox for the session; state survives between tool calls
51 — sandbox as a tool, per callsandbox-as-a-toola disposable sandbox per call; the same task, and the state is gone

Examples 50 and 51 run the same two-step task — write a file, then read it back in a separate tool call — so the difference between the two lifecycles is something you can watch happen.

100,000+ Builders. One Platform.

Get product updates, builder stories, and early access to features that help you ship faster.

NodeOps is the agentic operating system for production AI. CreateOS is its flagship product.