AI EngineeringZero to ProductionHome·About·What’s new·Contact
Project 13 · Design Chapter

Multi-Agent Research Crew

A team of specialized agents — a planner, several researchers, a critic, and a writer — collaborating on a research task the way a human team would. Where the Deep Research Agent (Project 6) is one agent looping, this is role-specialized division of labor: each agent does one job well, and an orchestrator coordinates them.

🎯 Advanced📈 trending🧑‍🔬 analysis / knowledge workmulti-agent
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need an API key for whichever vendor you follow (toggle the Claude | OpenAI tabs on the code below):Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.
Builds on CrewAI + Deep ResearchThis assembles M1 (CrewAI) / M2 (AutoGen) orchestration and the research loop of Project 6 into a crew. Read those first — this is where "one agent" becomes "a coordinated team", and where you learn when that's worth it.

What this project teaches you to design

  • Role specialization: planner, researcher(s), critic, writer — each with one job.
  • An orchestration pattern (sequential, hierarchical, or debate) and why.
  • Inter-agent communication and shared state without chaos.
  • A critic/verifier agent that catches errors before they reach the output.

The brief advanced

You've watched one agent try to hold the whole thing in its head — plan the questions, chase the sources, second-guess itself, and write the summary — and the answer came out plausible but thin, with a citation that turned out to point at nothing. That is the failure this project answers. A single agent is a soloist trying to play every instrument at once; a crew is an ensemble where each player owns one part.

Think about how a good newsroom handles a hard story. An editor breaks the question into angles, several reporters chase different sources at the same time, a fact-checker refuses to let an unverified claim through, and a writer stitches the survivors into a piece. Nobody does all four jobs, and that division is exactly why the output is stronger than one exhausted person could produce. A multi-agent research crew is that newsroom rendered as code: a planner, parallel researchers, a critic, and a writer, coordinated by an orchestrator.

The reason to specialize isn't that specialists are smarter — the same model backs every role. It's that a narrow role gets a narrow prompt, and a narrow prompt is easier to steer, easier to test, and harder to derail. A researcher told only "gather and cite evidence" won't wander off into writing conclusions; a critic told only "verify claims against their sources" won't get talked into agreeing. The common mistake is to reach for a crew because it sounds impressive — it isn't free. Every extra agent is more calls, more latency, and one more place errors can enter. Reach for it only when the task genuinely splits into distinct jobs and an independent check measurably raises quality; otherwise a single looping agent is the cheaper, more reliable choice, and the rest of this page keeps pushing you to prove the crew earns its keep.

"One agent doing everything is a jack of all trades — we want a team of specialists." Complex research benefits from division of labor: someone plans the questions, several people dig into sources in parallel, a skeptic pokes holes, and a writer synthesizes. A multi-agent crew mirrors that — specialized roles produce higher-quality, more thorough output than a single generalist agent on genuinely hard tasks.

one agent — every job at once a crew — one job each single agent jack of all trades plan research critique write thin, plausible, one dead citation orchestrator planner researcher critic writer each role narrow, steerable, testable A soloist vs an ensemble. Left: one agent tries to plan, research, critique and write all at once — the four jobs pile onto a single overloaded box. Right: the same four jobs become four specialists an orchestrator coordinates, each with a narrow prompt that's easier to steer and check.
🗺️ How to read this diagram

Read the two halves against each other across the dashed divider: the same four jobs, done one way on the left and another on the right.

  • The left red box is a single agent, and the four amber tiles beneath it — plan, research, critique, write — are all jobs it juggles itself. Every arrow points back into the one overloaded worker.
  • The right side splits those same four jobs into separate boxes: a planner, a researcher, a critic (outlined red — the independent check), and a green writer, all fanning out from one orchestrator at the top.
  • The payoff is the labels at the bottom: the soloist's output is thin and plausibly wrong; the crew's roles are each narrow enough to steer and test — which is the whole reason to specialize.

In short: the crew doesn't use smarter models — it gives each of the four jobs its own narrow role, so each is easier to steer, test, and trust.

1 · Discovery — do you actually need a crew? advanced

Here is the trap that swallows most multi-agent projects: they get built before anyone asks whether the task needed a team at all. The crew runs, produces a nice-looking report, and only weeks later does someone notice it cost five times what a single agent would have — for output that was no better. By then the architecture is load-bearing and nobody wants to rip it out.

So this section is a gate, not a formality. Before you write a line of orchestration code, you answer one question honestly: what does splitting this into roles buy me that one agent looping can't? The useful test is to imagine handing the task to a small human team. If you can point at genuinely different jobs — someone who plans, someone who digs, someone who checks, someone who writes — and especially if you can point at a job whose whole value is being independent (a fact-checker who didn't do the research and therefore isn't invested in it), then the roles are real and a crew earns its cost. If instead you're just describing one person's linear workflow, you've found a single agent wearing a costume.

The signal that matters most is the second row of the table: quality benefits from an independent critic. That independence is the entire reason a crew can beat a soloist — a separate agent reviewing claims it has no stake in catches errors the author would defend. The mistake to avoid is treating parallelism as the justification. Running three researchers at once saves wall-clock, which is nice, but it doesn't make the answer more correct; only the critic does that. Read the table below as a scorecard — the more three-star rows you can honestly tick, the more a crew is the right call, and the last row is the humbling reminder that "one agent is fine" is a perfectly good, cheaper answer.

answer honestly before writing any orchestration code the task hand it to a team? distinct roles + an independent critic? YES build the crew NO single agent cheaper (Project 6) Discovery is a gate, not a formality. The task only becomes a crew if you can point at genuinely different jobs and a job whose value is being independent — the critic. If it's really one person's linear workflow, the honest answer is a single agent.
🗺️ How to read this diagram

