Hirestack
Hirestack
Chapter 13

AI Agents · Multi-agent orchestration

📖 3 min read · 6 sections · May 2026

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

PatternStructureUse when
Supervisor / workerOne planner agent delegates to specialised workers, collects resultsComplex tasks with distinct sub-skills (research + write + review)
Parallel fan-outMap a list across N identical agents; gather + mergeMap-reduce style (summarise 100 PDFs, score 50 candidates)
Generator + criticOne agent produces; second agent reviews and proposes edits; loop till acceptableQuality-critical outputs (code, content, structured data)
Debate / consensusMultiple agents argue positions; aggregator synthesisesReducing 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

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

War story: team replaced a single GPT-4o call ("extract these 12 fields from invoice text") with a 4-agent crew (parser / validator / enricher / formatter). Latency went 1.2s → 9s. Cost up 5×. Accuracy: identical. The agents added zero capability the single call lacked — only ceremony. They reverted.

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.

Q: Supervisor-worker keeps looping forever. How do you stop it?
Three layers: (1) hard iteration cap on the supervisor's outer loop (typically 10-15); (2) a "no-progress" detector — if the state hasn't materially changed in 3 iterations, force exit; (3) the supervisor's prompt should include "If after 3 tries you cannot make progress, return what you have and explain why." Without all three, agents loop until the budget runs out.
Cross-Q: How do you detect "no progress"? A: hash the relevant state subset; compare to previous hashes. If you've seen this exact state before in the last N iterations, you're cycling. Force-break.

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.