We use cookies to deliver and improve our services, analyze site usage, and if you agree, to customize or personalize your experience and market our services to you. You can read our Cookie Policy here.
Multiagent orchestration lets one agent coordinate with others to complete complex work. Agents can act in parallel with their own isolated context, which helps improve output quality and can also improve time to completion.
All Managed Agents API requests require the managed-agents-2026-04-01 beta header. The SDK sets this beta header automatically.
All agents share the same container, filesystem, and vault credentials, but each agent runs in its own session thread, a context-isolated event stream with its own conversation history. The coordinator reports activity in the primary thread (which is the same as the session-level event stream); additional threads are spawned at runtime when the coordinator delegates work.
Threads are persistent: the coordinator can send a follow-up to an agent it called earlier, and that agent retains everything from its previous turns.
Each agent uses its own configuration (model, system prompt, tools, MCP servers, and skills) as defined when that agent was created. Tools, MCP servers, and context are not shared.
Multiagent coordination is best suited for complex tasks that either require work across a variety of surfaces, or where multiple well-scoped tasks contribute to an overall goal.
Patterns that work well:
When defining your agent, set multiagent to declare the roster of agents the coordinator can delegate to:
ant beta:agents create <<YAML
name: Engineering Lead
model: claude-opus-4-7
system: You coordinate engineering work. Delegate code review to the reviewer agent and test writing to the test agent.
tools:
- type: agent_toolset_20260401
multiagent:
type: coordinator
agents:
- type: agent
id: $REVIEWER_AGENT_ID
- type: agent
id: $TEST_WRITER_AGENT_ID
YAMLmultiagent.agents can accept any of the following:
{"type": "agent", "id": agent.id} references a previously created agent by ID. Defaults to pinning the latest agent version if no version is specified.{"type": "agent", "id": agent.id, "version": agent.version} pins a specific agent version.{"type": "self"} allows the coordinator to spawn copies of itself.The coordinator can only delegate to one level of agents; depth > 1 is ignored. A maximum of 20 unique agents can be listed in multiagent.agents, but the coordinator can call multiple copies of each agent.
Create a session referencing the coordinator. The coordinator delegates to the agents in its roster as needed.
session = client.beta.sessions.create(
agent=coordinator.id,
environment_id=environment.id,
)MCP servers are agent-scoped (each agent definition declares its own servers and tools), while vault credentials are session-scoped (vault_ids passed at session creation apply to every thread). Two implications for your integration:
research_agent = client.beta.agents.create(
name="researcher",
model="claude-haiku-4-5",
mcp_servers=[
{"type": "url", "name": "github", "url": "https://api.githubcopilot.com/mcp/"},
],
tools=[{"type": "mcp_toolset", "mcp_server_name": "github"}],
)
coordinator = client.beta.agents.create(
name="coordinator",
model="claude-opus-4-7",
tools=[{"type": "agent_toolset_20260401"}],
multiagent={
"type": "coordinator",
"agents": [{"type": "agent", "id": research_agent.id}],
},
)
session = client.beta.sessions.create(
agent=coordinator.id,
environment_id=environment.id,
vault_ids=[vault.id],
)
print(session.id)In this example, only the researcher declares the GitHub MCP server, so the coordinator does not have access. The session's vault_ids supply the GitHub credential to the researcher's thread.
If an agent's MCP calls fail to authenticate after you declare the server, confirm the credential's mcp_server_url matches the agent's mcp_servers[].url exactly, including scheme and trailing slash.
The session-level event stream (/v1/sessions/:id/events/stream) is considered the primary thread, containing a condensed view of all activity across all threads. You won't see the full activity from subagents, but you will see the start and end of their work, and blocking events such as tool permission requests.
Session threads are where you drill into a specific agent's activity.
The session status is an aggregation of all agent activity; if at least one thread is running, then the overall session status is running as well.
A maximum of 25 concurrent threads are supported. The coordinator can call multiple copies of a single agent in the roster, creating multiple threads associated with one agent.
These events surface multiagent activity on the primary thread at /v1/sessions/:id/events/stream.
| Type | Description |
|---|---|
session.thread_created | A thread was created. Includes session_thread_id and agent_name. |
session.thread_status_running | A thread started activity. |
session.thread_status_idle | The agent associated with the thread is awaiting input. Includes a stop_reason indicating why the agent stopped. |
session.thread_status_terminated | A thread was archived or encountered a terminal error. |
agent.thread_message_received | An agent delivered its result to the coordinator. Includes from_session_thread_id, from_agent_name, and content. |
agent.thread_message_sent | The coordinator sent a follow-up to another agent. Includes to_session_thread_id, to_agent_name, and content. |
Critical events are proxied to the primary thread. However, you might still want to investigate a specific agent's reasoning and tool calls. To do so, stream or list the events from the associated session thread.
If a subagent needs something from your client, such as permission to run an always_ask tool, or the result of a custom tool, the event is cross-posted to the primary thread with session_thread_id identifying the originating session thread.
{
"type": "session.thread_status_idle",
"id": "sevt_01ABC...",
"session_thread_id": "sth_01DEF...",
"agent_name": "code-reviewer",
"stop_reason": {
"type": "requires_action",
"event_ids": ["toolu_01XYZ..."]
}
}Post user.tool_confirmation (with tool_use_id) or user.custom_tool_result (with custom_tool_use_id); the server routes the response to the correct thread automatically.
The following example extends the tool confirmation handler to route replies. The same pattern applies to user.custom_tool_result.
for event_id in stop.event_ids:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
}
],
)Was this page helpful?