Cloud and BYOC for Orca Agent Engine are in Private Preview — request an invite
Docs

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" }
  ]
}
FieldTypeRequiredDescription
typestringYescoordinator. No other value is accepted.
agentsarrayYes1 to 20 roster entries.

A roster entry takes any of three forms:

FormMeaning
"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:

ConditionMessage
Not a coordinator object with an agents arraymultiagent must be a coordinator object with an agents array
Fewer than 1 or more than 20 entriesmultiagent.agents must contain 1-20 entries
More than one self entrymultiagent.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 integermultiagent agent reference version must be a positive integer
Referenced agent missing or archivedmultiagent referenced agent <id> not found
Referenced version missingmultiagent referenced agent <id> version <n> not found
Referenced agent is itself a coordinatormultiagent referenced agents must not themselves have multiagent set
The same agent listed twicemultiagent.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:

EventMeaning
agent.thread_message_sentThe coordinator sent a message to a delegate. Carries to_session_thread_id.
agent.thread_message_receivedA delegate replied. Carries from_session_thread_id.

Those two are what a coordinator session actually emits. The thread lifecycle events are a different matter:

EventMeaning
session.thread_createdDeclared, but never emitted. A thread appears as a row when an event carries its subpath; no event announces it.
session.thread_status_running / _idle / _rescheduledDeclared, but never emitted.
session.thread_status_terminatedEmitted 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.

What's next

On this page