Module 12 — Multi-Agent Coordination¶
Time: 10–14 days · Depends on: 11 Single agents · Next: Production
Learning objectives¶
By the end of this module you will be able to:
- Decide when multi-agent is wrong (default remains one agent + tools)
- Choose among sequential, hierarchical (manager–worker), and peer topologies
- Define message contracts and role charters with structured payloads
- Handle disagreement, handoffs, and shared memory without clobbering
- Measure multi-agent quality and cost against a single-agent baseline
Why this matters (CS engineer)¶
Hackathon energy: “CEO, engineer, designer, critic” agents write a README. Each persona re-reads the whole repo context. Critic and writer debate for six uncapped rounds. Final doc is worse than a single agent with read_file + one pass. Cost: ~10×. Latency: painful. The team shipped a microservice mesh for a 200-line CRUD app — theater, not topology. The fix was not more personas; it was one agent + tools, then maybe a capped writer→critic contract if metrics demand it.
Multi-agent systems are distributed systems with nondeterministic workers. Every extra agent adds:
- Latency (serial) or coordination overhead (parallel)
- Tokens (each role re-reads context)
- Failure modes (dropped handoffs, conflicting writes, critique loops)
The industry demo pattern (“team of 5 personas”) often loses to one competent agent with good tools on cost and reliability. Use multiple roles only when specialization has a crisp interface and a metric that proves the split helps.
Think: microservices. You do not split a 200-line CRUD app into 12 services for “architecture.” You split when independent deployability, scaling, or ownership boundaries demand it.
Mental model¶
flowchart TB
subgraph single [Default]
U1[User goal] --> A1[One agent + tools]
A1 --> OUT1[Result]
end
subgraph multi [Only when justified]
U2[User goal] --> M[Manager / router]
M --> W1[Worker A]
M --> W2[Worker B]
W1 --> MER[Merge / judge]
W2 --> MER
MER --> OUT2[Result]
end
Charter per role: goal, inputs schema, outputs schema, tools allowed, max steps, success metric.
No charter → no second agent.
Intuition lock
Sticky picture: Multi-agent is microservices for cognition. Default to one agent. Message contracts are APIs between roles — schemas, versions, validation — not free-form personality chat.
Kill this idea: “More agents / personas = smarter system.” → Replace with: Add a role only with a written charter, structured I/O, budgets, and a measured win over single-agent (or a hard separation constraint).
Core tutorial¶
1. When multi-agent helps — and when it hurts¶
| Helps | Hurts |
|---|---|
| Clear role boundaries (research vs write vs test) | Tiny tasks (coordination > work) |
| Parallelizable subtasks with mergeable outputs | Shared mutable state without a single writer |
| Independent critique with a capped revision loop | No success metric vs single-agent |
| Different tools/permissions per role | “Personas” that share one prompt soup |
| Human handoff at role boundaries | Unbounded debate between peers |
Default: one agent + tools (Module 11) until a second role has:
- A written charter
- A structured I/O schema
- A hypothesis for quality or latency improvement
- A plan to measure against single-agent
Think about it
Question: Product asks for “a team of agents: CEO, engineer, designer, critic” to write a README. Is this multi-agent? Should you build it?
Reveal a strong answer
It is multi-*persona* prompting, often implemented as one sequential chain or even one prompt with roleplay — not a meaningful distributed design. For a README, a single agent with repo tools usually wins. If you want quality, use a **capped** writer→critic loop with structured critique (`issues[]`, `severity`) and max 1–2 revisions — two roles, clear contract — not four theatrical titles.2. Topologies¶
Sequential: A → B → C → answer
Hierarchical: Manager → delegates → workers → merge
Peer graph: Agents message on channels / shared store
| Topology | Strength | Weakness |
|---|---|---|
| Sequential pipeline | Simple, easy to log | Latency sums; early errors poison later stages |
| Manager–worker | Clear ownership; parallel workers | Manager can be a bottleneck / single point of bad plans |
| Peer / blackboards | Flexible collaboration | Hard to debug; deadlock and conflict risk |
| Router + specialists | Good when intent classes are stable | Needs solid classification upfront |
Frameworks to study (concepts first): LangGraph, CrewAI, AutoGen/AG2, provider agent SDKs. Adopt a framework when the topology is clear — not to discover the topology. Measurable trade-offs (HITL, durability, lock-in, $/step) are Module 26; failure modes and sandboxes are 20–21.
Explainer
Pick topology like you pick RPC vs pub/sub — from data flow, not from blog aesthetics. Sequential = pipeline stages with typed handoffs. Manager–worker = fan-out when subtasks are independent. Peer graphs = last resort when you accept debugging pain. If you cannot draw the graph and name stop conditions before coding, you are not ready for a multi-agent framework.
3. Message contracts¶
Natural language only at the edges (user in, final prose out). Internally, prefer structured messages:
from dataclasses import dataclass, field
from typing import Any
import time
@dataclass
class Message:
sender: str
recipient: str # or "broadcast"
type: str # task | result | critique | question | abort
payload: dict[str, Any]
ts: float = field(default_factory=time.time)
correlation_id: str = ""
Example payloads:
# task
{"role": "researcher", "instruction": "Find 3 sources on X", "budget_steps": 4}
# result
{"facts": [{"text": "...", "source_id": "doc:9"}], "open_questions": []}
# critique
{"issues": [{"severity": "high", "msg": "Missing citation for claim 2"}], "accept": False}
Version your schemas. A worker that returns free-form essays into a manager that expects facts[] will fail silently until you validate.
Explainer
A message contract is an API between agents. You would not let microservice A POST arbitrary HTML into service B’s database. Do not let agent A dump poetry into agent B’s context and call it architecture. Validate payloads; reject with a structured error message type.
4. Role charters (write them down)¶
Template:
Role: researcher
Goal: gather evidence for the manager’s question
Inputs: {question: str, constraints: str[]}
Outputs: {facts: {text, source_id}[], open_questions: str[]}
Tools: search, fetch_url
Max steps: 5
Must not: invent sources; write final user-facing prose
| Role | Typical responsibility |
|---|---|
| Router / manager | Split goal, assign, merge, enforce budgets |
| Researcher | Retrieve and extract facts |
| Writer | Produce user-facing artifact from facts |
| Critic / judge | Score against rubric; request revision once |
| Executor | Code / ticket actions with tight tool allowlist |
5. Manager–worker sketch¶
class Manager:
def __init__(self, workers: dict[str, callable], llm, max_tasks: int = 6):
self.workers = workers
self.llm = llm
self.max_tasks = max_tasks
def run(self, goal: str) -> str:
plan_raw = self.llm(
f"Split into at most {self.max_tasks} tasks as JSON list of "
f'{{"role": str, "instruction": str}}. Goal: {goal}'
)
tasks = parse_tasks(plan_raw)[: self.max_tasks]
results = []
for t in tasks:
worker = self.workers.get(t["role"])
if not worker:
results.append({"role": t["role"], "error": "unknown role"})
continue
results.append({"role": t["role"], "output": worker(t["instruction"])})
return self.llm(
f"Synthesize final answer for: {goal}\n"
f"Worker results (JSON):\n{results}\n"
"Cite worker facts; flag conflicts; do not invent."
)
Production upgrades:
- Parallelize independent workers with a thread/async pool
- Per-worker max_steps and $ budget
- Structured result validation before merge
- Manager may not invent tools — only workers with allowlists
6. Sequential pipeline example: research → write → critique¶
researcher(goal) → facts
writer(facts) → draft
critic(draft, facts) → accept | issues
if issues and rounds < 2: writer(draft, issues) → draft
else: return draft or escalate
def pipeline(goal: str, research, write, critique, max_rounds: int = 2) -> str:
facts = research(goal)
draft = write(goal, facts)
for _ in range(max_rounds):
verdict = critique(draft, facts) # {"accept": bool, "issues": [...]}
if verdict.get("accept"):
return draft
draft = write(goal, facts, issues=verdict.get("issues") or [])
return draft # or escalate to human
Cap critique rounds. Unbounded critic–writer loops are a sophisticated way to light money on fire.
7. Consensus, conflict, and judges¶
When workers disagree:
| Strategy | Use when |
|---|---|
| Critic with one revision | Writing quality, checklist compliance |
| Judge rubric / pairwise rank | Choosing among candidate answers |
| Union + flag conflicts | Fact gathering (don’t silently overwrite) |
| Human gate | High impact: legal, prod deploys, payments |
| Majority vote | Independent samples of same task (costly) |
def merge_facts(blobs: list[list[dict]]) -> dict:
seen = {}
conflicts = []
for fact in (f for blob in blobs for f in blob):
key = fact["text"].strip().lower()
if key in seen and seen[key]["source_id"] != fact["source_id"]:
conflicts.append((seen[key], fact))
else:
seen[key] = fact
return {"facts": list(seen.values()), "conflicts": conflicts}
Never “last writer wins” on a shared answer document without an explicit merge policy.
8. Shared memory¶
| Store | Use | Danger |
|---|---|---|
| Per-agent scratchpad | Local reasoning | Not visible to others (often good) |
| Shared task board | Status of subtasks | Needs ownership fields |
| Vector store | Long research notes | Stale / duplicated notes |
| Single-writer artifact | Final doc / PR | Avoid multi-writer clobber |
| Message log | Audit / replay | Growth — compact summaries |
Rules:
- One owner for the final artifact
- Append-only message logs when possible
- Summarize shared boards; do not paste every scratchpad into every agent
Think about it
Question: Researcher and writer both “own” a shared Markdown file and rewrite it each turn. What’s the distributed-systems bug, and what’s the multi-agent fix?
Reveal a strong answer
**Multi-writer clobber** / lost update: last agent wins, intermediate facts vanish, no merge policy. Fix: **single writer** for the artifact (usually writer); researcher emits structured `facts[]` messages only; optional merge function with conflict flags; append-only message log for audit. Same rule as microservices: one service owns the table.9. Handoffs and human-in-the-loop¶
Handoff checklist:
- Structured payload validated
- Correlation id preserved
- Budget remaining passed downstream
- Explicit
accept/reject/escalatestates
Human gates for:
- Irreversible tools (delete, charge, page on-call)
- Persistent critic–worker deadlock
- Low confidence on high-severity domains
10. Measure vs single-agent baseline¶
If you cannot show improvement, the multi-agent system is costume jewelry.
On a fixed suite of N ≥ 10 tasks:
| Metric | Single agent | Multi-agent |
|---|---|---|
| Task success rate | ||
| Median steps / LLM calls | ||
| Cost per success | ||
| p95 latency | ||
| Human edits required |
Promote multi-agent only if success improves enough to justify $ and latency — or if organizational constraints (permissions, compliance) require separation of roles.
def compare(baseline_fn, multi_fn, tasks: list) -> dict:
def run(fn):
ok = cost = 0
for t in tasks:
r = fn(t) # returns {success: bool, cost_usd: float}
ok += int(r["success"])
cost += r["cost_usd"]
return {"success_rate": ok / len(tasks), "cost": cost}
return {"single": run(baseline_fn), "multi": run(multi_fn)}
Think about it
Question: Multi-agent success_rate is +4% vs single-agent, but cost_per_success is 3× and p95 latency 4×. Ship it?
Reveal a strong answer
Usually no — not as the default path. A 4% quality bump rarely justifies 3× unit cost unless the task is extremely high value (e.g. rare legal review) and you route *only* those cases through the multi-agent graph. Prefer: single agent default, multi-agent escalate on hard cases, or fix the single agent (better tools/RAG) first. Always recompute cost_per_success, not vanity success alone.11. Failure modes unique to multi-agent¶
| Symptom | Cause | Fix |
|---|---|---|
| Circular critique | No max rounds | Cap revisions; judge accept threshold |
| Manager hallucination of worker output | Soft merge prompt | Structured results only; validate |
| Duplicate work | No task board status | Idempotent task ids; mark in progress |
| Cost explosion | Fan-out × deep workers | max_tasks, per-worker budgets |
| Lost handoff | Free-text channels | Message schema + dead-letter |
| Worse than single agent | Overhead, conflict | Revert; narrow to 2-role pipeline |
12. Mapping to frameworks (study refs)¶
| Concept here | LangGraph-ish | CrewAI-ish |
|---|---|---|
| Topology | Graph nodes + edges | Crew + process |
| State | Shared state object | Memory / context |
| Worker | Node + tools | Agent + tools |
| Manager | Router node / supervisor | Hierarchical process |
| Stop | Conditional edges + recursion limit | max_iter |
Learn the concepts in this module; use frameworks as implementations, not as substitutes for charters and metrics.
Failure modes (summary table)¶
| Symptom | Likely root cause | Direction |
|---|---|---|
| Beautiful demo, prod chaos | No contracts / budgets | Schemas + hard stops |
| Agents rewrite each other | Multi-writer artifact | Single owner + merge |
| Endless debate | Peer topology without judge | Critic cap or human |
| Latency too high | Sequential overkill | Parallel workers or single agent |
| No idea if it helps | No baseline | A/B on fixed suite |
Lab¶
- Implement researcher + writer + critic with max 2 critique rounds.
- Use a
Message(or dict) log for every handoff; print/save it. - Define JSON schemas for researcher output and critic verdict; reject invalid payloads.
- On 10 tasks, compare quality and cost vs a single-agent baseline (same tools, Module 11 loop).
- Write a short memo: keep multi-agent, narrow it, or revert — with numbers.
Stretch: manager–worker with two parallel researchers and a conflict-flagging merge.
Quizzes¶
Quiz · +25 XP
What is the best default architecture before multi-agent?
Quiz · +25 XP
Why prefer structured payloads between agents over free-form prose?
Quiz · +25 XP
You should keep a multi-agent design when:
Open source materials¶
| Resource | Use it for |
|---|---|
| huggingface/agents-course | Multi-agent and LangGraph exposure |
| LangGraph | Graph topologies, durable execution ideas |
| CrewAI | Role/process framing (study critically) |
| AutoGen / AG2 | Multi-agent conversation patterns |
| humanlayer/12-factor-agents | Control flow, human gates, ownership |
Module 11 src/agents.py |
Worker implementation building block |
Checkpoint¶
- Each agent has a written charter and I/O schema
- There is a max round / budget on coordination
- Handoffs are structured and logged
- You measured multi-agent vs single-agent on a fixed suite
- You know when you would not use multi-agent
Mark complete when you have a capped multi-role pipeline with message logs and a numeric comparison to a single-agent baseline.
Exercise¶
- Catalog: EX-12 — Multi-agent vs single
- Prove: A capped multi-role pipeline has numbers (success, cost) against a single
Agent. - Test:
pytest tests/test_agents.py -v