Choreography vs. Orchestration: Multi-Agent Architecture for Enterprise AI
Centralized orchestration or decentralized choreography? A production-tested breakdown of when each pattern wins for reliability, debuggability, and failure recovery.
Choreography or orchestration for multi-agent AI? Use orchestration when you need auditable, deterministic control flow in regulated workflows, and choreography when loosely coupled agents must scale independently under high throughput. The deciding factor is not happy-path latency but how each pattern handles failure recovery and observability. Most enterprise systems end up with a hybrid: an orchestrated backbone for the critical path and choreographed leaf tasks for parallel work.
I learned this the hard way. Our first production multi-agent system looked elegant in the architecture diagram and behaved fine in staging. Then it deadlocked silently at 2am, and nobody knew until a customer noticed stale data seven hours later.
What follows is a production-tested breakdown of when each pattern wins, where the hidden costs hide, and a decision framework you can apply this week.
The Failure That Killed Our First Multi-Agent Deploy
We shipped a document-processing pipeline built as a choreographed agent chain. An intake agent published an event, an extraction agent reacted, an enrichment agent reacted to that, and a persistence agent closed the loop. Clean, decoupled, elegant. No central coordinator to bottleneck.
At 2:14am, the enrichment agent hit a rate limit on a third-party API, threw an exception, and died. It had already consumed the extraction event but never published its own. The persistence agent was waiting for an event that would never arrive. Nothing crashed loudly. No alarm fired. The system just quietly stopped making progress on one branch while the rest kept humming along, which made the dashboards look healthy.
We found out because a customer's overnight report showed data from the previous day. Debugging took four hours because there was no single place that knew "the workflow for document 88213 is stuck between step 2 and step 3." State lived nowhere and everywhere.
That incident taught me the real question every team faces the moment they move past a single agent: who owns the workflow state, and how do you recover when a step fails? The answer determines whether you pick orchestration, choreography, or a mix. And the honest thesis is this: the choice is about failure recovery and observability, not about which pattern shaves 200ms off the happy path.
Two Architectures, Two Failure Modes
Orchestration means a central coordinator holds the workflow state and explicitly tells each agent what to do next. Think of a conductor. The supervisor calls agent A, gets a result, decides based on that result to call agent B, and keeps the entire process state in one place. Frameworks like LangGraph, AWS Step Functions, and Temporal implement this model.
Choreography means there is no central brain. Agents react to events and emit new events. Each agent knows only its own inputs and outputs. The workflow is an emergent property of the event flow, not a script anyone wrote down. An event-driven mesh on Amazon SNS, EventBridge, or Kafka is the classic implementation.
Here is a concrete pair. A LangGraph supervisor is orchestration: one graph node reads state, routes to a specialist agent, collects the result, and updates shared state before deciding the next hop. An EventBridge agent mesh is choreography: the extraction agent publishes DocumentExtracted, and any agent subscribed to that event pattern wakes up and does its part, publishing its own events downstream.
Both work. They fail differently, and that difference is the whole game.
| Dimension | Orchestration | Choreography |
|---|---|---|
| Control flow | Central coordinator scripts each step | Emergent from event reactions |
| State ownership | Single source of truth in the coordinator | Distributed across agents and event log |
| Coupling | Coordinator coupled to all agents | Agents loosely coupled, decoupled deploys |
| Failure blast radius | Coordinator is a single point of failure | Failures isolated but can strand branches |
| Observability | Workflow state readable in one place | Requires distributed tracing to reconstruct |
| Scaling model | Coordinator can bottleneck | Agents scale independently |
Read the bold cells. Orchestration wins on observability and control. Choreography wins on coupling and scale. Neither wins on everything, which is why the decision is a tradeoff, not a verdict.
Where Orchestration Actually Wins
Orchestration is the right default for regulated workflows that need deterministic, auditable step sequences. If you work in finance or healthcare, a regulator does not care that your agents are elegantly decoupled. They care that you can produce an ordered log proving the KYC check ran before the funds moved. A central coordinator gives you that log for free because it owns the sequence.
Human-in-the-loop approvals are trivial with orchestration. When the workflow needs a compliance officer to sign off between step three and step four, the coordinator simply pauses, persists state, and resumes on approval. Implementing that same pause in a pure event mesh means inventing a distributed state machine that recreates orchestration badly.
Here is a supervisor routing to specialist agents with explicit state transitions:
from langgraph.graph import StateGraph, END
def supervisor(state):
if not state.get("kyc_verified"):
return "kyc_agent"
if not state.get("risk_scored"):
return "risk_agent"
if state["risk_score"] > 0.8:
return "human_review" # explicit compliance checkpoint
return "settlement_agent"
graph = StateGraph(dict)
graph.add_node("kyc_agent", run_kyc)
graph.add_node("risk_agent", run_risk)
graph.add_node("human_review", queue_for_officer)
graph.add_node("settlement_agent", run_settlement)
graph.set_entry_point("supervisor")
graph.add_conditional_edges("supervisor", supervisor)
graph.add_edge("settlement_agent", END)Every transition is explicit and inspectable. When an auditor asks "what happened to transaction 4471," you query one state object. When a step fails, the coordinator knows exactly where you are and can retry that step or compensate. This is why teams in regulated domains lean orchestrated. The observability advantage compounds during incidents, which is where cost concentrates.
Where Choreography Earns Its Complexity
Choreography earns its keep in high-throughput, loosely coupled work where agents must scale independently. If your extraction agents need 40 instances during a batch spike while enrichment needs only 3, an event mesh lets each scale on its own consumer lag. A central coordinator would either bottleneck or force you to over-provision the whole graph.
The trick that makes choreography survivable is disciplined event contracts and idempotency. Every event carries a schema version and a correlation ID, and every consumer must handle the same event twice without corrupting state. Skip idempotency and a retry storm will double-charge a customer.
def handle_document_extracted(event):
doc_id = event["correlation_id"]
# idempotency guard: skip if already enriched
if store.exists(f"enriched:{doc_id}"):
return
result = enrich(event["payload"])
store.put(f"enriched:{doc_id}", result)
publish("DocumentEnriched", {
"correlation_id": doc_id,
"schema_version": 2,
"payload": result,
})Here is the hidden cost nobody budgets for: distributed tracing stops being optional. In an orchestrated system you can debug by reading the coordinator's state. In a choreographed mesh, the only way to answer "why did document 88213 stall" is to reconstruct the event timeline across services with correlation IDs, OpenTelemetry spans, and a trace backend. If you deploy choreography without that, you are back to my 2am incident.