Agent Mentor Learn
Multi-Agent Collaboration · Lesson 6 of 6

Lesson 6: Hands-On: Building a Two-Agent Review Pipeline

Learning goals:

  • Write a genuinely runnable producer-reviewer two-agent pipeline with the Claude API
  • Get the reviewer to return a structured, checkable review result instead of a blanket "looks fine"
  • Put a safety valve on the loop so the producer and reviewer don't polish back and forth forever

Prerequisites: finish Lessons 1-5, be able to read basic JavaScript/Node.js, and have a working Claude API key | Previous: Lesson 5 <<

First, the result: one full run

This is what you'll have running by the end of the lesson. You hand the terminal a task, and two agents take turns until the review passes or you hit the round limit:

$ node review-pipeline.js "Write an API change announcement for developers: the v2 endpoint changes the user_id field from a number to a string"
[Producer v1]The v2 endpoint is here! Hugely improved experience — please switch to the new version soon.
[Reviewer round 1] Rejected. Issues:- Doesn't spell out the specific field this change affects (never mentions that user_id goes from number to string)- Gives no migration advice; developers don't know how to update their code- "Hugely improved experience" is an unverifiable, exaggerated claim with no concrete basis
[Producer v2]v2 API change notice: the user_id field type changes from number to string.Check every piece of code that parses this field and switch the read logic from numeric to string,to avoid parse failures caused by the type mismatch. This change takes effect in v2.1.0.
[Reviewer round 2] Approved
Final draft (approved in round 2):v2 API change notice: the user_id field type changes from number to string.Check every piece of code that parses this field and switch the read logic from numeric to string,to avoid parse failures caused by the type mismatch. This change takes effect in v2.1.0.

The first version gets bounced by the reviewer, with reasons tied to each specific criterion; the producer revises into a second version, the reviewer looks again, and this time it passes. This is the producer-reviewer pattern from Lesson 4 turned into code: "one LLM call generates a response while another provides evaluation and feedback in a loop."1

The overall shape: the same skeleton as an execution loop

If you took the course Agent Tool Calling: Getting Agents to Actually Do Things in this series, this pipeline's skeleton will look familiar: a loop, one judgment per round, a result that decides whether to keep going, plus a safety valve against infinite looping. The only difference is what the judgment judges — that course's tool-execution loop judges "does the model still want to call a tool" (the loop's semantics are in that course's lesson The Full Round-Trip of a Tool Call and its official sources), while here it judges "did the reviewer say it passed." Same skeleton, different contents in the loop body.

The whole pipeline is three functions stitched together: runProducer generates or revises the text, runReviewer scores it against criteria and gives specific notes, and runPipeline links the two into a loop with a round ceiling as the safety valve.

Step 1: The producer — take the task, produce the text

On its first run the producer has only the task itself; on a second run after a rejection, it also carries the full previous version and the review notes, so the producer revises on top of the last version according to the notes rather than free-styling from scratch:

The producer's prompt is self-contained. As Lesson 3 covered, a subagent can't see what happened on the orchestrator's side, and it can't see how it was reviewed last time either2. So every call writes "what the task is," "what the previous version said," and "(if any) what last round's problems were" into this call's prompt verbatim. Notice that even the producer's own previous draft has to be passed back explicitly — this is the half of the self-contained principle that's easiest to miss: the Messages API is stateless, every request must carry the full history it needs, and the server keeps nothing between requests3. "Revise your previous version" only means something when the previous version was actually written into this prompt.

Step 2: The reviewer — score against concrete criteria, no vague verdicts

The reviewer doesn't just ask the model "is this any good." As Lesson 5 covered, verification has to land on concrete, checkable criteria rather than an impression-based score4. Here the reviewer gets an explicit checklist and is required to reply in a fixed JSON format:

Together, the approved and issues fields make up a structured review result: not a single "it's okay," but "pass or fail" plus "the specific problem behind each failed criterion." Once the producer has issues, it revises those specific problems instead of guessing where to go from a vague verdict.

Step 3: Don't trust the review result blindly — treat a parse failure as a rejection

runReviewer returns a string, not an actual JSON object, so it still has to be parsed. Even though the reviewer is told to "reply strictly in JSON," without a structured-output constraint the model can still produce syntactically invalid JSON, drop fields, or wrap the JSON in a code block with a few lines of explanation around it5. The trap here is: what happens when parsing fails? Taking the lazy route — defaulting to letting it through on a parse failure — quietly turns a "the reviewer didn't do its job" failure into "review passed." That's exactly the point Lesson 5 made: output that "looks" finished isn't the same as output that's actually correct, and what you can't verify you shouldn't ship6. Here we do the opposite: a parse failure always counts as a rejection, never as a pass:

