Warmup Guide — Multi-Agent Orchestration

Zero-to-senior primer for Phase 13. We start from "what does a second agent actually buy you, and what does it cost" and end with a working orchestration model: the blackboard (shared authored state), role specialization (planner / executor / critic / router), scheduling (round-robin, priority, dynamic handoff), the critique-revise loop (the cheapest reliable quality boost), consensus and voting, and the part that separates a system from an incident — termination and stall detection. The lab builds every one of these from scratch with injected deterministic policies in place of LLMs, so the mechanism is the lesson and the run replays exactly.

Table of Contents


Chapter 1: When a Second Agent Helps — and When It Just Costs You

From zero. In Phase 12 you built an agent: an LLM in a loop that can reason, call a tool, observe the result, and loop again until it produces an answer. A multi-agent system is just several of those loops, sharing some state and taking turns. That's the whole idea. The interesting question is not how (it's plumbing — you'll build it in an afternoon), it's when.

Why a second agent ever helps. A single LLM call has one system prompt, one objective, and one context window. A second agent buys you exactly three things, and you should be able to name which one you're after before you add it:

ReasonWhat it gives youExample
Distinct rolesa different prompt + objective per role; a critic and an author should not share a promptplanner vs. executor vs. reviewer
Parallelizable subtasksindependent work fanned out and recombinedsummarize 10 documents, one agent each
Cross-checkingone agent verifies another — the reflection win (Ch. 6)a "verifier" agent that re-derives the answer

Why a second agent usually hurts. If none of those apply, a second agent is pure cost:

  • Latency — turns are sequential; two agents that chat take at least twice the round-trips.
  • Tokens / money — every agent re-reads the shared context as tokens, every turn. Coordination cost grows with the conversation, often super-linearly (Ch. 3).
  • Coordination bugs — who talks next, when do they stop, what if they disagree, what if they loop. These are new problems a single agent never had.

The senior's habit. When someone says "let's make this multi-agent," the senior asks: which of the three reasons is this? If the answer is "it'll feel smarter," that's not a reason — it's a one-agent task with extra steps. The default is one agent with good tools; multi-agent is an argument you win with the three reasons, not a reflex.

Common misconception. "More agents = more intelligence." Adding agents adds coordination, and coordination is overhead until it buys you one of the three things above. A team of five mediocre agents arguing is slower, costlier, and often worse (groupthink, error amplification — Ch. 8) than one good agent with a verifier.


Chapter 2: Coordination Architectures — Blackboard, Messages, Hierarchy, Debate

The question. Once you have several agents, how do they communicate and coordinate? There are four canonical patterns, and real frameworks are combinations of them.

1. Blackboard (shared memory). All agents read from and write to a single shared workspace. Nobody calls anybody directly; a controller picks who contributes next. This is the pattern from the 1980 Hearsay-II speech system, and it's the one this lab builds because it's the cleanest mental model: agents are decoupled through state. Add or remove a role and the others don't change.

        ┌──────────────────────────────────────────┐
        │                BLACKBOARD                 │
        │   task · plan · draft:x · review:x ...     │
        └──────────────────────────────────────────┘
            ▲        ▲        ▲          ▲
            │        │        │          │   (read / write; never call each other)
        ┌───┴──┐ ┌───┴──┐ ┌───┴──┐  ┌────┴───┐
        │plan- │ │exec-1│ │exec-2│  │ critic │
        │ ner  │ │      │ │      │  │        │
        └──────┘ └──────┘ └──────┘  └────────┘
                      ▲ controller picks who runs next ▲

2. Message-passing (actor model). Agents send each other typed messages; each agent has a mailbox and reacts. More decoupled in time (async), but you now reason about message ordering, delivery, and back-pressure. OpenAI Swarm handoffs and most "agent A calls agent B" designs are this.

3. Hierarchical (manager-worker). A manager agent decomposes a task and delegates to workers, then aggregates. This is CrewAI's hierarchical process and MetaGPT's software-company SOP (PM → architect → engineer → QA). The lab's Orchestrator (planner → executors → critic) is a hierarchy with a blackboard underneath.

4. Debate. Two or more agents argue opposing positions and a judge (or a vote) decides. Useful for hard reasoning where a single chain of thought is brittle; expensive and prone to persuasive but wrong convergence.

How to choose. Blackboard for shared, evolving state and easy role swapping. Message-passing for async, loosely-coupled services. Hierarchy when there's a natural decomposition and an owner. Debate only when cross-examination demonstrably beats a single verifier — it rarely pays for its cost.

