AI Agents · Multi-agent orchestration
Why this matters
Single-agent ReAct loops handle 80% of agentic work. The remaining 20% — tasks needing specialised skills, parallel execution, or self-criticism — is where multi-agent setups earn their complexity. Done right, they unlock work no single agent does well; done wrong, they're slow, expensive, and brittle.
Four patterns that actually work
| Pattern | Structure | Use when |
|---|---|---|
| Supervisor / worker | One planner agent delegates to specialised workers, collects results | Complex tasks with distinct sub-skills (research + write + review) |
| Parallel fan-out | Map a list across N identical agents; gather + merge | Map-reduce style (summarise 100 PDFs, score 50 candidates) |
| Generator + critic | One agent produces; second agent reviews and proposes edits; loop till acceptable | Quality-critical outputs (code, content, structured data) |
| Debate / consensus | Multiple agents argue positions; aggregator synthesises | Reducing single-model bias; research-stage; rarely production-ready |
flowchart TB
U[User task] --> S[Supervisor]
S -->|plan| R[Researcher agent]
S -->|plan| W[Writer agent]
S -->|plan| C[Critic agent]
R --> M[Merge results]
W --> M
C --> M
M --> S
S -->|done| O[Final output]
LangGraph: agents as state machines
LangGraph models agents as a directed graph: nodes are functions (call LLM, call tool, decide next step), edges are transitions, state is a typed dict that flows through. The big win over ad-hoc ReAct loops: checkpointing (resume from any step), human-in-the-loop (pause for approval), cyclic flows (loop until critic approves).
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
messages: list
plan: str | None
drafts: list[str]
approved: bool
g = StateGraph(AgentState)
g.add_node("plan", plan_node)
g.add_node("draft", draft_node)
g.add_node("critique", critique_node)
g.set_entry_point("plan")
g.add_edge("plan", "draft")
g.add_conditional_edges("critique",
lambda s: "draft" if not s["approved"] else END)
g.add_edge("draft", "critique")
app = g.compile(checkpointer=mem_checkpointer)
Communication: shared state vs message passing
- Shared state (LangGraph default): all agents read/write the same state dict. Simple, fast, but no encapsulation — workers see everything.
- Message passing (AutoGen, CrewAI): agents send structured messages. Cleaner encapsulation, but adds serialisation overhead.
Pick shared-state for trust-aligned single-team agents; message-passing when you're stitching together agents that came from different teams or vendors.
When NOT to use multi-agent
Rule of thumb: multi-agent helps when sub-tasks need genuinely different prompts or tools. If one prompt with examples does the job, one agent is right.
Takeaway
Multi-agent is a tool for when sub-tasks have genuinely different roles. The complexity tax is real — choose the simplest pattern that gets the job done, cap iterations brutally, and instrument every state transition so you can see why a run took 47 seconds when it should have taken 5.