Build your first Claude Managed Agent: a hosted agent that codes in its own sandbox
On November 12, Anthropic opened a private beta for running its whole Managed Agents service inside a customer's own cloud. The hosted version — the one anyone with an API key can use today — has been in public beta since April, and it changes the shape of agent work: instead of writing your own loop, your own tool execution, and your own sandbox, you configure three things and let Anthropic run the rest. This tutorial builds a working coding agent on that API, end to end, with every call verified against the Python SDK.
- The four objects: agent (the configuration), environment (the sandbox), session (one run), events (the stream between you and the agent). Three API calls create them; everything after that is streaming.
- The price: standard token rates for Claude Sonnet 5.5 — $2 per million input tokens, $10 per million output — plus $0.08 per session-hour, metered to the millisecond and charged only while the session is running.
- The beta header: every Managed Agents request must carry
anthropic-beta: managed-agents-2026-04-01. The Python SDK sets it for you — verified in SDK 2.8.0 source — so you never pass it by hand. - Verification: the code in this tutorial was run end to end against a mocked API transport (no key was available in the verification sandbox), so every request shape, parameter, and cleanup call is exactly what the SDK sends — but the model only replies when your key is in the environment.
- Self-hosted is separate: the self-hosted edition announced November 12 is a private-beta product for enterprises. This tutorial uses the hosted API, which is enabled by default for all API accounts.

Building an agent the old way means writing four things yourself: the loop that calls the model, the code that executes its tools, the sandbox those tools run in, and the storage that keeps the conversation alive between turns. Each one is a project. Managed Agents is Anthropic's answer to that: the loop, the sandbox, and the state live on its servers, and your code shrinks to configuration plus a stream reader.
That is a genuine trade. You give up control over the runtime in exchange for not operating it — and for sessions that keep running, and keep their history, even if your process disconnects. Whether the trade is right depends on the workload, and there is a section at the end that lays out the alternatives honestly. First, the build: by the end of this tutorial you will have created an agent, an environment, and a session, watched the agent write and run a Python script inside its own sandbox, sent it a follow-up, and torn everything down.
What you'll need#
- Python 3.10 or later. The Anthropic SDK requires it; check with
python3 --version. - The Anthropic Python SDK, version 2.3.0 or later (
pip install anthropic). Version 2.8.0 is current at time of writing, and every call below was verified against it. - An Anthropic API key from the Claude Console at platform.claude.com, set as the
ANTHROPIC_API_KEYenvironment variable. Managed Agents is enabled by default for all API accounts — there is no waitlist or separate signup for the hosted service. - A few dollars of API credit. The worked example in this tutorial costs about $0.15 at standard rates. The session carries a $5 hard budget cap, set in Step 4, so the worst case is bounded before you start.
One note on how this tutorial was verified, because it affects what you will see: no API key was available in the verification sandbox, so the script was executed end to end against a mocked transport standing in for the API. The mock asserted the exact request paths, the beta header on every call, and the request bodies — model, toolset, networking, budget, event shapes — and fed back canned events in the real SSE format. The request shapes, parameters, and cleanup calls are therefore exactly what the SDK sends. The transcript shown in Step 5 is the shape of a real run; the model's actual words and tool calls in your live run will differ, because a real model is doing the work.
The four concepts#
Everything in Managed Agents hangs off four resources. Learn them once and the API reads plainly:
| Resource | What it is | Created |
|---|---|---|
| Agent | The configuration: model, system prompt, tools, MCP servers, skills. Versioned — updating one creates a new version. | Once, then reused across sessions |
| Environment | Where sessions run: an Anthropic-managed cloud sandbox, or a self-hosted sandbox on your own infrastructure. Holds the networking rules and pre-installed packages. | Once, then reused across sessions |
| Session | One running instance of an agent in an environment, doing one task. Owns its sandbox, its event history, and its budget. | Per task |
| Events | The messages between you and the agent: your turns, its replies, its tool calls and results, status changes. An append-only log that lives server-side. | Continuously, while a session runs |
The mental model that makes it click: the agent is the job description, the environment is the office, the session is one shift, and events are the paper trail. You write the first two once and start as many sessions against them as your workload needs.

