Concepts · 06

Multi-agent choreography

Agents collaborate by leaving traces in the world, not by calling each other. A workflow item gets created, a row gets updated, a run finishes, a thread gets a reply — and the platform routes those facts to whatever automation was waiting for them. There is no agent-to-agent API. There is a shared key.

The thesis: agents share keys, not calls

The naive way to make two agents work together is to give one of them a tool that calls the other. The naive way scales badly. Synchronous chains of agents inherit each other's permissions, hide their state inside one process, are nearly impossible to replay months later, and have no natural place to put a timeout, an escalation, or a human review.

Ergon refuses that shape. Instead, two agents that need to coordinate write into the same modeled world — a workflow item, a worksheet row, a conversation, a channel thread, an agent run — and the engine routes the next step using a resume key derived from that shared object.

One agent finishes. An event is emitted. An automation that was suspended on the matching key wakes up. The next agent runs. Each link in the chain has its own audit row, its own grants, its own snapshot. No one ever held a reference to anyone else.

Two multi-agent patterns

Ergon supports two complementary patterns for multi-agent work. Both use primitives that already exist for other reasons. Pick the pattern that matches the social shape of the work, not the technical shape:

Agents in the same room

Conversations as substrate

A human (or another agent) adds two agents to one conversation. They exchange messages, see each other's tool calls, and the human can step in at any time. This is the right pattern when the work is dialogue-shaped: drafting, reviewing, debating, briefing.

Read multi-agent collaboration →

Agents that never meet

Choreography via events

Agent A creates work. The platform routes it to Agent B (or a human, or a schedule, or a worksheet write). When B finishes, the platform routes the result back to whatever the original automation said to do next. Right pattern when the work is task-shaped: triage, assignment, fulfilment, callback.

Read on ↓

They compose

The patterns are additive — most production flows use both. A conversation between a human and Agent A may produce a workflow item assigned to Agent B (choreography), and Agent B's output may be posted back into the original conversation as a message (substrate). The audit log shows every hop in either pattern.

Five resume-key namespaces

Choreography uses the five frozen resume-key namespaces the engine already derives from every event in the platform. Five namespaces, ten realistic combinations, one engine.

channel_thread

One inbound/outbound thread on a single channel medium

Resume when the customer replies on the same email / WhatsApp / Teams thread.

conversation

One Conversations-service conversation

Resume when any participant — human or agent — posts the next message in this conversation.

workflow_item

One workflow item, across its entire lifecycle

Resume when the item is created, updated, or transitioned (often by a human or another agent acting on assignment).

agent_run

One specific agent invocation

Resume when that exact agent run completes or fails.

worksheet_row

One row in one worksheet

Resume when the row is updated or deleted by anyone (a person editing in the UI, an automation, an integration).

Why the set is closed

These five cover every cross-party correlation we have ever needed in production. New namespaces are deliberately rare and require coordinated engine and validator changes. The discipline of a small set is what lets the Studio show the right dropdown for every wait, the validator catch typos before save, and a year-old automation still resume the way it was authored.

Anatomy of a choreography

Every choreography graph reduces to the same five nodes. Once you have seen one, you have seen the shape. The catalog action types and the namespace change; the topology does not.

trigger      ──▶ action_emit ──▶ wait ─[resumed]─▶ action_callback ──▶ end
                                  └─[timed_out]─▶ action_escalation
  • trigger — whatever started this run. A customer email, a schedule fire, an agent run completion from somewhere else, a worksheet update.
  • action_emit — the action that causes the side-effect the next party will respond to. Typically agents.invoke_agent, workflows.items.create, worksheets.update_row, or a channel send.
  • wait — suspends keyed on the resume namespace that fits the side-effect. The pairing is direct: an invoke pairs with agent_run, an item create pairs with workflow_item, and so on. The Studio's “send and wait” preset pre-fills it.
  • action_callback — runs when the wait resumes. Posts back into the conversation, transitions the item, writes a row, or invokes the original agent with the new context. Closes the loop.
  • action_escalation — runs on timed_out. Every wait in a choreography graph should have one. A choreography that silently disappears is the worst failure mode of the pattern.

Worked example — triage hands off to a specialist

