Communication with Claude Managed Agents is event-based. You send user events to the agent, and receive agent and session events back to track status.
All Managed Agents API requests require the managed-agents-2026-04-01 beta header. The SDK sets the beta header automatically.
Events flow in two directions.
Event type strings follow a {domain}.{action} naming convention.
Every event includes a processed_at timestamp indicating when the event was recorded server-side. If processed_at is null, it means the event has been queued by the harness and will be handled after preceding events finish processing.
When the agent invokes a custom tool:
agent.custom_tool_use event containing the tool name and input.session.status_idle event containing stop_reason: requires_action. The blocking event IDs are in the stop_reason.requires_action.event_ids array.user.custom_tool_result event for each, passing the event ID in the custom_tool_use_id param along with the result content.running.When a permission policy requires confirmation before a tool executes:
agent.tool_use or agent.mcp_tool_use event.session.status_idle event containing stop_reason: requires_action. The blocking event IDs are in the stop_reason.requires_action.event_ids array.user.tool_confirmation event for each, passing the event ID in the tool_use_id param. Set result to "allow" or "deny". Use deny_message to explain a denial.running.The session object includes a usage field with cumulative token statistics. Fetch the session after it goes idle to read the latest totals, and use them to track costs, enforce budgets, or monitor consumption.
{
"id": "sesn_01...",
"status": "idle",
"usage": {
"input_tokens": 5000,
"output_tokens": 3200,
"cache_creation_input_tokens": 2000,
"cache_read_input_tokens": 20000
}
}input_tokens reports uncached input tokens and output_tokens reports total output tokens across all model calls in the session. The cache_creation_input_tokens and cache_read_input_tokens fields reflect prompt caching activity. Cache entries use a 5-minute TTL, so back-to-back turns within that window benefit from cache reads, which reduce per-token cost.
Was this page helpful?
with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop := event.stop_reason):
match stop.type:
case "requires_action":
for event_id in stop.event_ids:
# Look up the custom tool use event and execute it
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Send the result back
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.custom_tool_result",
"custom_tool_use_id": event_id,
"content": [{"type": "text", "text": result}],
},
],
)
case "end_turn":
breakwith client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop := event.stop_reason):
match stop.type:
case "requires_action":
for event_id in stop.event_ids:
# Approve the pending tool call
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
break