Step 1 — Install the SDK and set your key#
pip install "anthropic>=2.3.0"
export ANTHROPIC_API_KEY="sk-ant-..."
The SDK reads ANTHROPIC_API_KEY from the environment, so the key never appears in your code. Keep it that way — the tutorial script below constructs its client with a bare Anthropic() and nothing else. If the key is missing, the constructor raises immediately rather than failing on the first API call, which makes a bad environment easy to spot.
Managed Agents lives under client.beta in the SDK — client.beta.agents, client.beta.environments, client.beta.sessions — because the service is in public beta. The beta in the path is about the product's status, not about your code being experimental: the endpoints, the pricing, and the header are all documented and stable for the beta.
Step 2 — Create the agent#
The agent is a reusable configuration. This one is a careful coding assistant: Sonnet 5.5 for the model, a short system prompt, and the built-in toolset with the web tools switched off, because the task in this tutorial never needs the web.
from anthropic import Anthropic
MODEL = "claude-sonnet-5-5" # $2 / MTok in, $10 / MTok out
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
# --- Step 2: create the agent -------------------------------------------
# The agent is a reusable configuration: model, system prompt, tools.
agent = client.beta.agents.create(
model=MODEL,
name="scratch-coder",
description="Writes and runs small Python scripts in a sandbox.",
system=(
"You are a careful coding assistant. Work in the sandbox only. "
"Write the file, run it, and report the exact output. "
"If a step fails, read the error and fix it before moving on."
),
tools=[
{
"type": "agent_toolset_20260401",
"configs": [
{"type": "web_search", "name": "web_search", "enabled": False},
{"type": "web_fetch", "name": "web_fetch", "enabled": False},
],
}
],
)
print(f"agent: {agent.id} (version {agent.version})")
Three choices here deserve a sentence each. The model is claude-sonnet-5-5 — the middle of Anthropic's current lineup at $2 per million input tokens and $10 per million output, and the right default for a task this small. Fable 5.2 ($8/$40) and Opus 5.5 ($4/$20) exist for harder work; Haiku 5.5 ($0.10/$0.50) for high-volume routine work. The model field also accepts a model_config object if you later want fast mode or a pinned inference geography, but a plain model string is all this tutorial needs. The toolset is the single built-in bundle, agent_toolset_20260401, which enables bash, file read/write/edit, glob, grep, web search, and web fetch. The configs list overrides individual tools — here it disables the two web tools. Give an agent only the tools its task needs; an agent that cannot reach the web cannot leak anything to it, and cannot burn your $10-per-1,000-searches budget on curiosity. The system prompt is short and behavioral rather than encyclopedic: work in the sandbox, report exact output, fix errors before moving on. That last clause matters — an agent that stops at the first error is a demo; one that reads the traceback and retries is a worker.
The call returns the agent with an id like agnt_... and a version starting at 1. Keep the id: sessions reference it, and every later change to the agent creates a new version while old sessions keep running against theirs.
Step 3 — Create the environment#
The environment is the sandbox the agent's tools run in. This one is deliberately strict: limited networking, package managers allowed, and the requests library pre-installed so the task can prove the package setup works.
env = client.beta.environments.create(
name="scratch-coder-env",
description="Limited network, pip packages allowed.",
config={
"type": "cloud",
"networking": {"type": "limited", "allow_package_managers": True},
"packages": {"type": "packages", "pip": ["requests"]},
},
)
print(f"environment: {env.id}")
The config has three layers. type: "cloud" means Anthropic hosts the sandbox; the alternative is a self-hosted sandbox on your own infrastructure, which keeps tool execution inside your perimeter while Anthropic still runs the agent loop. networking set to limited with allow_package_managers: True lets code inside the sandbox reach public package registries — PyPI, npm, and the like — and nothing else. With no allowed_hosts listed, there is no outbound web access at all, which is consistent with disabling the web tools in Step 2: even if the agent tried to fetch a URL, the sandbox would refuse. If your agent genuinely needs the web, add the hosts it needs to allowed_hosts and re-enable the tools — the allowlist then applies to web search and web fetch too, even though those run outside the sandbox. packages pre-installs dependencies at environment build time, so the agent never has to run pip install itself; versions use the package manager's own syntax, so requests==2.32.3 would pin one.