The typeof parsed.approved !== "boolean" and !Array.isArray(parsed.issues) lines extend the same idea — even when JSON.parse succeeds, you still confirm the parsed fields have the right shape, and a wrong field type also counts as a rejection. Don't let your guard down just because it's "at least valid JSON."

One aside: there's an official structured-outputs feature that guarantees, at the sampling level, that the response strictly matches a schema5. This lesson deliberately uses the "bare call plus your own defensive parsing" style so you feel firsthand that model output can't be trusted blindly; in production you can use structured outputs to remove this pothole entirely.

Step 4: Wire it into a loop, add the safety valve

With runProducer, runReviewer, and parseReview in hand, runPipeline wires the three together, and MAX_ROUNDS is the only safety valve here — the producer and reviewer could in theory polish forever, so there has to be a ceiling:

When it hits MAX_ROUNDS still un-passed, runPipeline doesn't force a "pass" verdict. It honestly hands over the last draft and the still-unresolved problems for human review — this too is Lesson 5's point applied at the closing step: when the result-integration stage runs into something it can't judge, it shouldn't paper over it by deciding for itself in code.

Recap

  • The producer-reviewer pipeline's skeleton is the same thing as an execution loop: a loop, one judgment per round, a result that decides whether to keep going, plus a safety valve against infinite looping. The official definition of this pattern is exactly "one LLM call generates a response while another provides evaluation and feedback in a loop"1 — here the judgment switches from "should a tool be called" to "did the reviewer say it passed."
  • The producer's prompt is self-contained: every call writes the task, the full previous version, and (if any) last round's specific problems into the prompt verbatim — the Messages API is stateless, every request must carry the full history, and nothing is kept between requests3, so you can't count on the model remembering what happened last round on its own2.
  • The reviewer scores against concrete, checkable criteria, item by item, and returns a structured {approved, issues} rather than a blanket verdict4.
  • What the reviewer returns can't be trusted blindly either — a parse failure or a wrong field shape should count as a rejection, not a quiet pass6; this principle applies not only to "trusting what a subagent says" but also to "trusting the data format a subagent returns."
  • When it hits the max round count still un-passed, the pipeline should honestly hand over the last draft and the unresolved problems for human review, rather than deciding a pass for itself in code.

That's all six lessons of this course: from "why multiple agents," through how the orchestrator and subagents divide the work, how to write delegation prompts, which collaboration pattern fits which scenario, and how to handle failure, ending with building a working producer-reviewer pipeline by hand. The most worthwhile thing to do next isn't rereading the explanations — it's picking a small, real task you have on hand, dropping it into this pipeline skeleton, tweaking the review criteria, and running it to see whether it bounces the draft and how many times. Tuning the review criteria yourself once beats rereading the theory ten times.

Footnotes

  1. Building effective agents (Anthropic Engineering) — https://www.anthropic.com/engineering/building-effective-agents 2

  2. Create custom subagents (Claude Code Docs) — https://code.claude.com/docs/en/sub-agents 2

  3. Using the Messages API (Claude API) — https://platform.claude.com/docs/en/build-with-claude/working-with-messages 2

  4. How we built our multi-agent research system (Anthropic Engineering) — https://www.anthropic.com/engineering/multi-agent-research-system 2

  5. Structured outputs (Claude API) — https://platform.claude.com/docs/en/build-with-claude/structured-outputs 2

  6. Best practices for Claude Code (Claude Code Docs) — https://code.claude.com/docs/en/best-practices 2

Exercises

01

Assemble this lesson's code into a review-pipeline.js, run npm install @anthropic-ai/sdk, npm pkg set type=module, set ANTHROPIC_API_KEY, and run this lesson's example task once. Confirm you see at least one "Rejected" round before you see "Approved." (If the producer's first version passes straight through, swap in a task that's easier to trip over — for example, deliberately ask for "a very short announcement" without saying how short.)

Level 1: Get it running, then add a review criterion

Once it runs, add a new criterion to REVIEW_CRITERIA: "Does the text mention the specific version number where the change takes effect?" Run it again and confirm the reviewer's issues now include a note tied to this new criterion.

Done criteria · checked locally
02

The version of parseReview below has a problem. First explain the situation in which it would let a draft that was never really reviewed through as "approved," then give the fixed code.

Level 2: Break something on purpose, then fix it
Done criteria · checked locally