Claude Managed Agents give you hosted infrastructure for running autonomous Claude agents without building your own agent loop, sandbox, or event stream. This tutorial walks through the actual code: creating an agent, spinning up an environment, starting a session, streaming events, and wiring in a custom tool.
Prerequisites
Python 3.9+, an Anthropic API key, and the anthropic Python package. Install it with:
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
Step 1: Create the Agent
An agent bundles a model, a system prompt, and the tools it is allowed to call.
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY
agent = client.beta.managed_agents.agents.create(
name="release-notes-agent",
model="claude-sonnet-4-6",
system_prompt="Draft release notes from git diffs.",
tools=[{"type": "bash_20250124", "name": "bash"}],
)
print(agent.id)
Step 2: Create an Environment and Start a Session
The environment is the sandbox the agent executes in. A session is one run of the agent inside that environment.
environment = client.beta.managed_agents.environments.create(
agent_id=agent.id,
type="sandbox",
)
session = client.beta.managed_agents.sessions.create(
agent_id=agent.id,
environment_id=environment.id,
input="Summarize the last 10 commits into release notes.",
)
print(session.id, session.status)
Step 3: Stream Events and Read the Result
A session emits events as the agent thinks, calls tools, and finishes. Loop over the stream instead of polling for status.
for event in client.beta.managed_agents.sessions.events.stream(
agent_id=agent.id,
session_id=session.id,
):
if event.type == "tool_call":
print("Tool call:", event.tool_name, event.input)
elif event.type == "message":
print("Agent:", event.text)
elif event.type == "session_completed":
print("Done:", event.result)
break
Step 4: Add a Custom Tool
Bash covers shell commands, but most real agents need at least one custom tool. Define the schema, register it on the agent, then handle the resulting tool_call event yourself.
def get_open_prs(repo: str):
# call your own GitHub API wrapper here
return [{"number": 42, "title": "Fix flaky test"}]
agent = client.beta.managed_agents.agents.update(
agent_id=agent.id,
tools=[
{"type": "bash_20250124", "name": "bash"},
{
"name": "get_open_prs",
"description": "Return open pull requests for a repo",
"input_schema": {
"type": "object",
"properties": {"repo": {"type": "string"}},
"required": ["repo"],
},
},
],
)
When the agent calls get_open_prs, submit the result back on the same event loop:
if event.type == "tool_call" and event.tool_name == "get_open_prs":
result = get_open_prs(**event.input)
client.beta.managed_agents.sessions.events.submit_tool_result(
agent_id=agent.id,
session_id=session.id,
tool_call_id=event.id,
output=result,
)
Step 5: Full Working Example
Everything above, combined into one runnable script:
from anthropic import Anthropic
client = Anthropic()
agent = client.beta.managed_agents.agents.create(
name="release-notes-agent",
model="claude-sonnet-4-6",
system_prompt="Draft release notes from git diffs.",
tools=[{"type": "bash_20250124", "name": "bash"}],
)
environment = client.beta.managed_agents.environments.create(
agent_id=agent.id, type="sandbox"
)
session = client.beta.managed_agents.sessions.create(
agent_id=agent.id,
environment_id=environment.id,
input="Summarize the last 10 commits into release notes.",
)
for event in client.beta.managed_agents.sessions.events.stream(
agent_id=agent.id, session_id=session.id
):
if event.type == "session_completed":
print(event.result)
break
Common Errors and Fixes
429 rate limited: back off and retry, the same as the standard Messages API.
Session stuck in queued: check environment.status before creating the session; a sandbox that failed to provision never runs a session.
Tool call never resolves: the loop is waiting on submit_tool_result for that tool_call_id. Make sure every tool_call event gets a matching submission, even on error paths.
Where This Fits
If you already run your own agent loop with the Messages API, Managed Agents is not a replacement, it is an alternative for cases where you would rather not operate the sandbox, retries, and event plumbing yourself. For quick scripts and tight control, stick with the Messages API. For longer-running or production agents, the managed path removes a lot of the undifferentiated infrastructure.
The API surface above is illustrative of the Managed Agents building blocks (agent, environment, session, event); check the official Claude Platform docs for exact current method names before shipping.