Split work across specialists only when isolation creates real value.
Diagnose whether a task has genuine substructure that justifies agent boundaries, using a concrete content-pipeline scenario (planner → researcher → writer → reviewer) as the running example throughout this lesson. You leave with a sharp criterion: real value vs. accidental complexity.
A decision framework for diagnosing whether a task has genuine substructure that justifies splitting it across multiple agents.
Why this matters: Prevents the most common multi-agent mistake — adding agent boundaries before proving a single agent can't handle the task — saving latency, cost, and debugging pain.
You've wired a planner → researcher → writer → reviewer pipeline and it's slower, buggier, and harder to debug than the single agent it replaced. The question isn't whether multi-agent systems are powerful — it's whether this task has the structure that earns the cost.
Every you draw adds : serialization, routing logic, error propagation across hops, and state that can diverge. That overhead is only justified when the task has genuine — parallelism, tool isolation, or context that overflows a single agent's working memory.
The default failure mode is : five agents where one loop suffices, multiplying latency and bugs without adding capability.
Genuine shows exactly three structural signals.
Only these justify drawing a boundary.
If none apply, the split is architectural theater.
It looks modular but adds latency and failure surfaces without unlocking capability.
A team ships a planner → researcher → writer → reviewer pipeline for a weekly newsletter.
Each stage is a separate agent. Latency triples.
Errors in the researcher's output silently propagate to the writer.
The reviewer cannot ask the researcher a follow-up question without routing back through the planner.
Now consider the same task as a single agent with four tools: search(), outline(), draft(), critique().
The agent calls them in whatever order the task demands.
It can loop back without a hand-off protocol.
It carries full context across all steps.
The pipeline wins only when research queries are genuinely parallel.
Or when the draft stage's context would overflow if it carried all raw research.
Without those conditions, the single-agent version is faster, cheaper, and easier to debug.
Each point is a task type. Click a query to find the nearest tasks — tasks that cluster near 'Split justified' share real substructure; those near 'Keep single' don't.
Before drawing any , run the single-agent version and observe where it actually fails — not where you predict it might. A boundary is justified only when you can point to a concrete, reproducible failure that the split fixes.
If the struggle isn't demonstrable, the split is premature. The and the you must now enforce between agents are costs you're paying for a problem you haven't proven exists.
Run the demonstrable-struggle test against the four-stage content pipeline.
The split earns its cost only partially.
The pipeline that survives this test has at most two justified boundaries.
Knowing which boundaries survive is the prerequisite for the next question.
How do you carve each agent's role so it stays clean under load?
That's what the next module answers: specialization axes, tool ownership, and output contracts.
These are the three ways the split decision goes wrong.
Each has a concrete symptom you'll actually see.
The non-obvious trap: role inflation is invisible in demos because toy tasks complete fast. It surfaces only under load or when a stage fails mid-run.
When an LLM proposes a multi-agent architecture, treat it as a first draft.
It needs structural justification — not a design decision.
Map the four specialization axes — tool ownership, context scope, output contract, and delegation permissions — onto the content-pipeline agents (researcher owns search + retrieval; writer owns generation; reviewer owns critique). You practice drawing agent interfaces that don't leak responsibilities.
Defines the four axes — tool ownership, context scope, output contract, and delegation permissions — that make an agent role unambiguous, using the content pipeline as the working example.
Why this matters: Clean role boundaries are the prerequisite for every topology choice in the modules ahead; a leaking boundary makes orchestration unpredictable regardless of which topology you pick.
Decision this forces: For each agent: what tools does it own exclusively, what is its output contract, and does any responsibility overlap with a neighbor?
exists — subtasks that differ in skill, tool access, or latency profile. Now the question shifts: once you've decided to split, how do you carve each role so its boundary stays clean under pressure?
Every agent role is fully specified by four axes: (exclusive APIs/retrievers), context scope (shared state slice), (exact schema), and (sub-agent spawning).
Axes 1 and 3 are load-bearing: shared tools or overlapping schemas leak boundaries. Axes 2 and 4 control blast radius. Narrow context limits state corruption. Restricted delegation prevents runaway spawning.
The content pipeline — researcher → writer → reviewer — tests the four axes. Each agent tempts reaching into neighbors' domains.
Each point is a capability. Points that cluster near multiple agents signal shared ownership — the primary leak zone. Click a query to see which capabilities are dangerously close to two roles.
Responsibility leakage accumulates through small, well-intentioned shortcuts.
# STAGE 1 — leaking design: spot what's wrong before reading on researcher = Agent( tools=[search, retrieve, llm_generate], # ← leak output_schema={"sources": list, "summary": str} # ← leak ) writer = Agent( tools=[llm_generate, retrieve], # ← leak output_schema={"draft": str, "summary": str} # ← overlap )
Agent(tools=[...])output_schema={...}llm_generate in researcher.toolsThis design violates all three load-bearing axes simultaneously — a realistic pattern when agents are assembled incrementally without an explicit interface contract.
The fix is in Stage 2: strip each agent to its exclusive tool set and disjoint output schema before wiring them together.
Three violations: (1) Tool ownership — both researcher and writer own 'retrieve'; two agents hit the same store with divergent queries, breaking provenance. (2) researcher owns 'llm_generate' — it can synthesize prose, blurring the boundary with writer. (3) Both agents emit a 'summary' key — overlapping output contracts; the orchestrator or downstream consumer silently drops one, causing state divergence.
researcher = Agent( tools=[search, retrieve], output_schema={"sources": List[SourceItem]} ) writer = Agent( tools=[llm_generate], reads_from=["sources", "style_spec"], output_schema={"draft": str, "word_count": int, "sources_cited": List[str]} ) reviewer = Agent( tools=[rubric_eval, fact_check], reads_from=["draft", "query", "sources"], output_schema={"verdict": Literal["pass","revise"], "issues": List[Issue]}, delegation=["writer", "researcher"] # TODO: constrain to one at a time )
reads_from=[...]Literal["pass","revise"]delegation=["writer","researcher"]# TODO: constrain to one at a timeEach agent now owns a disjoint tool set and emits a non-overlapping output schema — the two load-bearing axes are clean.
The TODO on the reviewer's delegation is the crux: granting delegation without a concurrency constraint is the most common path to an undefined termination condition in production pipelines.
Add: max_concurrent_delegations=1 (or enforce via the orchestrator's routing logic). Without it, the reviewer can trigger writer and researcher in parallel, producing two in-flight state mutations — the orchestrator can no longer determine a single convergence point, so the termination condition becomes undefined. Changed lines: the delegation field gains the constraint; everything else is unchanged.
Compare the four canonical topologies against the content-pipeline's dependency graph: sequential for strict ordering, parallel fan-out for independent sub-tasks, hierarchical supervisor-worker for dynamic routing, and swarm/blackboard for emergent task pickup. You map each topology's latency profile and coordination cost.
Compares the four canonical multi-agent topologies — sequential, parallel fan-out, hierarchical, and swarm — against a task's dependency graph and latency budget.
Why this matters: Picking the wrong topology is the most common source of avoidable latency and coordination cost in a multi-agent build; this module gives you the decision framework to get it right.
Decision this forces: Which topology matches the task's actual dependency graph — and does the coordination overhead of the chosen shape fit within the latency budget?
The right topology reads the task's dependency graph: which sub-tasks must see prior output, and which are independent.
A chains agents in fixed order. Each hop adds with zero parallelism. It is the only safe shape when every stage consumes the previous stage's output.
A dispatches independent sub-tasks simultaneously. Wall-clock time collapses to the slowest worker. It demands a merge step and raises proportional to branch count.
A adds a routing layer that inspects state and decides which worker runs next. It is valuable when the path is data-dependent, expensive when the path is always the same.
A removes central control. Agents poll a shared workspace and claim tasks matching their capability. This enables emergent parallelism at the cost of harder design and risk.
The content pipeline — planner → researcher → writer → reviewer — has one strict chain and one fan-out opportunity.
The planner → researcher link is a hard dependency. The researcher needs the planner's topic decomposition before searching. Sequential is the only valid shape here.
If the planner emits three independent section briefs, three researcher instances can run in parallel. This collapses research latency from 3 × T to roughly 1 × T, plus merge overhead.
The writer → reviewer link is sequential again. The reviewer must see the draft. A adds value only if routing is data-dependent — e.g., route to fact-checker when writer's confidence score is below threshold.
Answer: every researcher → writer hand-off now pays an extra LLM call for a routing decision that is always the same. The supervisor earns its cost only when routing becomes genuinely data-dependent — e.g., low-confidence research triggers a re-search loop before the writer sees it.
| Option | Latency profile | Routing flexibility | Merge complexity | When to choose | Cost | Complexity |
|---|---|---|---|---|---|---|
| Sequential | Additive — total = sum of all hop latencies. | None — order is fixed at design time. | None — each agent hands off to exactly one successor. | Every stage strictly depends on the previous output — e.g., planner → researcher → writer → reviewer with no skippable steps. | Lowest coordination overhead; highest wall-clock time (hops are serial). | Lowest — no merge logic, no routing decisions. |
| Parallel Fan-out | Max of branches — major win when branches are balanced. | Fixed fan-out; branches determined at scatter time. | High — conflicts, deduplication, and synthesis required. | Sub-tasks are provably independent — e.g., researching three separate topic sections simultaneously before a single writer merges them. | Higher coordination overhead than sequential; wall-clock time = slowest branch, not sum. | Medium — requires a scatter step and a merge/synthesis step. |
| Hierarchical Supervisor-Worker | Additive + supervisor overhead per routing hop. | Highest — can branch, loop, or skip based on live state. | Medium — supervisor controls merge timing explicitly. | The next agent to invoke depends on intermediate results — e.g., a supervisor that routes to a fact-checker only when the writer flags uncertainty. | Each routing decision adds a supervisor LLM call; overhead scales with decision frequency. | High — supervisor must encode routing logic and inspect shared state each cycle. |
| Swarm / Blackboard | Near-optimal parallelism when tasks are balanced. | Emergent — agents self-select tasks; no central router. | Very high — provenance, ordering, and conflict resolution all manual. | Task volume is high and unpredictable, sub-tasks are homogeneous, and no single agent needs the full picture — e.g., a pool of reviewer agents claiming article sections from a queue. | Highest coordination overhead; risk of state divergence without careful locking. | Highest — requires a shared workspace, claim/lock protocol, and explicit termination condition. |
Drag to see how wall-clock latency changes as you fan out independent researcher agents. Each branch takes T=10 s. Sequential total = N × 10 s. Parallel total ≈ 10 s + merge overhead (est. 2 s). Coordination overhead grows with N.
A supervisor that re-evaluates routing on every cycle adds one full LLM call per hop. On a deterministic path this is pure waste. Wall-clock time grows linearly with pipeline depth while throughput stays flat.
Parallel branches that share an implicit dependency produce . Example: two researcher agents both write to the same section of the scratchpad. The merge step receives conflicting writes and output is non-deterministic. The writer receives contradictory facts with no trail to resolve them.
Without an explicit : swarm agents re-claim completed tasks or loop indefinitely. The result is runaway token spend and delayed output. This failure is hardest to catch in testing because it only manifests under concurrent load.
# Stage 1 — naive sequential (baseline to beat) def run_pipeline_sequential(briefs: list[str]) -> list[str]: results = [] for brief in briefs: # each brief blocks on the previous results.append(researcher(brief)) return writer(results) # writer waits for ALL research # Stage 2 — parallel fan-out with explicit merge import concurrent.futures def run_pipeline_fanout(briefs: list[str]) -> str: with concurrent.futures.ThreadPoolExecutor() as pool: futures = {pool.submit(researcher, b): b for b in briefs} results = [f.result() for f in futures] # blocks on slowest return writer(merge(results)) # explicit merge before writer
ThreadPoolExecutor()pool.submit(researcher, b)f.result()merge(results)Stage 1 shows the sequential baseline: total latency = len(briefs) × researcher_latency. Stage 2 fans out researcher calls across a thread pool, collapsing wall-clock time to the slowest single call plus merge overhead.
The critical non-obvious point: merge(results) must deduplicate and resolve conflicts before the writer sees the data — omitting it means the writer receives raw, potentially contradictory research sets.
f.result() re-raises the exception at the point of collection, crashing the entire fan-out — the writer receives nothing. The fix is to wrap each f.result() in a try/except and pass a structured error token into merge(), so the writer can handle partial research gracefully rather than failing silently or crashing.
Choosing a topology answers the shape question — who runs when. It does not answer the substrate question: what flows between agents, how hand-offs are encoded, and how the system knows it has converged.
Every topology above assumes a object and a . The design of that substrate determines whether coordination overhead stays bounded or compounds across hops.
The next module traces the full message-flow of the content pipeline. It covers the shared state object structure (task + intermediate results + scratchpad), how the hand-off protocol encodes routing decisions, and what a clean termination condition looks like in practice.
Trace the full message-flow of the content-pipeline: shared state object (task + intermediate results + scratchpad), hand-off protocol (supervisor routing vs. agent-emitted route), and the termination condition that prevents infinite ping-pong. You also revisit the threshold decision from Module 1 — state complexity is often the hidden cost that tips the balance.
Designs the shared state schema, hand-off protocol, and termination condition that let multiple agents coordinate without leaking context or looping forever.
Why this matters: Every multi-agent system you build lives or dies on these three decisions — a leaky schema, a mis-placed routing authority, or a missing termination condition each produce silent, expensive failures that are hard to debug in production.
Decision this forces: Who holds routing authority — the supervisor or the emitting agent — and what termination condition guarantees convergence?
Module 3 recommended the topology. The pipeline's dependency graph isn't fixed. The supervisor must inspect intermediate results and decide whether to re-route to the researcher or advance to the writer.
This topology choice raises a critical question: what does the supervisor inspect? How does it hand control off? What stops infinite loops?
The object is the single source of truth. Every agent reads from and writes to it. It carries the original task, each worker's results, and a scratchpad for reasoning.
Most teams make their first expensive mistake here: they dump the full conversation history into state. Every agent receives unneeded context. Token bills compound with each hop.
The right discipline is field-level scoping. Each agent gets read access only to fields its depends on. Write access is limited to its own result field.
State complexity is the hidden cost that tips the Module 1 isolation threshold. A schema requiring every agent to carry full upstream history signals over-decomposition.
Each point is a state field in the content-pipeline. Fields close together share a natural owner. Click a query to see which fields that agent legitimately needs — fields far from the query are likely leaking irrelevant history.
The answers one question: who decides where control goes next?
route_to field into state. The orchestrator dispatches blindly. Use this when the agent has the only needed information — e.g., the reviewer knows if its critique is blocking or advisory.The failure trap: using agent-emitted routing when the decision depends on state the agent never saw. The route is based on a partial view. The supervisor's global perspective is silently bypassed.
| Option | Who holds the deciding information | Coupling to global state | Debuggability | When to choose | Cost | Complexity |
|---|---|---|---|---|---|---|
| Supervisor-driven routing | Supervisor has full state view; correct choice when no single agent has enough context | Supervisor is explicitly coupled; workers stay decoupled from each other | All routing decisions are in one place — easy to trace and override | When the routing decision requires comparing outputs across multiple agents — e.g., weighing researcher confidence against reviewer critique before deciding to re-research. | One extra LLM call per hop through the supervisor | Higher — supervisor prompt must encode all routing logic |
| Agent-emitted routing | Only correct when the emitting agent's local state is sufficient; breaks silently otherwise | Agent must know valid route targets — creates implicit coupling to topology | Routing logic scattered across agents; harder to audit or change topology | When the finishing agent has sole, complete information for the routing decision — e.g., the reviewer knows its critique severity without needing cross-agent context. | Saves one LLM call per hop; risk of silent mis-routing if agent's view is partial | Lower — no supervisor call on the hot path |
A is the predicate the orchestrator evaluates after each hop. Without one, a reviewer that always finds critique will ping-pong with the writer indefinitely.
Three patterns cover most cases: a done flag written by any agent (simple but gameable), a max-iteration cap on state (blunt but safe), and a convergence predicate checking if the last revision delta fell below threshold (precise but requires a diff metric).
The non-obvious failure: combining a done flag with agent-emitted routing. The same agent decides to stop and decides where to go next. This conflicted authority can mask a stuck loop.
An agent appends its full reasoning trace to the shared scratchpad instead of a compact result. Every downstream agent's context window grows. Token cost compounds. You hit on the writer's call — observable as a truncated draft with no error raised.
The reviewer emits route_to: 'writer' based on its local critique. The supervisor would have routed back to the researcher because source confidence was below threshold. The supervisor's check never runs. The writer produces a well-structured draft from weak sources.
No max-iteration cap. The convergence predicate always returns false because the diff metric compares raw strings rather than semantic content. The pipeline runs until API rate-limits kill it. Observable as a runaway cost spike with no final output.
The researcher writes search results without metadata (source URL, retrieval timestamp). The writer cites confidently. The reviewer has no way to flag stale or low-credibility sources. The error surfaces only in production.
# Content-pipeline shared state — field-scoped, termination-aware state = { "task": "Write a 600-word post on carbon capture.", "research": None, # written by: researcher "draft": None, # written by: writer "critique": None, # written by: reviewer "route_to": None, # written by: supervisor (or reviewer if emitted) "revision_count": 0, "done": False, # TODO: add the convergence predicate field and the hard cap }
state = { ... }"route_to": None"revision_count": 0"done": FalseThis schema enforces field-level write ownership — each agent touches exactly one result field, and the supervisor owns route_to and done.
The TODO is the crux of this module: add a max_iterations cap (hard backstop) and a convergence_threshold field (semantic diff cutoff) — then implement the orchestrator loop that checks both before dispatching the next agent.
Add max_iterations: 5 and prev_draft: None to state. In the orchestrator loop, after each worker returns: (1) increment revision_count; (2) if revision_count >= max_iterations, set done = True and break — this is the hard backstop; (3) else, compute semantic similarity between draft and prev_draft; if similarity > threshold, set done = True; (4) update prev_draft = draft before the next dispatch.
Changed lines vs. the stub: max_iterations and prev_draft are new fields; the orchestrator gains a pre-dispatch guard that checks both conditions. The crux is that the cap fires unconditionally — the convergence predicate alone is not enough because it can silently return False if the diff metric is broken.
Quantify the four cost axes of agent isolation — added latency per hop, token cost of repeated context, coordination overhead from routing logic, and context loss at boundaries — against the content-pipeline's measured performance. You apply a completion problem: given a latency budget and a task graph, decide whether to merge two agents or keep them split.
Quantifies the four cost axes of agent isolation — latency per hop, repeated-context tokens, coordination overhead, and context loss — and teaches when merging two agents is cheaper than keeping them split.
Why this matters: Every topology decision in a multi-agent build has a measurable price; this module gives you the framework to calculate it and the judgment to act on it.
Decision this forces: Given measured latency and cost per hop, does the specialization value of each boundary exceed its coordination overhead — or should two agents be merged?
Module 4 established that the state object carries task, intermediate results, and scratchpad. The (or an agent-emitted route) decides who acts next.
That hand-off is where every cost in this module originates. Each boundary serializes state, invokes a routing decision, and re-injects context into the next agent's prompt. Every step has a measurable price.
The question: does the specialization value of a given exceed the sum of those prices — or is a merge cheaper?
Every in the content-pipeline imposes cost on four independent axes — and they compound, not average.
Drag to see how sequential hops consume a fixed 10-second latency budget, assuming ~1.5 s/hop (LLM call + routing). Each stop shows what's left for actual work.
Context loss is the most insidious cost because it produces wrong outputs, not slow ones — and it's silent.
The researcher returns a summary without source URLs or confidence scores. The writer fabricates citations because was never in the schema. Observable symptom: the reviewer flags citations that don't exist, but only intermittently.
In a topology, two researcher agents write to the same scratchpad key concurrently. The last write wins; the other's findings vanish. is a race condition, not a logic error — no exception is raised.
When the supervisor's routing call takes longer than the worker's actual task — common for short classification or formatting steps — you've created . Merge the worker into its caller, not optimizing the routing prompt.
# Content-pipeline topology: planner → researcher → writer → reviewer # Measured per-hop costs (your profiler output): hop_latency_s = {"planner→researcher": 1.4, "researcher→writer": 1.6, "writer→reviewer": 1.2} repeated_tokens = {"planner→researcher": 800, "researcher→writer": 2100, "writer→reviewer": 600} latency_budget_s = 6.0 def should_merge(hop: str, specialization_value: float) -> bool: overhead = hop_latency_s[hop] # TODO: return True when merging is justified given the budget and value ...
hop_latency_srepeated_tokensspecialization_value: float...This snippet models the merge decision as a function over measured hop costs and a specialization-value score — the two quantities you must estimate before any topology change.
The researcher→writer boundary is the highest-cost hop in the content-pipeline; filling the TODO forces you to weigh latency slack against quality lift, which is exactly the judgment this module targets.
Return True. The slack is only 0.2 s (6.0 − 5.8), and this hop alone costs 1.6 s — it blows the budget by itself. specialization_value=0.3 is below the threshold where quality lift justifies the split. The TODO becomes: return (overhead > (latency_budget_s - sum(hop_latency_s.values()) + overhead)) or (specialization_value < 0.5). Changed lines vs. the worked example: the condition replaces '...' and uses both the budget slack and the value score — the crux of this module's decision.
The content-pipeline's researcher agent returns a findings string — a prose summary. The writer has no access to source URLs, confidence scores, or intermediate reasoning.
Redesign the schema so the carries structured provenance. Replace findings: str with findings: list[Finding] where each Finding holds text, source_url, and confidence.
This costs more tokens per hop — each Finding object is larger than a prose sentence — but it eliminates citation-hallucination and gives reviewers a ground-truth surface to check.
The tradeoff is explicit: you're trading token cost for context fidelity. If the pipeline is over budget on tokens, merge researcher and writer into one agent that never compresses its own reasoning.
| Option | Latency impact | Token cost | Context fidelity | When to choose | Cost | Complexity |
|---|---|---|---|---|---|---|
| Keep agents split | Stacks per hop; unavoidable in sequential chains. | Repeated context re-injected at each boundary. | High if schema is explicit; low if provenance is compressed. | When the boundary enforces a genuinely different tool set, output contract, or context scope — and the quality lift is measurable against the overhead. | Pays latency-per-hop + repeated context tokens + supervisor routing call on every invocation. | Higher — routing logic, state schema, and failure surface all grow. |
| Merge agents | Eliminates one hop; significant in tight latency budgets. | No repeated context; single system prompt. | Full reasoning chain stays in one context window — no compression. | When two agents share >60% of their context, the routing decision is deterministic, or the worker's task duration is shorter than the supervisor's routing call. | Pays one LLM call; no routing overhead; context is never compressed. | Lower — one prompt, one call, one output contract to maintain. |
Merging the right agents reduces latency, token spend, and context loss — but it doesn't make the remaining boundaries safe. A well-merged pipeline can still suffer in parallel branches, infinite hand-offs from a missing , or silent tool failures that corrupt the scratchpad.
The next module works through all five canonical failure modes of the content-pipeline — over-decomposition, state divergence, infinite hand-off, silent tool failure, and prompt-injection via retrieved content — and gives you a systematic debugging approach for each.
Work through the five canonical failure modes of the content-pipeline — over-decomposition, state divergence, infinite hand-off, silent tool failure, and provenance loss — and apply structured tracing (per-agent logs, state snapshots, confidence tags) to isolate each. You solo-diagnose a broken pipeline given only its state log.
A structured diagnosis framework for the five canonical failure modes in distributed multi-agent pipelines, with instrumentation patterns and a design-vs-runtime triage decision.
Why this matters: Distributed agent systems fail in predictable ways — knowing the failure signatures and how to trace them to a specific agent boundary is what separates a debuggable production system from an opaque one.
Decision this forces: Is this failure a design flaw in the topology or boundary — requiring a restructure — or a runtime bug in state management or termination logic?
The answer: the — an explicit predicate evaluated against the . It inspects task status, result fields, and iteration count to signal convergence and halt routing.
If you hesitated, that gap matters: every canonical failure mode in the content-pipeline traces back to a broken or missing version of this contract.
Every distributed-agent breakdown in the content-pipeline (planner → researcher → writer → reviewer) falls into one of five categories. Each has a distinct observable signature.
The first two are (wrong topology or boundary). The last three are (bad state schema, missing termination guard, absent error handling).
You receive this state snapshot from a parallel where researcher-A and researcher-B ran concurrently, then the writer merged their outputs:
Identify: (1) which failure mode is active now, (2) which second failure mode is imminent, and (3) what single schema change would prevent the first.
Active: — researcher-B's write clobbered researcher-A's sources. The writer's draft may cite a source no longer in state.sources, breaking silently.
Imminent: — state.done is never set. The supervisor will keep routing until it hits the hard iteration cap at 10, burning 3 more unnecessary hops.
Fix for divergence: change state.sources from a scalar list to a dict[agent_id, list[str]]. Each researcher appends under its own key. The merge step unions them explicitly rather than last-write-wins.
import time, json def agent_trace(agent_id, state_before, state_after, confidence): delta = {k: state_after[k] for k in state_after if state_after[k] != state_before.get(k)} record = { "agent": agent_id, "ts": time.time(), "delta": delta, # only changed keys "confidence": confidence, # 0.0–1.0; None = not set } print(json.dumps(record)) # ship to log aggregator
state_after[k] != state_before.get(k)"confidence": confidencejson.dumps(record)— the minimal instrumentation needed to isolate which agent introduced a divergence or dropped provenance.
Logging only the delta (not the full state) keeps records small and makes conflicting writes immediately visible: two records with the same key in their delta, different values, overlapping timestamps.
delta = {'sources': ['arxiv:2401.01', 'wiki:llm-agents']} — the full new list, not just the addition. This tells you researcher-B overwrote the key rather than appending under its own namespace, which is the exact write pattern that causes state divergence when two researchers run in parallel.
def supervisor_route(state): if state.get("done"): # explicit done signal return "__end__" if state["iterations"] >= 10: # hard safety cap log_warn("cap hit", state) return "__end__" if state.get("draft") and state.get("review_passed"): state["done"] = True # TODO: return the correct terminal route return route_to_next_agent(state)
state.get("done")state["iterations"] >= 10route_to_next_agent(state)The supervisor checks three termination conditions in priority order: an explicit done flag, a hard iteration cap, and a semantic completion predicate (draft exists and review passed).
Stop — fill in the TODO before revealing. The missing return must halt the graph cleanly when the semantic predicate fires. What should it return, and why does returning route_to_next_agent(state) here instead cause an infinite loop?
Return '__end__' — the terminal sentinel that halts graph traversal. Changed lines: replace the TODO with 'return "__end__"'. If you return route_to_next_agent instead, the next agent runs, then calls supervisor_route again; this time done=True fires at line 2 and returns __end__ — so you get exactly one extra unnecessary hop, not an infinite loop. The real infinite-loop risk is when done is never set AND the semantic predicate never fires, so the cap is the only backstop.
Click a failure mode to see which quadrant it occupies. Failures near the top-right require topology restructuring; near the bottom-left, a targeted code fix suffices.
The capstone challenge hands you a fully broken content-pipeline — state log, tool traces, and supervisor history included. Diagnose all five failure modes. Classify each as design or runtime. Propose the minimal set of changes to make it production-ready. Everything from the six modules converges there: boundary judgment, topology choice, state design, cost tradeoffs, and structured tracing.
Before reviewing the spine: reconstruct from memory the five-node dependency chain — starting from the isolation threshold — and name the one failure mode that can only be caught by auditing the termination condition rather than the state schema. What does that tell you about where to instrument first?
You are handed a broken four-agent content pipeline: planner → researcher → writer → reviewer. The system occasionally loops between writer and reviewer indefinitely; sometimes the reviewer's critique references facts the writer never received; and the parallel research fan-out occasionally produces contradictory claims in the final draft. Using only the state log provided, (1) identify which failure mode each symptom maps to, (2) determine whether each is a design flaw or a runtime bug, and (3) propose the minimal set of changes — to topology, state schema, or termination condition — that fixes all three without adding a fifth agent.
A team proposes splitting a research task into three agents: one searches the web, one summarizes results, and one writes the final report — all running sequentially. The task takes 12 seconds end-to-end. A single agent with all three tools completes the same task in 9 seconds with no quality loss. What does this outcome most directly demonstrate?
When a single agent with the same tools outperforms the split with no quality loss, it means none of the structural signals — parallelism opportunity, tool isolation need, or context overflow — were present. That is exactly what the demonstrable struggle test checks before any boundary is added. The parallel fan-out option is wrong because the sub-tasks are sequential by nature (search must precede summarize). Responsibility leakage is a boundary-design problem, not a cost problem. Token-cost overhead from state schema is a real concern but is not what the 9-vs-12-second comparison directly reveals.
You are designing a pipeline where Agent A produces a ranked list of candidates and Agent B selects the top candidate and calls an external API. You discover that both agents call the same scoring tool to validate candidates. Which problem does this describe, and what is the correct fix?
When two agents share a tool, neither has exclusive ownership, which is the definition of responsibility leakage. The fix is to assign the scoring tool to exactly one agent and expose its result through that agent's output contract so the other agent consumes a score, not a tool call. Context overflow is about context-window size, not shared tool calls. State divergence involves conflicting writes to shared state, not shared tool ownership. Adding a supervisor changes the topology but does not resolve the underlying ownership ambiguity.
A workflow has four sub-tasks: T1 must finish before T2 and T3 can start; T2 and T3 are independent of each other; T4 requires both T2 and T3 to finish. A colleague proposes a flat sequential pipeline: T1 -> T2 -> T3 -> T4. What is the most precise objection, and what topology should replace it?
T2 and T3 have no dependency on each other — only on T1 — so running them sequentially wastes exactly the wall-clock time of whichever finishes first. A parallel fan-out after T1, with a join gate before T4, matches the real dependency graph and cuts latency. The termination-condition answer conflates a different failure mode with a topology problem. A hierarchical supervisor adds routing overhead that is unnecessary here because the dependency graph is static and known. The last option misreads the graph: T4 depends on both T2 and T3, not T3 on T2.
Consider this termination logic in a two-agent loop:
while state['status'] != 'done':
state = agent_b(agent_a(state))
What specific failure does this code risk, and what is the minimal fix?
The loop has no guarantee that 'done' is ever written to state['status'], so it can cycle indefinitely — the classic convergence failure from a missing termination condition. The fix must be explicit: either the emitting agent sets the done signal when its work is complete, or a hard iteration cap is imposed. This is a state-management runtime bug, not a topology design flaw, because the two-agent loop shape may be correct — the schema simply never closes the loop.
After deploying a parallel fan-out system, you observe that the final merge agent occasionally produces contradictory outputs. Inspecting state snapshots, you find that two branch agents both wrote different values to the same 'recommendation' field at nearly the same time. How do you classify this failure, and what is the correct first response?
Two agents writing different values to the same field is the definition of a conflicting write, which is state divergence — a runtime bug in the state schema, not a flaw in the parallel topology itself. The fix is to namespace each branch agent's output (e.g., 'recommendation_agent1', 'recommendation_agent2') and give the merge agent an explicit resolution rule. Switching to sequential eliminates parallelism and is an overreaction to a schema problem. Responsibility leakage describes shared tool ownership, not shared state fields. Context overflow is about context-window size, not write conflicts.