Follow the arrow left to right: the task walks up to a decision diamond, and which way it exits decides the whole architecture.

  • The blue box on the left is the task you're about to build. The test is the thought experiment: would you hand this to a small human team?
  • The amber diamond is the one question that matters — does the work split into distinct roles and include a job whose value is being independent (a critic who didn't do the research)?
  • The two exits are the payoff: YES drops into the green "build the crew" box; NO slides right to the plain "single agent" box, which is cheaper and simpler and often the correct answer.

In short: a crew is justified only when the diamond answers YES — real distinct roles plus an independent critic; otherwise one looping agent wins on cost.

SignalPoints to
Task splits into distinct expertises / parallel sub-tasks⭐⭐⭐ multi-agent helps
Quality benefits from an independent critic/verifier⭐⭐⭐ add a critic role
Sub-tasks can run in parallel to save wall-clock⭐⭐ parallel researchers
Task is linear and a single agent handles it fine⭐ don't — one agent is cheaper & simpler (Project 6)
Problem statement"A single agent produces shallow, sometimes-wrong research on hard questions. We want a coordinated crew — planning, parallel investigation, independent criticism, and synthesis — that produces thorough, verified, well-cited output, while keeping cost and coordination under control."

2 · Architecture advanced

The first instinct when you wire up several agents is to let them all talk to each other — a group chat where everyone chimes in. Do that and you get exactly what a leaderless meeting produces: agents restating each other, arguing in circles, and no clear moment where the work is done. The shape below exists to prevent that. It is deliberately not a mesh.

Picture an assembly line with one foreman. Work enters at the top, moves through stations in a fixed order, and comes out the bottom — and critically, there is a single point of control deciding what happens next, not a committee. The orchestrator is that foreman: it's the only box that talks to everyone, so coordination lives in one place instead of being smeared across every pairwise conversation. The researchers are interchangeable stations that can run side by side; the critic is the quality-inspection station that work must pass before it reaches the end; the writer is the final station that assembles what survived.

The load-bearing detail — the one that makes this an architecture and not just a diagram — is the direction of flow. Findings move down through the critic before they reach the writer, which means the writer physically cannot synthesize from unverified material; it only ever sees what the critic passed. That's a structural safety property, not a rule you have to remember to enforce. The common mistake here is to let the writer read the raw research "for context" — the moment you do that, an unverified claim can slip into the report and the critic becomes decorative. Keep the pipe one-directional and the guarantee holds. Everything the code on this page builds is this exact top-to-bottom sequential flow.

work flows straight down · one orchestrator, one gate, one writer user query orchestrator plans & coordinates researcher A source set 1 researcher B source set 2 researcher C source set 3 shared search tool → sources critic / verifier claim vs source unsupported claim ✕ writer → report survivors One pipe, one gate. A query enters the orchestrator, which fans it out to parallel researchers that each hit a shared search tool for sources; every cited finding must pass the critic — unsupported claims are dropped (red) — before the writer synthesizes the survivors into the final cited report. The writer never sees raw research.
🗺️ How to read this diagram

Trace one query from the top straight down: it never skips a station, and it can only move forward through the gate.

  • The user query enters the purple orchestrator, the single point of control — it plans the work and hands sub-queries out to the researcher row (the blue fan-out arrows).
  • The three researchers run side by side, each calling the amber shared search tool to pull its own source set — parallelism that saves wall-clock but, on its own, proves nothing.
  • Every finding funnels into the red-outlined critic, which checks each claim against its source; the red arrow is an unsupported claim being dropped, so it can never reach the writer.
  • Only the survivors flow right into the green writer, which emits the final cited report — that one-directional pipe is the structural safety property.

In short: query → orchestrator → parallel researchers + search → critic gate → writer; because the pipe only runs downward through the critic, unverified material physically cannot enter the report.

Orchestratorplans & coordinates Researcher Asource set 1 Researcher Bsource set 2 Researcher Csource set 3 Critic / verifierchecks claims Writer → report
🗺️ How to read this diagram

This diagram shows how the crew is wired: one coordinator at the top, a row of specialists in the middle, and the final report at the bottom. The arrows show work flowing down and results flowing back.

  • The purple box at the top is the Orchestrator — it plans the work and coordinates everyone else. It's the only agent that talks to all the others.
  • The middle row holds the specialists: Researcher A, B, and C each dig into a different set of sources (they can run in parallel), and the pink Critic / verifier box independently checks their claims.
  • The arrows fanning out from the orchestrator are it handing tasks to each specialist; the arrows curving back down toward the bottom are their results flowing to the Writer.
  • The green Writer → report box at the bottom synthesizes the verified findings into the final cited report — it receives what survived the critic, not raw research.

In short: Read it top-to-bottom: orchestrator plans, researchers gather, the critic filters, the writer synthesizes. That top-down flow is exactly the sequential pipeline the code on this page builds.

An orchestrator plans the work and coordinates specialized agents: several researchers investigate different sources (often in parallel), a critic independently verifies claims and flags weak evidence, and a writer synthesizes the verified findings into a cited report. Each agent has a narrow role and prompt — specialization is what raises quality over a single generalist.

3 · Risk & safety model advanced

The seductive lie about multi-agent systems is that more agents means more safety — more eyes, more checks, more robustness. The opposite is the honest default. Every agent you add is another thing that can be wrong, another API bill, and another turn in a conversation that might never end. A crew multiplies capability, but it multiplies failure surface right alongside it.

The worst failure is quiet, and it's worth feeling. In a chain of agents, one researcher's mistake doesn't just sit there — it gets laundered. The writer receives it, phrases it confidently, cites a source next to it, and now a fabrication looks exactly like a fact. This is error compounding: bad input becomes confident bad output, and the polish the crew adds is what makes it dangerous. A lone agent that's unsure at least sounds unsure; a crew can make a wrong answer sound peer-reviewed.

So the controls in this table aren't garnish — they're the price of admission. The critic exists specifically to break the compounding chain by refusing to pass a claim its source doesn't support. The hard step and token caps exist because two agents can happily talk past each other forever, and "forever" on a metered API is a real invoice. Clear termination conditions exist because an orchestrator that ends the round is safer than open-ended chat that hopes to converge. Read each row as a specific failure someone has actually shipped, paired with the one control that contains it. The mistake to avoid above all: shipping a crew without caps and without a verifying critic, then being surprised when it loops, hallucinates, and bills you for the privilege.

top: no critic — the error is laundered · bottom: the critic breaks the chain Researcher one wrong claim Writer states it as fact Report fabrication looks peer-reviewed Researcher one wrong claim Critic claim vs its source Writer → report verified only dropped ✕ survivors A crew launders errors unless a critic breaks the chain. Top row: with no verifier, one wrong claim rides through the writer and comes out as a confident, cited "fact." Bottom row: the critic checks each claim against its source and drops the unsupported one, so only verified findings reach the writer.
🗺️ How to read this diagram

Compare the two rows left to right. Both start with the same researcher error; the only difference is whether a critic sits in the path.

  • The top row has no critic: the red "one wrong claim" box flows straight into the amber Writer, which phrases it confidently, and out into a red Report where the fabrication now looks peer-reviewed. That is error compounding — bad input becoming polished bad output.
  • The bottom row inserts the Critic between researcher and writer. It compares each claim against its cited source; the red dashed arrow marked "dropped ✕" is the unsupported claim being rejected before it can travel any further.
  • The payoff is the green Writer → report box: because only the survivors reach it, the final report is built from verified findings, not laundered ones.

In short: the critic is the one box that turns "more agents = more ways to be confidently wrong" into "more agents = a real check." Remove it and the top row is what you ship.

RiskControl
🔴 Errors compounding across agents (bad input → confident bad output)A dedicated critic/verifier agent; every claim traced to a source; the writer only uses verified findings
🔴 Runaway cost — many agents, many calls, loopsHard step/turn caps per agent; a total token budget (E2); bounded delegation depth
🟠 Agents talking in circles / no convergenceClear termination conditions; an orchestrator that ends the round, not open-ended chat
🟠 Role confusion / duplicated workCrisp role prompts + task assignment; shared state so agents see what's done
🟠 Hallucinated citations in the final reportCritic verifies citations against retrieved sources before the writer synthesizes (Project 7)
More agents = more ways to fail and more costMulti-agent systems multiply both capability and failure surface: errors propagate, costs stack, and conversations can loop forever. The critic role and hard budgets aren't nice-to-haves — they're what makes a crew safe. If a single agent (Project 6) does the job, prefer it; only pay the multi-agent tax when specialization genuinely wins.

4 · Orchestration patterns advanced

Watch someone discover multi-agent frameworks and you can almost predict the first build: a hierarchical manager delegating to a debating swarm, because it's the most impressive thing the docs demo. It also almost never works on the first try — the manager mis-delegates, the debaters won't agree, and you spend a week debugging an orchestration you never needed. The patterns below are a menu, and the whole skill is ordering the cheapest dish that feeds you.

The four patterns are really answers to one question: how do agents hand work to each other? Sequential is a relay race — a fixed baton pass, plan then research then critique then write. Hierarchical is a manager who decides at runtime who does what, useful when you can't know the sub-tasks in advance. Parallel is several runners on separate lanes whose results merge at the finish, which saves wall-clock when sub-questions are truly independent. Debate is two agents arguing to surface an error neither would catch alone, worth its cost only when accuracy is high-stakes.

Here's the point the whole course keeps making, applied to orchestration: start sequential, and earn every step up from there. A plain plan→research→critique→write pipeline is easy to reason about, easy to test, and easy to put a leash on — and for most research tasks it's genuinely all you need. Parallelism and hierarchy are optimizations; add them only once you can measure them helping, not because the architecture looks smarter with them. The classic mistake is picking the fanciest pattern up front and inheriting all its failure modes for a task a relay race would have handled. A well-run sequential crew beats a chaotic hierarchical one almost every time. The code sketch just below shows that sequential shape — four roles handed to a Crew with a hard max_steps leash.

the same agents, wired four ways — start with the simplest that works Sequential — start here plan research critique write fixed relay — easiest to test & leash Hierarchical manager worker worker worker Parallel split research research research runs concurrently, then merges Debate / critique agent A agent B argue to surface an error · high-stakes Four ways to wire the same agents. Sequential is a fixed relay (plan→research→critique→write); hierarchical adds a manager that delegates at runtime; parallel fans researchers out and merges; debate has two agents argue to catch each other's errors. The green box marks the one to start with.
🗺️ How to read this diagram

Read the four quadrants as four answers to one question — how do agents hand work to each other? The shape of the arrows in each box is the pattern.

  • Sequential (top-left, green) is a straight chain: each stage passes its result to the next in a fixed order. It's boxed green because the course tells you to start here — it's the easiest to test and to put a leash on.
  • Hierarchical (top-right) puts a manager above interchangeable workers; the dashed arrows are it deciding at runtime who does what — useful when you can't know the sub-tasks in advance.
  • Parallel (bottom-left) splits into several researchers that run at the same time and then merge — it saves wall-clock but, on its own, doesn't make answers more correct.
  • Debate (bottom-right) has two agents argue back and forth (note the arrows going both ways) to surface an error neither would catch alone — worth its extra cost only when accuracy is high-stakes.

In short: the arrow shape tells you the cost. A straight line is cheapest and clearest; branches, loops, and back-and-forth each buy something but add failure modes — so earn them.

PatternHow it worksBest for
Sequentialplan → research → critique → write, in orderClear pipeline; most research tasks
HierarchicalA manager agent delegates to workers and integratesDynamic task decomposition
ParallelResearchers run concurrently, then mergeIndependent sub-questions; save wall-clock
Debate / critiqueAgents argue/critique to surface errorsHigh-stakes accuracy; reducing hallucination
crew.py (shape — CrewAI-style)planner   = Agent(role="Planner",   goal="break the question into sub-queries")
researcher= Agent(role="Researcher",goal="gather & cite evidence", tools=[search])
critic    = Agent(role="Critic",    goal="verify claims, flag weak evidence")
writer    = Agent(role="Writer",    goal="synthesize a cited report")

crew = Crew(agents=[planner, researcher, critic, writer],
            process="sequential", max_steps=20)   # the leash
▶ How this works

This is a sketch (not the file you build later) showing the shape of a crew in CrewAI style: you declare each agent with a role and a one-line goal, then hand the whole team to a Crew that runs them. Read it as "here are four specialists and how they're wired together".

  1. Each Agent(...) line creates one specialist. The role is its job title and the goal is its single responsibility in plain English — planner splits the question, researcher gathers evidence, critic checks it, writer synthesizes.
  2. Only the researcher gets tools=[search] — it's the one that needs to look things up. The others reason over what they're handed. Giving each agent only the tools it needs is a safety habit.
  3. Crew(agents=[...], process="sequential", ...) assembles the team and says run them in order: plan, then research, then critique, then write.
  4. max_steps=20 is the leash — a hard cap on how many turns the crew may take, so it can't loop forever and run up cost. The comment calls this out.

Try this: This is the mental model for the rest of the page. As you build agents.py and crew.py, match each real function back to one of these four roles.

Start sequential; earn complexityBegin with the simplest pattern that works — usually sequential plan→research→critique→write. Add parallelism or hierarchy only when you can measure it helping. A well-orchestrated sequential crew beats a chaotic hierarchical one almost every time.

5 · Agent / tool surface advanced

The tempting shortcut is to give every agent every tool — search, retrieval, export, the lot — so any of them can do anything. It feels flexible. It's also how a critic ends up "fixing" a claim by going and searching for support instead of judging what it was handed, and how a writer quietly publishes a report nobody approved. When every agent can do everything, no agent's role means anything.

Think of it like keys on a building's access cards. You don't hand every employee a master key; the researcher's card opens the archive, the writer's card opens the drafting room, and only a manager's card, plus a signature, opens the door marked "publish." Least privilege isn't bureaucracy — it's what lets you reason about what each agent can do wrong. The tool surface is where role specialization stops being a prompt and becomes an enforced boundary.

The pattern in the table is deliberate. The researcher is read-only: it can look things up but not change the world, so the worst it can do is retrieve badly. The critic gets no research tools at all — its job is to judge the claim against the source it was given, and handing it a search tool would let it rationalize a weak claim instead of failing it, quietly destroying the independence that made it valuable. The writer produces a draft and nothing more. And the one genuinely dangerous action — writing or publishing the report to a doc or ticket — is the single amber row, gated behind a human approval, because an autonomous system that can publish is an autonomous system that can publish something wrong. The rule to carry away: give each role the narrowest tools that let it do its job, and put a human in front of any action that touches the outside world.

each role gets the narrowest keys that let it do its job Researcher read-only search / read Critic judges the claim no tools Writer drafts only format / cite Publish world-facing human approves 🔒 gated Least privilege, drawn as an access map. The researcher gets read-only lookup, the critic gets no tools (a search tool would let it rationalize a weak claim instead of failing it), the writer only drafts, and the one world-touching action — publish — is gated behind a human.
🗺️ How to read this diagram

Scan left to right across the four roles; under each role sits the exact set of tools it is allowed, and the color tells you the blast radius.

  • The Researcher gets a read-only search / read tool — it can look things up but not change anything, so the worst it can do is retrieve badly.
  • The Critic (outlined red) deliberately gets no tools — handing it a search tool would let it hunt for support for a weak claim instead of judging what it was given, destroying the independence that made it valuable.
  • The Writer can only format and cite a draft. The amber Publish box on the far right is the single world-touching action, and its arrow runs through a human approves gate — marked 🔒 gated.

In short: give each role the narrowest tools for its job, keep the critic tool-free to stay independent, and put a human in front of anything that publishes.

RoleToolsRisk
OrchestratorTask assignment, termination control🟢 coordination
Researchersearch / retrieval (Project 9), read sources🟢 read-only
CriticVerify claim vs source; score confidence🟢 the safety gate
WriterSynthesize; format; cite🟢 produces a draft
Any write/publishExport report to a doc/ticket🟠 gated — human-approved

6 · Evaluation expert

A multi-agent crew will always feel better than a single agent — it's more elaborate, it produces more structured output, and you built it, so you want it to win. That feeling is worth nothing. The uncomfortable question this section forces is the only one that matters: measured against a plain single agent, does the crew actually produce better answers, and is "better" worth what it costs?

The evaluations split into two honest halves. The first is does the machinery work: is the report accurate and fully cited, and — the load-bearing one — does the critic actually catch errors? A critic you never tested is a critic you should assume is broken, because a critic that rubber-stamps everything is invisible until the day it lets a fabrication through. The second half is is it worth it: the uplift-vs-single-agent number and the cost-per-task number, side by side. This pairing is the whole justification for the project. A crew that beats one agent by two percent while costing five times as much has failed its evaluation no matter how sophisticated it looks.

The single most important test is the one the tip below names, and it's the one people skip because it feels like sabotaging your own system: plant a known-false claim in the researchers' findings and confirm the critic drops it. This is how you prove the crew's central safety property empirically instead of hoping. Testing the happy path — feeding it good data and seeing a good report — tells you almost nothing, because a broken critic passes the happy path too. You have to feed it a lie and watch it get caught. Build in evidence of decline: if a prompt change makes the planted-error test start passing lies, your evals just earned their keep.

does the machinery work? is it worth it? report accurate & fully cited critic catches planted errors the load-bearing test plant a lie dropped ✓ uplift vs 1 agent cost / task tokens & calls weigh side by side big uplift, modest cost → keep tiny uplift, 5× cost → drop it Two honest halves. Left proves the crew works — the report is faithful and the critic actually drops a planted lie. Right proves it's worth it — uplift over a single agent weighed against cost per task. A crew that beats one agent by 2% at 5× the cost has failed.
🗺️ How to read this diagram

Read the two columns split by the dashed line: the left is correctness, the right is economics, and both must pass.

  • The left column is "does the machinery work" — the report is accurate and fully cited, and the red-outlined box is the load-bearing test: does the critic actually catch errors? The little plant a lie → dropped ✓ chain below shows how you prove it — feed it a known-false claim and watch it get rejected.
  • The right column is "is it worth it" — the crew's uplift over a single agent set beside its cost per task, weighed together.
  • The payoff is the bottom-right verdict box: a big uplift at modest cost is a keep; a tiny uplift at several times the cost is a drop, no matter how sophisticated the crew looks.

In short: a crew passes evaluation only when it both works (critic drops the planted lie) and pays (real uplift over a single agent for the cost) — testing the happy path proves neither.

EvalMeasures
Report quality & faithfulnessAccuracy, coverage, every claim cited (LLM-as-judge + spot human, Ch 5)
Critic effectivenessDoes the critic actually catch injected/planted errors?
Uplift vs single agentProve the crew beats Project 6 on hard tasks — the whole justification
Cost & steps per taskTotal tokens/calls — is the quality worth the multi-agent tax?
Convergence rate% of runs that terminate cleanly within the caps
Test the critic with planted errorsThe critic is your main defense against compounding mistakes — so measure it directly: seed the researchers' findings with known-false claims and check the critic flags them. A crew whose critic rubber-stamps everything is just a more expensive single agent.

7 · Phased rollout expert

The failure mode for a working crew isn't that it breaks — it's that it works well enough in a demo that someone points it at the real world and lets it publish, all at once. A crew that autonomously writes reports into your docs or tickets on day one is a crew that will autonomously write a wrong report into your docs the first time the critic slips. Rollout is how you buy the trust to loosen the leash, one notch at a time.

The right way to grow it is the same ladder the whole course uses: start where a human is unambiguously in the loop, then earn each step up with evidence. Phase 1 is the crew running end-to-end but with a person reading the output before it's used — this is where you establish baseline quality and cost with the stakes low. Phase 2 is where you strengthen the critic and add parallelism, and crucially where you measure the uplift over a single agent, because that number is the permission slip for the extra machinery. Phase 3 is scaling carefully — hierarchical delegation, budgets, monitoring, a gated export — and only where the evals from §6 prove it pays.

The reason to climb slowly rather than jump is that each phase de-risks the next: you don't add autonomy to a crew whose quality you haven't measured, and you don't add parallelism you can't show helps. Read the red rung as the non-negotiable floor beneath all of this — never run without step and token caps, and never publish a report whose citations the critic hasn't verified. Those two are the lines that don't move regardless of how much trust the earlier phases earned. The common mistake is skipping straight to Phase 3 because the crew "seemed fine," which is precisely the confidence that ships the first laundered hallucination.

climb one rung at a time — each phase earns the next with evidence Floor · never move these lines always step + token caps · never publish unverified citations Phase 1 · human reads output sequential crew — establish quality + cost Phase 2 · critic + parallelize verify citations — measure uplift vs 1 agent Phase 3 · scale carefully hierarchy, budgets, gated export — earn it more autonomy → Trust is earned one rung at a time. Each phase adds autonomy only after the phase below it has produced evidence — and the whole ladder rests on a red floor of caps and verified citations that never moves, no matter how much trust the earlier phases earned.
🗺️ How to read this diagram

Read it bottom to top, the way you'd climb a ladder: the lowest bar is the ground you never leave, and each rung above it is a step up in autonomy.

  • The red bar at the bottom is the non-negotiable floor: always run with step and token caps, and never publish citations the critic hasn't verified. It sits under everything because it holds regardless of which phase you're in.
  • Phase 1 (green) is the safe starting rung — a sequential crew whose output a human reads, so you learn baseline quality and cost with the stakes low.
  • Phase 2 (blue) steps up: strengthen the critic and parallelize, and crucially measure the uplift over a single agent — that number is your permission slip for the extra machinery.
  • Phase 3 (amber, highest) is scaling — hierarchy, budgets, a gated export — reached only when the evals prove it pays. The blue arrows show that each rung is entered from the one below, never skipped.

In short: autonomy goes up as you climb, but the red floor never drops — you earn each rung with evidence and never trade away the caps and the verified-citation rule.

Phase 1 · Sequential crew, human reads output — planner→researcher→critic→writer; a person reviews the report. Establish quality + cost. (M1)
Phase 2 · Strengthen the critic + parallelize — verify citations, run researchers concurrently, measure uplift vs single agent. (Project 6 baseline)
Phase 3 · Scale carefully — hierarchical delegation, budgets, monitoring, gated export. Only where evals prove it. (Ch 6 + L5)
Never — run without step/token caps, or publish a report whose citations the critic hasn't verified.

Skills & course map expert

SkillLearn it in
Multi-agent orchestration (CrewAI)M1
Conversational multi-agent (AutoGen)M2
The single-agent research loopProject 6
Verified retrieval & citationsProject 7 · Ch 3
State, cycles & iteration capsL5
Quality/cost evals, budgetsCh 5 · E2
🛠️ Hands-on build — everything below is on this pageThe rest of this page is the complete, self-contained build: set up from an empty folder, paste in every file, run it (with a mock, so no API key is needed), and pass the tests. Follow it top to bottom — no other page required.

What you need before you startPython 3.10+. The crew orchestration is ordinary control flow, so agents are injected as functions and the whole thing — including the critic catching a planted false claim — is tested offline. No key needed until the optional final step.

By the end you will have

  • Role-specialized agents (planner, researcher, critic, writer), each one job.
  • A sequential orchestrator with a hard step cap.
  • A critic that verifies claims and drops unsupported ones before the writer.
  • Six tests, including a planted-false-claim test proving the critic works.

How to use this page expert

Steps in order. terminal = run it; file = create it with the exact contents.

Step 1 · Folder + venv expert

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Step 1 — run in your terminal
terminalmkdir -p research-crew/tests
cd research-crew
python3 -m venv .venv
source .venv/bin/activate     # Windows: .venv\Scripts\Activate.ps1
pip install pytest "anthropic>=0.40"
pip freeze > requirements.txt
(.venv) ... Successfully installed anthropic-0.69.0 pytest-8.3.4
▶ How this works

Before any agent code, you build an isolated workspace. A virtual environment (venv) is a private copy of Python just for this project, so the packages you install here can't clash with anything else on your machine. These are terminal commands — type them one line at a time.

  1. mkdir -p research-crew/tests makes the project folder and a tests sub-folder in one go. cd research-crew steps into it so every later command runs in the right place.
  2. python3 -m venv .venv creates the private Python in a hidden .venv folder. source .venv/bin/activate switches your terminal to use it — you'll see (.venv) appear at the start of the prompt. (Windows uses the .ps1 line instead.)
  3. pip install pytest "anthropic>=0.40" downloads the two libraries you need: pytest to run the tests and anthropic for the optional real model call later.
  4. pip freeze > requirements.txt writes the exact versions you installed into a file, so anyone (including future-you) can recreate the same setup.

What the output means: The Successfully installed anthropic-0.69.0 pytest-8.3.4 line confirms both libraries landed in your venv. The (.venv) prefix means the environment is active.

Try this: If you close the terminal and come back, re-run source .venv/bin/activate first — the venv isn't active until you activate it in each new terminal.

Step 2 · The agents (roles as functions) expert

Create agents.py. Each role is a function that takes a task and a model_call (injectable), so tests can script exact behaviour. The default mock_call makes it run with no key.

Step 2 — create this file

research-crew/agents.py

agents.py"""Role-specialized agents. Each is one job; model_call is injectable."""
from dataclasses import dataclass


@dataclass
class Finding:
    claim: str
    source: str        # the text the claim is supposed to come from


def mock_call(role: str, task):
    """Deterministic stand-in for an LLM, keyed by role. No API key."""
    if role == "planner":
        return ["What is our churn?", "What caused it?"]
    if role == "researcher":
        # returns a supported finding for each sub-query
        return [Finding(claim=f"Answer to: {task}",
                        source=f"Answer to: {task} (source text)")]
    if role == "writer":
        return "REPORT: " + "; ".join(f.claim for f in task)
    raise ValueError(f"unknown role {role}")


def plan(question, call=mock_call):
    return call("planner", question)


def research(subquery, call=mock_call):
    return call("researcher", subquery)


def write(verified_findings, call=mock_call):
    return call("writer", verified_findings)
▶ How this works

This file defines the crew's roles as plain functions and a fake LLM so the whole thing runs offline with no API key. The trick that makes it testable: every role takes a call argument, so a test can swap in its own scripted "model".

  1. @dataclass class Finding defines a tiny record with two fields: a claim (something the researcher asserts) and the source text it supposedly came from. Keeping the source attached is what lets the critic check claims later.
  2. mock_call(role, task) is a pretend model: given a role string it returns fixed, predictable output. Planner returns two sub-questions; researcher returns a Finding whose claim does appear in its source (so it's honest); writer joins the claims into a report string.
  3. plan, research, and write are thin wrappers. Each just calls call("<role>", ...). The call=mock_call default means they use the fake model unless you pass a real one.
  4. Because call is a parameter, a test can inject a fake researcher that lies — a claim whose words are NOT in its source — to prove the critic catches it (that happens in Step 4).

What the output means: Nothing prints yet — this file only defines the roles and the mock. The next file wires them into an orchestrator that actually runs them.

Try this: Read mock_call's researcher branch: the claim string is a substring of the source string. That's deliberate — an honest finding. Later you'll see a dishonest one where it isn't.

Specialization is the pointEach role does one narrow job — that's what makes a crew beat a single generalist agent. Passing call in means a test can inject a "researcher" that lies, so you can prove the critic catches it (Step 4).

Step 3 · The critic + the orchestrator expert

Create crew.py. The critic verifies each finding (claim must be supported by its source); the orchestrator runs plan → research → critique → write under a step cap, and the writer only ever sees verified findings.

Step 3 — create this file

research-crew/crew.py

crew.py"""Sequential crew with a step cap and a verifying critic."""
from agents import plan, research, write, mock_call, Finding

MAX_STEPS = 12


def critique(findings) -> list:
    """Keep only findings whose claim is supported by its source text.
    This is the crew's defence against compounding errors."""
    verified = []
    for f in findings:
        # 'supported' = the claim's words appear in its cited source
        if f.claim.lower() in f.source.lower():
            verified.append(f)
    return verified


def run_crew(question, call=mock_call):
    steps = 0
    subqueries = plan(question, call); steps += 1
    findings = []
    for q in subqueries:
        if steps >= MAX_STEPS:
            break                        # the leash across all agents
        findings += research(q, call); steps += 1
    verified = critique(findings); steps += 1   # the gate
    report = write(verified, call); steps += 1
    return {"report": report, "verified": verified,
            "dropped": len(findings) - len(verified), "steps": steps}


if __name__ == "__main__":
    r = run_crew("Why did churn rise?")
    print("REPORT:", r["report"])
    print("verified:", len(r["verified"]), "dropped:", r["dropped"],
          "steps:", r["steps"])
▶ How this works

This is the heart of the project: the critic that verifies findings and the orchestrator that runs the four roles in order under a step cap. The single most important rule here — the writer only ever sees findings the critic approved.

  1. MAX_STEPS = 12 is the hard cap on total agent turns across the whole run — the leash that prevents runaway cost or infinite loops.
  2. critique(findings) is the safety gate. For each finding it checks whether the claim's text actually appears inside its source text (if f.claim.lower() in f.source.lower()). Supported claims are kept; unsupported ones are silently dropped. This is a simple stand-in for a real verifier.
  3. run_crew(question, call) runs the pipeline in order: plan turns the question into sub-queries, the for loop calls research on each (counting a step each time and breaking if it hits MAX_STEPS), then critique filters, then write gets only the verified list.
  4. It returns a dict summarising the run: the report, the verified findings, how many were dropped, and the total steps. The if __name__ == "__main__" block runs a demo when you execute the file directly.

What the output means: Running it prints the report plus a one-line summary — e.g. all findings verified, 0 dropped, 5 steps — proving the pipeline ran end to end within the cap.

Try this: Notice write(verified, call) is passed verified, never the raw findings. That one choice is what stops an unverified claim from ever reaching the report.

The critic is what separates a crew from an echo chamberWithout an independent verifier, one researcher's error flows into the report with extra confidence. critique() drops any claim not supported by its source — and the writer only receives verified findings. This is the crew's main safety property, so it gets a dedicated planted-error test next.

Step 4 · Run it, then test it (no key) expert

Step 4 — run it
terminalpython crew.py
REPORT: REPORT: Answer to: What is our churn?; Answer to: What caused it?
verified: 2 dropped: 0 steps: 5

Now create the tests, including one where a researcher lies (claim not in its source) — the critic must drop it.

Step 4 — create this file

research-crew/tests/test_crew.py

tests/test_crew.py"""Offline crew tests with scripted agents — no key."""
from agents import Finding
from crew import run_crew, critique, MAX_STEPS


def test_planner_produces_subqueries():
    r = run_crew("why did churn rise?")
    assert r["steps"] > 0 and r["report"].startswith("REPORT:")


def test_supported_claims_pass_critique():
    good = [Finding(claim="net 30", source="payment is net 30 days")]
    assert len(critique(good)) == 1


def test_critic_drops_planted_false_claim():
    # claim is NOT in its source -> must be dropped
    liar = [Finding(claim="revenue tripled", source="payment is net 30 days")]
    assert len(critique(liar)) == 0


def test_writer_only_sees_verified():
    mixed = [Finding(claim="net 30", source="net 30 terms"),
             Finding(claim="fake", source="unrelated text")]
    assert len(critique(mixed)) == 1


def test_step_cap_not_exceeded():
    r = run_crew("why did churn rise?")
    assert r["steps"] <= MAX_STEPS


def test_report_contains_only_verified_claims():
    # build a crew where one researcher lies via a scripted call
    def call(role, task):
        if role == "planner": return ["q1"]
        if role == "researcher":
            return [Finding(claim="true fact", source="a true fact appears here"),
                    Finding(claim="lie", source="nothing relevant")]
        if role == "writer": return "; ".join(f.claim for f in task)
    r = run_crew("q", call=call)
    assert "lie" not in r["report"] and r["dropped"] == 1
▶ How this works

Six tests that prove the crew behaves — all offline, no API key, because the roles accept an injectable call. pytest treats every function named test_* as a test; assert means "this must be true, or fail".

  1. test_planner_produces_subqueries runs the whole crew and checks it took some steps and produced a report — a basic end-to-end smoke test.
  2. test_supported_claims_pass_critique feeds the critic an honest finding (claim "net 30" is inside "payment is net 30 days") and asserts it survives. test_critic_drops_planted_false_claim is the mirror: a planted lie whose claim isn't in its source must be dropped — this is the key safety test.
  3. test_writer_only_sees_verified mixes one good and one bad finding and asserts only one passes. test_step_cap_not_exceeded checks the run stays within MAX_STEPS.
  4. test_report_contains_only_verified_claims defines its own scripted call where the researcher returns one true finding and one lie, then asserts the word "lie" never appears in the final report and exactly one claim was dropped — an end-to-end proof the critic protects the output.

What the output means: All six report PASSED and pytest prints 6 passed. The planted-lie test passing is the headline: the critic really does catch a fabricated claim.

Try this: Temporarily break the critic in crew.py (e.g. always append(f)) and re-run — the two planted-lie tests should turn red. That's your safety net doing its job.

Step 4 — run the tests
terminalpython -m pytest tests/ -v
tests/test_crew.py::test_planner_produces_subqueries PASSED
tests/test_crew.py::test_supported_claims_pass_critique PASSED
tests/test_crew.py::test_critic_drops_planted_false_claim PASSED
tests/test_crew.py::test_writer_only_sees_verified PASSED
tests/test_crew.py::test_step_cap_not_exceeded PASSED
tests/test_crew.py::test_report_contains_only_verified_claims PASSED

6 passed in 0.04s
✅ What each test proves
TestProves
planner produces sub-queriesthe crew runs end to end
supported claims passreal findings survive the critic
critic drops planted false claimthe critic actually catches errors — the key safety property
writer only sees verifiedunsupported claims can't reach the report
step cap not exceededthe leash holds — no runaway cost
report excludes the lieend-to-end: a lying researcher's claim never gets written

Step 5 · Go live with Claude or OpenAI / CrewAI (optional) expert

Replace mock_call with a real model call — the orchestration is unchanged. A minimal real caller:

The real model_call — in Claude or OpenAI

The ROLES system prompts and the orchestrator are identical for both vendors — only the one real_call differs. In Claude you call client.messages.create(system=…, messages=[…]) and read msg.content[0].text; in OpenAI the same call is client.responses.create(instructions=…, input=…) and you read resp.output_text. Toggle the tab to switch SDK — run_crew(question, call=real_call) is unchanged either way.

Step 5 — a real model_call
real_call.pyimport anthropic
client = anthropic.Anthropic()
ROLES = {
  "planner": "Break the question into 2-4 sub-queries. One per line.",
  "researcher": "Answer with facts and quote the source text you used.",
  "writer": "Write a short cited report from the findings.",
}

def real_call(role, task):
    msg = client.messages.create(model="claude-opus-4-8", max_tokens=600,
        system=ROLES[role],
        messages=[{"role":"user","content": str(task)}])
    return msg.content[0].text
# then: run_crew(question, call=real_call)  (after setting ANTHROPIC_API_KEY)
▶ How this works

This shows how to go live: swap the fake mock_call for a real one that talks to Claude or OpenAI (toggle the tabs above). Crucially, the orchestrator doesn't change at all — only the function that produces text is different.

  1. The client (anthropic.Anthropic() or OpenAI()) reads your key from the environment (ANTHROPIC_API_KEY or OPENAI_API_KEY) automatically, so the key never appears in code.
  2. ROLES is a dictionary mapping each role name to a system prompt — a one-line instruction that turns the same model into a planner, a researcher, or a writer. Specialization here is just a different prompt per role.
  3. real_call(role, task) sends one message: it picks the role prompt with ROLES[role] (as system in Claude, instructions in OpenAI), passes the task, and returns the model's text (msg.content[0].text / resp.output_text).
  4. The final comment shows the payoff: run_crew(question, call=real_call) — the exact same orchestrator, now driven by a real model instead of the mock.

Try this: Because the mock and the real caller share the same (role, task) shape, you can flip between offline testing and live runs by changing one argument. That separation — logic vs. model — is the pattern to remember.

Only build a crew if a single agent isn't enoughA crew multiplies both capability and cost. Compare against a single agent (Project 5); the crew must beat it — and the critic must earn its keep on planted errors — to justify the extra spend.

Troubleshooting — every error you might hit expert

⚠️ If something doesn't match
What you seeWhat it means & the fix
ModuleNotFoundError: crew / agentsRun pytest from inside research-crew/.
Critic passes the planted lieThe support check must compare claim vs its own source; confirm critique() uses f.source.
Crew never finishesConfirm MAX_STEPS is checked in the researcher loop, not per-agent.
Report contains dropped claimsEnsure write() receives verified, not the raw findings.
Real mode: researcher won't quote sourcesStrengthen the researcher role prompt to require a quoted source span.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Scaffold: the Finding + one role functionBeginner

Context: A research crew is a set of single-job agents. The first slice defines the shared data shape and one role, with the model call injected so the whole crew runs offline and deterministically in tests.

Your task: Define a Finding dataclass (claim, source) and a plan(question) role that returns 2-4 sub-queries, taking the model call as an injectable parameter.

Requirements:

  • Finding carries a claim and its source
  • plan() returns a small list of sub-queries
  • The model call is a parameter, defaulting to a scripted mock
  • The demo runs with no API key
  • The injectable-call seam is what makes every later rung testable

💡 Hint: Make call a parameter on every role; a scripted mock_call keyed on the role name lets the crew run without a model.

Show solution

Design. Each agent is one job and every role takes an injectable call so tests " "use a scripted stand-in. That seam is the whole reason the crew is testable without a key.

from dataclasses import dataclass

@dataclass
class Finding:
    claim: str
    source: str

def mock_call(role, task):
    if role == "planner":
        return ["What is our churn?", "What caused it?"]
    raise ValueError(role)

def plan(question, call=mock_call):
    return call("planner", question)

print(plan("why did churn rise?"))   # ['What is our churn?', 'What caused it?']
Exercise 2 · Core feature: the critic gateIntermediate

Context: The crew's defence against compounding errors is a critic. A researcher can be wrong, but a claim survives only if its own cited source actually contains it — a pure filter, no model needed.

Your task: Build critique(findings) that keeps only findings whose claim text appears in its cited source, and prove a planted false claim is dropped.

Requirements:

  • Supported = the claim's words appear in the cited source
  • The critic is a pure function with no model call
  • A true claim backed by its source is kept
  • A fabricated claim citing an unrelated source is dropped
  • Show the liar is removed while the good finding survives

💡 Hint: Case-insensitive containment of the claim in its source is enough offline; this one filter is what stops a single bad researcher poisoning the report.

Show solution

Design. The critic is a pure filter — no model needed. 'Supported' = the claim's words appear " "in the source it cites. This is the single most valuable agent: it stops one bad researcher poisoning the " "report.

from dataclasses import dataclass
@dataclass
class Finding:
    claim: str
    source: str

def critique(findings):
    return [f for f in findings if f.claim.lower() in f.source.lower()]

good = Finding("net 30", "payment is net 30 days")
liar = Finding("revenue tripled", "payment is net 30 days")
kept = critique([good, liar])
print(len(kept), kept[0].claim)   # 1 net 30  -- liar dropped
Exercise 3 · Harder variant: sequential orchestrator with the step leashAdvanced

Context: An orchestrator wires the roles together, but an unbounded loop can burn budget. A single step leash — one counter across all agents — caps total work regardless of how sub-queries fan out.

Your task: Wire plan → research → critique → write into run_crew with a MAX_STEPS cap shared across all agents, returning the report and how many findings were dropped.

Requirements:

  • Steps increment on every agent action, not per-agent
  • The research loop breaks when the shared cap is hit
  • Findings pass through the critic before the writer
  • The result includes the report and a dropped count
  • The cap bounds work even when planning fans out widely

💡 Hint: One counter, checked before each research call and incremented on every action, is the leash; it must be global, not reset per role.

Show solution

Design. The leash is a single counter incremented on every agent action and checked before " "each research call. It caps total work regardless of how sub-queries fan out.

from dataclasses import dataclass
@dataclass
class Finding:
    claim: str
    source: str
MAX_STEPS = 12

def mock_call(role, task):
    if role == "planner": return ["q1", "q2"]
    if role == "researcher":
        return [Finding(f"Answer to {task}", f"Answer to {task} (src)")]
    if role == "writer":
        return "REPORT: " + "; ".join(f.claim for f in task)

def critique(fs): return [f for f in fs if f.claim.lower() in f.source.lower()]

def run_crew(question, call=mock_call):
    steps = 0
    subs = call("planner", question); steps += 1
    findings = []
    for q in subs:
        if steps >= MAX_STEPS: break        # the leash across all agents
        findings += call("researcher", q); steps += 1
    verified = critique(findings); steps += 1
    report = call("writer", verified); steps += 1
    return {"report": report, "dropped": len(findings)-len(verified),
            "steps": steps}

print(run_crew("why did churn rise?"))
Exercise 4 · Subtle correctness: only-verified reaches the writer, under a scripted liarExpert

Context: The critical invariant is that the writer never sees an unverified claim, even when a researcher lies. Correctness here is a property proven by construction, not an output you eyeball.

Your task: Inject a scripted call where the researcher returns a claim absent from its source, and assert the false claim appears nowhere in the final report.

Requirements:

  • Only the critic's verified output feeds the writer
  • The scripted researcher returns a claim its source doesn't support
  • The critic gate runs before write
  • Assert the fabricated claim is absent from the report
  • The report is empty (or reduced) precisely because the lie was dropped

💡 Hint: Gate before the writer: pass only critique(findings) into write, then assert the lie's text is not in the returned report.

Show solution

Design. Correctness here is a property, not an output: verified findings are the only " "input to write. Prove it by construction — feed a liar and check the report.

from dataclasses import dataclass
@dataclass
class Finding:
    claim: str
    source: str

def critique(fs): return [f for f in fs if f.claim.lower() in f.source.lower()]

def run_crew(question, call):
    subs = call("planner", question)
    findings = []
    for q in subs: findings += call("researcher", q)
    verified = critique(findings)                 # gate BEFORE writer
    return call("writer", verified)

def lying_call(role, task):
    if role == "planner": return ["q"]
    if role == "researcher":
        return [Finding("revenue tripled", "payment is net 30 days")]  # lie
    if role == "writer":
        return "REPORT: " + "; ".join(f.claim for f in task)

report = run_crew("q", lying_call)
print(report)                          # REPORT:  (empty -- lie dropped)
assert "revenue tripled" not in report
print("invariant holds")
Exercise 5 · Production concerns: per-agent token budget + a dead-loop breakerProfessional

Context: Production crews need graceful degradation. A shared budget debited per call fails closed when exhausted, and a loop breaker stops when two consecutive rounds add no new verified findings.

Your task: Add a shared token budget the crew debits per call and a dead-loop breaker that stops after two consecutive rounds with no new verified findings.

Requirements:

  • A budget object debits per call and refuses when it can't cover the next
  • On exhaustion the crew returns partial results, not a crash
  • Progress is measured as growth in the verified set
  • Two stalled rounds stop the crew early with a reason
  • The stop reason (budget / no_progress / done) is reported

💡 Hint: Charge the budget before each call and return what you have on empty; track a stall counter that resets whenever the verified set grows and trips at two.

Show solution

Design. Budget is a debit account raised before each call; on empty, return partial results " "rather than crash. Progress = growth in the verified set; two stalls -> stop early and report what you " "have. This is 'graceful degradation', the production default.

class Budget:
    def __init__(self, total): self.left = total
    def charge(self, n):
        if self.left < n: return False
        self.left -= n; return True

def crew_with_budget(rounds, budget, cost=100):
    verified, stalls = [], 0
    for r in rounds:                      # r = list of (claim, source)
        if not budget.charge(cost):
            return {"verified": verified, "stopped": "budget"}
        before = len(verified)
        verified += [c for (c, s) in r if c.lower() in s.lower()]
        if len(verified) == before:
            stalls += 1
            if stalls >= 2:
                return {"verified": verified, "stopped": "no_progress"}
        else:
            stalls = 0
    return {"verified": verified, "stopped": "done"}

good = [("net 30", "net 30 terms")]
bad  = [("x", "unrelated")]
print(crew_with_budget([good, bad, bad], Budget(1000)))  # stopped no_progress
print(crew_with_budget([good]*20, Budget(300)))          # stopped budget
Exercise 6 · Real-world: swap in Claude + reconcile conflicting sourcesIndustry scenario

Context: Shipping means swapping in a real model and confronting real research: sources disagree. A reconciliation step keeps the more-trusted source on a conflict and flags it for human review rather than silently dropping it.

Your task: Wire a real call via the Anthropic SDK (needs a key) and add reconciliation that, when two verified findings disagree on a topic, keeps the higher source tier and flags the conflict.

Requirements:

  • The real call uses the Anthropic SDK with role-specific system prompts
  • The offline crew stays intact as the no-key fallback
  • Verified findings are grouped by topic
  • On disagreement, the higher-trust source tier wins
  • Conflicts are recorded for review, never silently dropped

💡 Hint: Keep the offline crew unchanged — the real model is just another call; reconciliation groups by a topic key and resolves ties by a source-tier map.

Show solution

Design. Keep the offline crew intact; the real model is just another call. " "Reconciliation groups verified findings by topic key; on disagreement, higher source tier wins and the " "conflict is recorded (never silently dropped) so a human can audit it.

# --- real model call (needs creds / API key) ---
# import anthropic
# client = anthropic.Anthropic()
# ROLES = {"planner": "Break into 2-4 sub-queries, one per line.",
#          "researcher": "Answer with facts; quote the source text.",
#          "writer": "Write a short cited report from the findings."}
# def real_call(role, task):
#     m = client.messages.create(model="claude-opus-4-8", max_tokens=600,
#         system=ROLES[role], messages=[{"role":"user","content":str(task)}])
#     return m.content[0].text
# run_crew(question, call=real_call)   # after export ANTHROPIC_API_KEY=...
# OpenAI equivalent: from openai import OpenAI; client = OpenAI()
#   resp = client.responses.create(model="gpt-5.5", max_output_tokens=600,
#       instructions=ROLES[role], input=str(task)); return resp.output_text  # OPENAI_API_KEY

# --- offline: conflict reconciliation (runnable) ---
TIER = {"filing": 3, "blog": 1}     # trusted -> untrusted

def reconcile(findings):
    # findings: list of dict(topic, value, source_tier)
    best, conflicts = {}, []
    for f in findings:
        cur = best.get(f["topic"])
        if cur and cur["value"] != f["value"]:
            conflicts.append((f["topic"], cur["value"], f["value"]))
            if f["source_tier"] <= cur["source_tier"]:
                continue                      # keep the more-trusted incumbent
        best[f["topic"]] = f
    return {"kept": {t: v["value"] for t, v in best.items()},
            "conflicts": conflicts}

res = reconcile([
    {"topic": "revenue", "value": "$10M", "source_tier": TIER["filing"]},
    {"topic": "revenue", "value": "$30M", "source_tier": TIER["blog"]}])
print(res["kept"])       # {'revenue': '$10M'}  -- filing beats blog
print(res["conflicts"])  # [('revenue', '$10M', '$30M')]  -- flagged

✓ You are done when…

  • python crew.py prints a report with verified findings and 0 dropped.
  • python -m pytest tests/ -v shows 6 passed, including the planted-lie test.
  • You can explain why the writer only ever sees verified findings.
  • (Optional) A real model_call plugs into the same orchestrator.
📁 Your finished folder
research-crew/
├─ .venv/
├─ requirements.txt
├─ agents.py          (roles + Finding + mock_call)
├─ crew.py            (critic + sequential orchestrator + cap)
└─ tests/
   └─ test_crew.py    (6 offline tests incl. planted lie)
📋 Staff-level self-scoring — does this crew earn its multi-agent tax?
DimensionMeets the barAbove the bar
Task decomposition is crispRoles have single, non-overlapping responsibilities; the planner splits the question cleanly.Decomposition is validated on real tasks; no duplicated work; shared state shows each agent what's done.
Handoffs stay on-taskThe writer uses only verified findings; each claim is traceable to a source through the chain.Handoff correctness is tested — a bad finding upstream cannot silently become confident output downstream.
The loop is boundedHard step/turn caps per agent, a total token budget, and clear termination conditions exist.Convergence rate (% of runs terminating cleanly within caps) is measured; circular chatter is caught.
The critic actually worksA dedicated critic/verifier checks claims and citations against sources before synthesis.The critic is tested with planted false claims and demonstrably catches them — not a rubber stamp.
Cost is justifiedTotal tokens/calls per task are tracked; the multi-agent cost is known.Uplift vs a single agent (Project 6) is proven on hard tasks; if it only ties, the crew is dropped.
Final report is faithfulReport accuracy, coverage, and citation-completeness are evaluated (LLM-judge + spot human).A held-out eval set with graded reports; hallucinated citations are near-zero and measured, not hoped.

Score each row 0 (missing) / 1 (meets) / 2 (above). 0–4: a prototype — keep building. 5–8: a solid build you could take to review. 9–12: staff-level — production-defensible. Any dimension at 0 blocks shipping regardless of the total.

Knowledge check check yourself

✓ Knowledge check

Before building a multi-agent crew, the design says to justify it against a single agent. Why, and what specifically must earn its keep?

Show answer
A crew multiplies both capability and cost/failure surface, so it must measurably beat a single agent (Project 6) on hard tasks to justify the multi-agent tax. In particular the critic must catch planted errors — a crew whose critic rubber-stamps everything is just a more expensive single agent.
✓ Knowledge check

How does the crew guarantee that a lying researcher's fabricated claim never reaches the final report?

Show answer
The critic verifies each claim against its own attached source and drops any claim the source doesn't support; the writer synthesizes only from the verified findings. This is proven by a planted-false-claim test — a claim not present in its source is dropped, so it can't be written.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in