Common misconception. "These are competing frameworks." They're patterns; LangGraph, AutoGen, and CrewAI each let you express several of them. Pick the pattern for the problem, then the framework that expresses it cleanly.


Chapter 3: The Blackboard Under the Hood

What it is. A shared key→value store with an append-only, authored log. Every write records who wrote it and a monotone version number. Agents read the latest value for a key and append their contributions. That's it — and that minimalism is the point.

Why authored + append-only. Three reasons, all of which you'll feel in production:

  1. Audit / debuggability — when the team produces garbage, the history tells you which agent wrote the bad value and when. Without authorship, multi-agent debugging is a séance.
  2. Replay & determinism — a monotone seq gives every write a total order independent of dict hashing, so the same policies replay to the same state (Ch. 5, Ch. 8).
  3. Progress detection — "has the board changed?" is the signal that powers stall detection (Ch. 8). You can't ask that question without a versioned log.
write("task", "compose a greeting", author="orchestrator")   → Entry(seq=1, ...)
write("plan", ["a","b","c"],        author="planner-1")      → Entry(seq=2, ...)
write("draft:a", "hello",           author="exec-1")         → Entry(seq=3, ...)
write("review:a", "accept",         author="critic-1")       → Entry(seq=4, ...)
                                                                 ▲ history is the replay tape

The mechanism (last-writer-wins + history). read(key) returns the latest value; the full sequence of values for a key lives in history(key). This is exactly a tiny event log with a materialized "current state" view on top — the same shape as a database write-ahead log.

The hidden cost: shared context is tokens. In a real system the blackboard is the context you feed each agent. If five agents each read a growing transcript every turn, your token bill scales with agents × turns × transcript_length — quadratic-ish in conversation length. This is why scoping matters: give each agent only the slice of the board it needs (the lab models this with _FocusedView, which hands a policy the board plus the one subtask it's working on, not everything).

Common misconception. "Shared memory is free coordination." It's free in a stdlib lab; in production it's the dominant cost and the main source of groupthink (everyone conditions on the same context, so they make the same mistake). Scope aggressively.


Chapter 4: Role Specialization — Planner, Executor, Critic, Router

The core idea. A "role" is just a label; the behaviour lives entirely in the agent's policy — a callable policy(view) -> action. In production the policy is a role-specific system prompt plus model.generate; in this lab it's an injected deterministic function. Because the scheduler and orchestrator only ever call agent.act(view), you can swap a scripted policy for a real LLM and nothing else changes. That substitutability is the whole abstraction.

The four canonical roles:

RoleJobReads / Writes
Plannerdecompose the task into subtasksreads task; writes plan
Executordo one subtask, produce a draft/resultreads the plan & dependencies; writes draft:<t>, result:<t>
Criticreview a draft, accept or request a revisionreads draft:<t>; writes review:<t>
Routerdynamically pick the next agent/specialistreads state; emits a handoff

Why split the critic out. This is the single most valuable specialization. An author and a reviewer want different prompts and different objectives — "write the best draft" vs. "find what's wrong with this draft." Asking one agent to do both in one pass is strictly worse than letting a fresh critic attack the draft (Ch. 6). The critic is the role that most reliably earns its cost.

Why the router matters. A fixed plan (planner → executors → critic) is rigid. A router reads the current state and decides who should act next — "this looks like a math question, hand off to the calculator specialist." This is the handoff pattern (OpenAI Swarm) and it's how you get dynamic, data-dependent coordination instead of a hard-coded pipeline.

Mechanism in the lab. Agent(name, role, policy) validates the role is one of ROLES and that policy is callable, then act(view) simply returns policy(view). The orchestrator interprets the returned action (a plan list, a draft string, an "accept"/"revise: ..." verdict). Mechanism and policy are cleanly separated.

Common misconception. "Roles are an LLM thing." Roles are a software-design thing — separation of concerns. The LLM is an implementation detail of one role's policy. Designing the roles and the shared protocol first, then dropping models in, is what keeps multi-agent systems debuggable.


Chapter 5: Scheduling & Turn-Taking

The question. Several agents share a board. Who acts next? This is the scheduler, and "whoever the framework happens to pick" is a bug, not a design.

Round-robin. Visit active agents in a fixed cyclic order, one per turn; everyone gets a turn before anyone gets a second. It's fair and trivially deterministic — the order is a fixed list plus a cursor. The lab skips inactive agents (an agent that's finished can bow out) while advancing the cursor across the full ring, so deactivation doesn't perturb the order.

agents:  [a, b, c]   cursor →
turn 1:   a          (cursor 0→1)
turn 2:   b          (cursor 1→2)
turn 3:   c          (cursor 2→0)
turn 4:   a ...      (b inactive? skip it: a, c, a, c, ...)