A user is talking with Agent A, a triage agent, in a conversation. Agent A decides the request needs a specialist team. The team has a workflow whose items are picked up by Agent B. The user should never have to chase anyone.

  1. Agent A finishes its run. Its structured output declares a handoff: which workflow, which phase, the summary. The platform emits agents.run.completed.
  2. Automation 1 (“Triage handoff”) fires.Triggered on Agent A's completion. It calls workflows.items.create with the conversation id stored in a field. It then suspends at a wait keyed on workflow_item:<new_item_id> with a 24-hour timeout and an escalation edge.
  3. Automation 2 (“Specialist pickup”) fires independently. It is owned by the specialist team, watches that workflow, and triggers on workflows.items.created. It calls agents.invoke_agent against Agent B and tells B to read the source conversation for context. It then suspends keyed on agent_run:<b_run_id>.
  4. Agent B does the work. When done, it transitions the item to a “ready-for-handoff” phase with a summary note.
  5. Automation 1 resumes. The transition event matches its waiting key. The callback action posts a message into the original conversation summarizing what Agent B produced.
  6. The user, still in the conversation, sees the answer. They never knew about the workflow item, the second agent, or the two automations.

Two automations, two agent runs, one workflow item

Notice that the two automations never reference each other. The two agents never reference each other. The only thing they share is a key — workflow_item:<id> — and the platform does the routing. Adding a third agent to the chain is a third automation, not a code change to the first two.

What the audit reconstructs

The choreography produced five durable rows: Agent A's run, Automation 1's run, Automation 2's run, Agent B's run, and one conversation message. Each row has its own immutable snapshot. The audit log can be walked in either direction.

  • Forward from the conversation: query the conversation's message events, pivot to the agent runs that wrote them, pivot to the items those runs touched.
  • Backward from the workflow item: query the item's lifecycle events, pivot to the automation runs that fired on each event, pivot to the agent runs and conversations they invoked.

Both walks return the same five rows. That is the property you cannot get if Agent A had simply called Agent B directly. It is also what makes this safe to ship to regulated industries: the question “why did this happen?” always has a deterministic answer.

Guardrails

The pattern is powerful enough to need explicit constraints next to it. Each one is a failure mode that would happen without the corresponding guard.

Always set a timeout and an escalation edge

A choreography wait that never resumes is invisible. Every wait must have a sensible timeout_seconds and a timed_out edge that posts something visible — back to the conversation, into a 'needs attention' phase, or to an on-call channel.

Respect the depth limit

Every automation declares a max_trigger_depth (default 3, max 10). A choreography of A → B → C → A is exactly the cascade the depth counter exists to bound. Raise it deliberately and minimally for graphs that need it. Never raise to 10 to be safe; that defeats the guard.

Use idempotency keys on side-effect actions

Both agents.invoke_agent and workflows.items.create accept Idempotency-Key. The engine sets it automatically for catalog actions that declare idempotent. Custom HTTP actions calling third parties are the author's responsibility.

Keep the invoked agent's grants narrow

When a graph invokes Agent B, grant only the tools B needs for the job. Future platform modes may narrow by tool action type, but IAM grants are the authorization boundary today.

Do not rely on aspirational memory fields

agents.invoke_agent is headless and creates no conversation notes by itself. generate_memory and conversation_privacy are not implemented action fields. If the graph separately writes a conversation, configure privacy and participants through Conversations.

Pin the callback to the originating conversation

The most common bug in choreography graphs is losing track of the conversation that started the chain. Carry the conversation id forward as a workflow item field (or worksheet column) so the callback action can read it from the resume event without navigating three hops back.

When choreography is the wrong answer

Three cases where the pattern is overkill or misaligned:

  • The agents are talking in real time with a human watching. That is the conversations-substrate pattern. Add both agents to one conversation; let messages flow. See multi-agent collaboration.
  • One agent needs another agent's output synchronously inside its tool loop. Use agents.invoke_agent as a tool inside Agent A's run. A waits for B; B's output comes back as a tool result; A continues. No graph involved. The authorization model is the same — B still runs under B's grants — but the coordination is inside the agent runtime.
  • The receiver is a passive cache, not an actor. Use a regular event subscriber on the receiving service. Choreography is overkill for “agent A wrote a record; now we need a search index update.”

Choreography earns its complexity when the parties are genuinely different and the work is genuinely asynchronous. When that fits, it is the only pattern that gives you the audit, the safety, and the operational legibility.

Related