Step 4 — Start a session with a budget cap#
A session pairs the agent with the environment and gives it a task. Two details in this call do most of the safety work: the budget, and the fact that the task arrives as an event, not as a parameter.
TASK = (
"Write a Python script called primes.py that computes the first 20 prime "
"numbers, saves them to primes.json, and prints the installed requests "
"library version. Then run the script with python3 and confirm primes.json "
"exists and holds 20 numbers."
)
session = client.beta.sessions.create(
agent=agent.id,
environment_id=env.id,
title="first-managed-agent-run",
budget={
"type": "limit",
"max_list_cost": {"amount": "500", "currency": "USD"},
},
initial_events=[
{"type": "user.message", "content": [{"type": "text", "text": TASK}]}
],
)
print(f"session: {session.id} (status: {session.status})")
The agent field takes the agent id string, which pins the latest version for the session; passing an object with both id and version pins a specific one instead. The budget is a hard ceiling: max_list_cost is a monetary amount in minor units, so "500" cents is $5.00, and the session stops issuing new model requests once the tracked list cost reaches it. List price is used regardless of any negotiated discount, so the cap fires at or before the actual charge. Omit the budget and the session is uncapped — don't. The task itself goes in initial_events as a user.message event with a text block. That shape is worth memorizing, because every later message you send uses the same one: events are the only way to talk to a running session.
The task is deliberately small and checkable: write a file, run it, confirm the output. It exercises the full loop — the agent must use the write tool, the bash tool, and its own judgment about verification — without needing the network, which the sandbox doesn't have. It also proves the package setup: if requests wasn't pre-installed, the version print fails and you'll know immediately.
Step 5 — Stream the run until the agent goes idle#
Sessions are asynchronous: creating one starts the work, and you watch it through the event stream. Open a stream, print the agent's messages, log its tool calls, and stop when it goes idle.
with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="", flush=True)
elif event.type == "agent.tool_use":
print(f"\n[tool] {event.name}: {event.input}")
elif event.type == "session.status_idle":
print("\n[session idle — task done]")
break
Three event types carry almost everything you need in a first project. agent.message holds the agent's replies as text blocks. agent.tool_use names the tool and shows its input — this is your audit trail, and in production you would log every one. session.status_idle means the agent has nothing more to do; the stream ends there. The event carries a stop_details field when there is something to report — a budget reached, a refusal, retries exhausted — so check it when a session stops earlier than you expected. The stream is a context manager: breaking out of the loop closes it cleanly, and the session itself keeps living server-side. You can open a new stream on the same session later and pick up exactly where you left off.
What the output looks like — the shape of it, from the verification run:
agent: agnt_... (version 1)
environment: env_...
session: sess_... (status: running)
I wrote primes.py and ran it.
[tool] bash: {'command': 'python3 primes.py'}
[session idle — task done]
In your live run the agent's words will be longer and the tool calls will include the file write and the verification read, in whatever order the model chooses. What won't change is the structure: messages, tool calls, idle. If the run stalls, the transcript is your debugger — open the session in the Claude Console, where every tool call and decision is laid out on a visual timeline.
Step 6 — Follow up, then clean up#
A session is a conversation, not a single shot. Send a follow-up as another user.message event — same shape as the first one — and stream again. The agent still has its sandbox, its files, and its history, so it can answer from the work it already did.
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{"type": "text", "text": "Show me the first five lines of primes.json."}
],
}
],
)
with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="", flush=True)
elif event.type == "session.status_idle":
print("\n[session idle]")
break
Expect the reply to list the first five primes — 2, 3, 5, 7, 11 — read back from the file the agent wrote in the previous turn. That round trip is the whole point of a stateful session: the second message is cheap because the first one already did the work.
Then tear everything down. Sessions and environments delete; agents archive, because they are versioned records rather than disposable objects:
client.beta.sessions.delete(session.id)
client.beta.environments.delete(env.id)
client.beta.agents.archive(agent.id)
print("cleaned up: session deleted, environment deleted, agent archived")
Deleting the session also deletes the server-side transcript and sandbox state, which is the part to remember for the privacy section below: if you want the record, fetch the event log with client.beta.sessions.events.list(session.id) before you delete. Agents and environments are cheap to keep — reuse them across sessions instead of recreating them per task — but delete the session when the work is done, or it keeps its history on Anthropic's servers indefinitely.
What it costs#
Managed Agents bills on two dimensions: tokens at the standard model rates, and session runtime. The runtime charge is small enough to forget and large enough to respect at scale.
| Line item | Rate | Notes |
|---|---|---|
| Sonnet 5.5 input tokens | $2 / million | Cache reads $0.10 / million |
| Sonnet 5.5 output tokens | $10 / million | — |
| Session runtime | $0.08 / session-hour | Metered to the millisecond; only while status is running |
| Web search inside a session | $10 / 1,000 searches | Standard rate; this tutorial disables it |
A worked example at standard rates. A ten-minute session that consumes 30,000 input tokens and 8,000 output tokens costs: input 30,000 × $2 / 1,000,000 = $0.06; output 8,000 × $10 / 1,000,000 = $0.08; runtime 10 / 60 × $0.08 = $0.013 — about $0.15 all in. Anthropic's own docs give a larger example for comparison: a one-hour Opus 5.5 coding session with 50,000 input and 15,000 output tokens totals $0.58. Two things keep real bills small: the runtime meter stops the moment the session goes idle, and prompt caching applies identically inside sessions, so repeated system prompts and conversation history read back at the cache rate. The budget cap from Step 4 is the backstop for everything else.
Which approach should you use?#
Anthropic now offers four ways to run a Claude agent, and they overlap enough to confuse. The deciding question is who you want running the loop:
| Managed Agents (hosted) | Messages API | Agent SDK | Self-hosted Managed Agents | |
|---|---|---|---|---|
| Who runs the loop | Anthropic | You | You (library) | You |
| Sandbox | Anthropic cloud | You build it | You build it | Your infrastructure |
| Session state | Server-side, persistent | You store it | You store it | Your database |
| Best for | Long-running, asynchronous work | Custom loops, fine-grained control | Claude Code-style agents inside your app | Regulated environments, data residency |
| Access | Public beta, all API accounts | Generally available | Generally available | Private beta, account team only |
Pick Managed Agents when the task runs for minutes or hours, when it should survive your process disconnecting, and when you don't want to operate a sandbox fleet. Pick the Messages API when the loop itself is the product — custom orchestration, unusual tooling, tight control over every turn. Pick the Agent SDK when you want Claude Code's machinery inside your own application, on your own machines. Pick self-hosted when the hosted service's data handling is the blocker: the private beta moves the server, the database, the workers, the credentials, and the history into your cloud, and the model calls route to Anthropic, Bedrock, Google's Agent Platform, Microsoft Foundry, or your own gateway. Note what self-hosting does not do: Claude itself still runs outside your network, scheduled deployments and webhooks are missing from the beta, and nothing migrates from the hosted service — you rebuild agents, skills, vaults, and history from scratch.
Gotchas worth knowing before you ship#
- The beta header is automatic in the SDK, manual everywhere else. If you call the API with raw HTTP, every request needs
anthropic-beta: managed-agents-2026-04-01. Without it you get a 403, which is the most common first error — and the least informative one. - Sessions are stateful by design, and that has a privacy cost. Transcripts, sandbox state, and outputs persist server-side until you delete them, and the hosted service is not currently eligible for Zero Data Retention or HIPAA BAA coverage. If that rules it out, the self-hosted edition is the answer, not a workaround.
- The built-in toolset is files, bash, web, and MCP. There is no computer-use tool in Managed Agents — keyboard-and-mouse automation still runs in an environment you control. MCP servers connect through the
mcp_serversfield on the agent, and every server must be referenced by anmcp_toolsetintoolsor the request is rejected. - No Batch discount, no partner clouds. The Batch API's 50% discount does not apply to sessions — they are stateful and interactive, so there is no batch mode — and Managed Agents is not available on partner-operated cloud platforms.
- Rate limits are organization-level. The reference documents separate limits for reads and writes; if you run many sessions in parallel, check them before you scale, and prefer reusing one agent and environment across sessions rather than creating fresh ones per task.
- Prefer the Console quickstart for the no-code path. If you want to see an agent run before writing anything, the Claude Console builds one from a template or a plain-language description. Claude Code can do the same from your terminal with
/claude-api managed-agents-onboard. Either way you end up with the same three resources this tutorial creates by hand — and now you know what they are.
The takeaway#
Managed Agents removes the three most tedious parts of building an agent — the loop, the sandbox, and the state — and leaves you with configuration and a stream reader. The whole first project is four calls: create an agent, create an environment, create a session, stream the events. The discipline that makes it production-ready is all in the details around those calls: least-privilege tools, a strict network policy, a budget cap set before the first token is spent, and cleanup when the work is done. Start with the hosted API and the script in this tutorial; reach for the Agent SDK when the loop belongs in your process, and for self-hosted when the data rules demand it.
Sources: Anthropic docs — Get started with Claude Managed Agents; Anthropic docs — Claude Managed Agents overview; Anthropic docs — Pricing (Claude Managed Agents section); Anthropic — Claude Managed Agents: get to production 10x faster (April 8, 2026); AI Frontier Post — Anthropic lets enterprises self-host the full Claude Managed Agents stack (November 12, 2026). Model and session pricing verified against Anthropic's pricing docs on November 13, 2026; SDK calls verified against anthropic Python SDK 2.8.0.