Priority. Some agents should move first — a planner before executors, a critic only after a draft exists. A priority scheduler sorts by descending priority, breaking ties by insertion order so the schedule stays fully deterministic. Priority is how you encode "the manager speaks first."

Dynamic handoff. The most flexible: the current agent (or a router) names who goes next, based on the state. This is how Swarm and tool-calling agents work — the schedule is data-dependent, not fixed. Powerful, but the hardest to make terminate (a handoff cycle is an infinite loop).

The determinism rule. Turn order must come from explicit, ordered structures — a list and a cursor, a sorted index — never from set/dict iteration. The instant your schedule depends on hash order, your multi-agent run stops being replayable, and an unreproducible distributed system is nearly impossible to debug. The lab's tests assert two fresh schedulers emit identical sequences.

Common misconception. "Let the agents figure out who talks." Free-for-all turn-taking (every agent responds to every message) is the fastest path to a token explosion and a loop. Someone — a scheduler or a manager — must own next-speaker selection.


Chapter 6: The Critique-Revise (Reflection) Loop

The single most important pattern in this phase. Generation in one pass is mediocre; generation with a critic in the loop is dramatically better for the cost. The loop:

        ┌─────────────────────────────────────────────┐
        │  executor: draft ──► critic: review          │
        │     ▲                    │                    │
        │     │   "revise: <notes>"│   "accept"        │
        │     └────────────────────┘        └──► result │
        │           (≤ max_revisions times)             │
        └─────────────────────────────────────────────┘
  1. The executor produces a draft.
  2. The critic reads the draft and returns accept or revise: <notes>.
  3. On revise, the executor reads the notes and produces a better draft.
  4. Repeat until acceptor a hard max_revisions cap is hit.

Why it works. It's the same reason code review and editing work for humans: finding flaws is a different, easier cognitive task than producing a flawless first draft. A critic with a fresh "what's wrong here?" objective catches errors the author — committed to its own draft — won't. This is Reflexion (Shinn et al., 2023) and the reviewer pattern baked into AutoGen and LangGraph. Empirically it's the best $/quality lever in agentic systems: one extra critic turn often beats swapping in a bigger model.

Why it's dangerous. The loop works because the critic can reject — which means a critic that's too picky (or simply broken) never accepts, and you have a perfect infinite loop, billing you on every iteration. This is not hypothetical; it's the most common multi-agent incident. The cap is not optional. In the lab, _critique_revise loops at most max_revisions times and returns a "capped" status the orchestrator surfaces honestly as an un-completed result — plus a stall guard (Ch. 8) for the case where the executor keeps emitting the same rejected draft.

Mechanism in the lab. Per subtask: write draft:<t>, run the critic to write review:<t>, and if the verdict starts with accept promote the draft to result:<t>. The executor's policy reads review:<t> (None on the first pass, the notes on a retry) to decide how to revise. The test test_critique_revise_improves_until_accepted proves a lazy first draft becomes a good one; test_critique_revise_caps_at_max_iterations proves a never-satisfied critic terminates.

Common misconception. "Reflection means the model critiques itself in one call." Self-critique in a single pass helps a little; the big win is a separate critic turn with its own objective and a fresh look at the draft. Separation is the mechanism.


Chapter 7: Consensus & Voting

The problem. Sometimes you run the same task through several agents (or one agent several times) and get several answers. How do you pick one — reproducibly?

Majority / plurality vote. Count the answers; return the most common. This is self-consistency (Wang et al., 2022): sample N reasoning chains, take the majority answer — it reliably beats a single sample on reasoning tasks, because independent errors don't usually agree, but the correct answer does.

$$ \text{decision} = \arg\max_{v}; \big|{i : \text{vote}_i = v}\big| $$

The tie-break is the whole engineering. Two options tied at the top — now what? If you break ties by "whatever max() over a dict returns," your decision depends on hash order and your system is non-reproducible. The lab breaks ties deterministically by earliest ballot: among options tied for the top count, return the one that appeared first in the vote list. Boring, correct, replayable.

Weighted vote. Not all voters are equal — a trusted/expert agent's ballot should count more:

$$ \text{decision} = \arg\max_{v}; \sum_{i:,\text{vote}_i = v} w_i $$

Same deterministic tie-break (earliest declared option wins). The lab rejects negative weights and a zero total weight — "no weight" is not a decision.

Debate-to-agreement. A richer form: agents don't just vote once; they argue, read each other's positions, and re-vote until they converge (or a judge calls it). More expensive; pays off only when cross-examination genuinely surfaces information a single critic misses.

