Multiagent orchestration
Let one agent delegate to others inside a single session in Orca Agent Engine, and read each delegate's work on its own thread.
A coordinator is an agent that can delegate to other agents during a session. The agents it may call are declared on the coordinator as a roster, and each one runs with its own model, system prompt, and tool allowlist while sharing the coordinator's sandbox and filesystem.
Delegation happens at run time. The coordinator decides when to call a delegate and what to ask it, and each delegate's conversation lands on its own session thread rather than in the main event stream.
Get your registry endpoint
Registry endpoint
Examples on this page target your registry endpoint - the deployment host root, with no path
suffix. For CLI, set ORCA_REGISTRY_URL and exactly one of ORCA_ACCESS_TOKEN (Bearer) or
ORCA_API_KEY (x-api-key). For TypeScript SDK, set ORCA_BASE_URL / ORCA_API_KEY (Bearer).
To find the endpoint, see Connect to the registry.
Declare a roster
multiagent is a field on the agent, so a coordinator is an ordinary agent with one extra key:
{
"type": "coordinator",
"agents": [
{ "type": "agent", "id": "agt_01H8...", "version": 3 },
{ "type": "self" }
]
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | coordinator. No other value is accepted. |
agents | array | Yes | 1 to 20 roster entries. |
A roster entry takes any of three forms:
| Form | Meaning |
|---|---|
"agt_01H8..." | A bare agent ID. The version resolves to that agent's latest at the time you write the roster. |
{ "type": "agent", "id": "agt_01H8...", "version": 3 } | An explicit reference. Omit version to resolve to latest. |
{ "type": "self" } | The coordinator may delegate to a copy of itself. At most one self entry. |
Delegates are always existing registry agents. There is no way to declare a delegate's prompt or model inline - create the agent first, then reference it.
Create a coordinator the same way you create any agent:
$RESEARCHER_ID / researcherId and $WRITER_ID / writerId are the IDs of two agents you created earlier - delegates must already exist in the registry.
coordinator=$(ork agent create \
--name "research-coordinator" \
--model claude-sonnet-4-6 \
--system "You plan research and delegate sections to your workers." \
--multiagent-type coordinator \
--multiagent-agent "$RESEARCHER_ID" \
--multiagent-agent "$WRITER_ID@2" \
-o json)
COORDINATOR_ID=$(jq -r '.id' <<< "$coordinator")The CLI's --multiagent-agent accepts <agent-id>, <agent-id>@<version>, or type=...,id=...,version=..., and repeats once per delegate.
Versions are pinned, and stay pinned
The registry resolves every roster entry to a concrete {type, id, version} when you create or update the coordinator, and stores that in the coordinator's own version snapshot. Omitting version means "latest as of now", not "always latest".
Publishing a new version of a delegate does not update coordinators that reference it. A coordinator pinned to worker v1 keeps calling v1 after you ship v2. To move it, update the coordinator - re-sending multiagent re-resolves every entry that omits version. Updating a coordinator without sending multiagent copies the existing pinned roster forward unchanged.
One level of delegation
A delegate may not itself be a coordinator. The registry rejects a roster whose referenced agent has multiagent set, so delegation is one level deep - a coordinator calls workers, and workers do the work.
Every rejection returns 400:
| Condition | Message |
|---|---|
| Not a coordinator object with an agents array | multiagent must be a coordinator object with an agents array |
| Fewer than 1 or more than 20 entries | multiagent.agents must contain 1-20 entries |
More than one self entry | multiagent.agents may contain at most one self entry |
An entry that is not an ID, {type:"agent"}, or {type:"self"} | multiagent.agents entries must be agent ids, {type:"agent", id, version?}, or {type:"self"} |
| A version that is not a positive integer | multiagent agent reference version must be a positive integer |
| Referenced agent missing or archived | multiagent referenced agent <id> not found |
| Referenced version missing | multiagent referenced agent <id> version <n> not found |
| Referenced agent is itself a coordinator | multiagent referenced agents must not themselves have multiagent set |
| The same agent listed twice | multiagent.agents must reference distinct agents |
What a delegate receives
Each delegate runs with its own model, system prompt, skills, and built-in tool allowlist, and shares the coordinator's sandbox, filesystem, and vault credentials. Its conversation history is isolated: a delegate does not see the coordinator's context or another delegate's.
Skills are resolved for the whole roster when the session starts. Every delegate's bundles are unpacked into the shared sandbox, and each delegate's own skills are listed in the system prompt it runs on - so a delegate sees the skills declared on its agent record, and no others.
A delegate's MCP servers are not carried over. Its mcp_servers and mcp_toolset entries stay on its agent record: a session's MCP servers come from the coordinator, so a delegate whose job depends on a server it declares itself will not have it. Declare that server on the coordinator instead, or do that work in the coordinator.
Reading delegate work
Each delegate gets a session thread - an event stream of its own, addressed by a sth_ ID. The main session stream carries the coordinator's own work plus a condensed view of each delegation:
| Event | Meaning |
|---|---|
agent.thread_message_sent | The coordinator sent a message to a delegate. Carries to_session_thread_id. |
agent.thread_message_received | A delegate replied. Carries from_session_thread_id. |
Those two are what a coordinator session actually emits. The thread lifecycle events are a different matter:
| Event | Meaning |
|---|---|
session.thread_created | Declared, but never emitted. A thread appears as a row when an event carries its subpath; no event announces it. |
session.thread_status_running / _idle / _rescheduled | Declared, but never emitted. |
session.thread_status_terminated | Emitted only when you archive a thread. orca-beta retains stop_reason: "archived"; the default response omits it. Not a failure signal. |
Do not wait on a thread lifecycle event to learn that a delegate started or finished - poll the
thread list, or watch for agent.thread_message_received.
To read a delegate's full conversation rather than the summary, stream its thread directly. See Session threads for listing and streaming them.
A session runs at most 25 active child threads concurrently. The primary thread does not count, and archiving a child thread frees its slot. Exceeding that returns 409.
Threads exist only on the default claude_agent_sdk harness. Under claude_code, delegation still works but produces no threads at all - no session.thread_created, no agent.thread_message_sent or _received, and nothing under the threads endpoints. Delegate output is folded into the main stream as ordinary agent.message events, so you can see the work but cannot attribute it.
What to delegate
Delegation costs a context switch and a separate model call, so it pays off when the sub-task is genuinely independent:
- Parallel research across sources. Each delegate reads a different corpus and reports back, and none of them needs the others' context.
- A specialist with a different model or prompt. A cheap fast model for extraction, a stronger one for synthesis.
- Work that would blow the coordinator's context. A delegate reads the large input and returns a summary.
It does not pay off for a linear sequence of steps the coordinator could do itself, or for work where every delegate needs most of the coordinator's context anyway - you pay to rebuild that context in each thread.
Permissions
The roster is part of the agent resource, and threads are part of the session. Treat every Workspace API key as full access to its Workspace's resources, and separate access with Workspaces. See Control registry access.