Common misconception. "Voting fixes hallucination." Voting fixes independent, uncorrelated errors. If all your agents share the same model, prompt, and context, they share the same bias — they'll confidently agree on the same wrong answer. That's groupthink (Ch. 8), and majority vote makes it look more trustworthy while doing nothing to fix it. Diversity of inputs is the precondition for voting to help.


Chapter 8: Termination, Deadlock & the Failure Modes

The thesis of the phase. A single agent's worst failure is a wrong answer. A multi-agent system's worst failure is never producing an answer at all — while spending money the entire time. The defining engineering work in multi-agent is making it stop.

The four failure modes (memorize these; they're the interview):

Failure modeWhat happensThe guard
Cost explosionevery agent re-reads the growing context every turn → token bill blows uptoken/cost budget; scope each agent's context
Error propagationone agent's bad output becomes another's input, amplified downstreama critic/verifier turn; schema-validate hand-offs
Groupthinkshared model/prompt/context → everyone makes the same mistake, voting hides itdiversify inputs; an adversarial critic
Non-terminationrevise-revise-revise or a handoff cycle with no exitmax rounds · stall detection · budgets

Three layers of termination guard:

  1. Max rounds / max iterations — a hard cap on turns. Crude but bulletproof: the system will stop. Every framework has one (max_round, max_iter, recursion limit). Non-negotiable.
  2. Stall detection (no-progress) — subtler and more useful. The system might be under the round cap but making no progress — the same draft rejected and re-emitted, the board not changing. Detect "no new writes" and halt early instead of burning the rest of the budget on a loop that cannot converge.
  3. Budgets — a token or dollar ceiling, independent of turns. The real-world stop condition, because cost, not turns, is what hurts.

Stall detection, mechanically. "Did the board change?" The lab's detect_stall(history, window) looks at the last window entries and asks: were they all no-op rewrites — each writing a value equal to what was already there for that key? If every recent write is a no-op, no information was added, and the orchestrator halts with reason "stalled":

history tail (window=4):
  draft:s1 = "STUCK"   (new)      ┐
  review:s1 = "revise" (new)      │  not all no-ops yet
  draft:s1 = "STUCK"   (NO-OP)    │
  review:s1 = "revise" (NO-OP)    ┘
  draft:s1 = "STUCK"   (NO-OP)   ┐
  review:s1 = "revise" (NO-OP)   │  last 4 all no-ops → STALL → halt
  draft:s1 = "STUCK"   (NO-OP)   │
  review:s1 = "revise" (NO-OP)   ┘

This is why the blackboard's versioned, valued history (Ch. 3) exists: stall detection is impossible without it. The lab's test_orchestrator_halts_on_stall_not_infinite_loop sets a huge max_revisions so only the stall guard can stop a stuck team — and it does, in a handful of turns rather than thousands.

Determinism as a debugging guard. The quiet fourth guard: a reproducible run. If your multi-agent system behaves differently every run, you cannot debug a non-termination or a wrong answer — you can't even reproduce it. Removing every source of non-determinism (sampling, speaker selection, dict ordering) is what makes the other guards usable. The lab is deterministic end-to-end on purpose.

Common misconception. "We set max_rounds=50, so we're safe." A round cap stops the bleeding but hides the bug — you'll pay 50 rounds of tokens on a task that stalled at round 3. Stall detection is what turns "it eventually stops" into "it stops as soon as it's stuck," which is the difference between a $0.10 failure and a $5 one at scale.


Lab Walkthrough Guidance

The lab (lab-01-multi-agent-orchestrator) turns these chapters into code. Suggested order (matches the file top-to-bottom):

  1. Blackboard — Chapter 3. write assigns a monotone seq and appends an authored Entry; read is last-writer-wins; history is a copy in write order; keys() is insertion order. Keep it deterministic — no sorting of keys, no set iteration.
  2. Agent — Chapter 4. Validate the role against ROLES, validate policy is callable; act just calls the policy. The discipline here is doing nothing clever — behaviour belongs in the injected policy.
  3. RoundRobinScheduler / PriorityScheduler — Chapter 5. next_agent advances a cursor over a fixed order, skipping inactive agents; priority pre-sorts indices by (-priority, insertion). The determinism tests are the point.
  4. consensus_vote / weighted_vote — Chapter 7. Count (or sum weights), take the top, break ties by earliest appearance. Reject empty/negative/zero-total input.
  5. detect_stall — Chapter 8. Replay the log to know the value present before each write, flag no-op rewrites, and return whether the last window flags are all True.
  6. Orchestrator + _critique_revise — Chapters 4, 6, 8. Plan → per-subtask critique-revise loop → mark done. _critique_revise returns "accepted", "capped", or "stalled". The two soul tests live here: test_orchestrator_decomposes_and_completes (a scripted team produces a correct final state) and test_critique_revise_caps_at_max_iterations (a never-satisfied critic terminates at the cap).

Run red → green: pytest test_lab.py -v, then LAB_MODULE=solution pytest test_lab.py -v, then python solution.py for the worked team.

Success Criteria

  • All tests pass against your lab.py and against LAB_MODULE=solution.
  • You can state the three reasons multi-agent helps and argue the default of one agent.
  • You can explain the blackboard pattern and why authored, append-only, versioned history enables audit, replay, and stall detection.
  • You can implement a deterministic scheduler and point to every place non-determinism could sneak in (set/dict iteration, sampling, speaker selection).
  • You can explain the critique-revise loop and why it must be capped — and what a never-accepting critic does without the cap.
  • You can describe stall detection (no-op rewrites over a window) and why it beats a bare round cap.
  • You can name the four failure modes and the guard for each, and explain why determinism is itself a guard.

Interview Q&A

  • "When does multi-agent actually beat one good agent?" — Three cases: genuinely distinct roles (different prompts/objectives, esp. author vs. critic), parallelizable subtasks, and cross-checking. Otherwise it adds latency, token cost, and coordination bugs. Default to one agent with tools.
  • "What's the blackboard pattern and why use it?" — A shared, authored, append-only state store agents read/write instead of calling each other. Decoupling through state lets you add/remove roles without rewiring, and the versioned history gives you audit, replay, and progress detection.
  • "How do you stop an agent system from looping forever?" — Three layers: a hard max-rounds cap, stall detection (halt when no new progress is made, e.g. the same draft re-emitted), and a token/cost budget. A round cap alone hides the bug and pays full budget on a stalled task.
  • "Why is the critique-revise loop so effective, and what's its risk?" — Finding flaws is easier than writing a flawless draft, and a separate critic with its own objective catches the author's blind spots — best $/quality lever in agentic systems. Risk: a critic that never accepts is a perfect infinite loop, so it must be capped (and stall-guarded).
  • "How do you make a multi-agent run reproducible?" — Remove every source of non-determinism: fixed scheduler order (no set/dict iteration), seeded/zero-temperature sampling, deterministic tie-breaks in voting (earliest ballot), and a monotone version on the shared state. Without reproducibility you can't debug a stall or a wrong answer.
  • "When does voting/consensus help, and when does it hurt?" — Helps when errors are independent (self-consistency: independent wrong chains disagree, the right one agrees). Hurts when agents share model/prompt/context — they share the bias (groupthink), so they agree on the same wrong answer and the vote makes it look trustworthy. Voting needs diverse inputs.
  • "Name the multi-agent failure modes." — Cost explosion (context re-read as tokens every turn), error propagation (bad output amplified downstream), groupthink (shared bias, hidden by voting), and non-termination (revise loops / handoff cycles). Guards: budgets + scoping, critic/verifier turns, input diversity, and max-rounds + stall detection.
  • "What does a router/handoff add over a fixed pipeline?" — Data-dependent coordination: the router reads the state and picks the right specialist next, instead of a hard-coded planner → executor → critic order. More flexible, but handoff cycles are a new non-termination risk.

References

  • Erman, Hayes-Roth, Lesser, Reddy, The Hearsay-II Speech-Understanding System (1980) — the original blackboard architecture.
  • Wu et al., AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation (2023) — group chat, speaker selection, termination conditions.
  • Li et al., CAMEL: Communicative Agents for "Mind" Exploration of Large Language Models (2023) — role-playing agents and the role-prompt mechanism.
  • Hong et al., MetaGPT: Meta Programming for a Multi-Agent Collaborative Framework (2023) — SOPs and the manager-worker software-company pattern.
  • Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning (2023) — the reflection / critique-revise loop.
  • Wang et al., Self-Consistency Improves Chain-of-Thought Reasoning in Language Models (2022) — the majority-vote-over-samples result behind consensus_vote.
  • Du et al., Improving Factuality and Reasoning in Language Models through Multiagent Debate (2023) — debate-to-agreement and its limits.
  • Minsky, The Society of Mind (1986) — the intellectual ancestor of "intelligence from many simple specialized agents."
  • Framework docs to read against the miniature: CrewAI (roles, hierarchical process, max_iter), LangGraph (shared State, edges, recursion limit), OpenAI Swarm (agents + handoffs).