A Specialized Agent for Every Phase

You don't have one engineer do the requirements, write the code, and review their own PR. Your agents shouldn't either.

Automating with Claude
Series
Automating with Claude

Practical Claude workflows for marketing, engineering, and everyday work — built for people who'd rather think than repeat themselves.

Part 6/6

You add a feature request to a conversation: “Add rate limiting to the API.” An hour later, Claude has clarified the requirements, designed the implementation, written the code, added tests, and reviewed the whole thing. It approves the PR.

Effective teams don’t let the same person write the spec and approve their own PR.

A solo engineer routinely does all of this and does it well. What teams have learned, through costly experience, is that the person who wrote the code is the wrong reviewer — not for lack of skill, but because they cannot un-know their intent. They evaluate the output against what they meant to build, not against the requirements. Inside a single context window, an agent has the same problem. By the time it reaches the review phase, the entire implementation decision trail is in context. It is checking its own reasoning.

Part 5 of this series covered the general theory: three coordination patterns, shared state without shared context, and the API mechanics behind multi-agent design. This post is a worked example of one of those patterns applied to a specific domain: software development. The pipeline pattern, applied to five phases, with a deterministic review at the end.

Each agent receives a structured file, produces a structured file, and is given an explicit scope constraint on what it may do. The pattern works through the Claude Messages API. It does not require a specific framework.

The Monolithic Agent Problem

A single agent asked to handle an entire development task encounters four failure modes that are structural, not fixable by making the prompt better.

Phase bleed

A generalist agent has no hard boundary between “understanding what to build” and “building it.” It forms opinions about the implementation while reading the requirements, and those opinions quietly shape what it decides the requirements mean. By the time it starts coding, the spec has shifted to match the solution it was already imagining. The requirement and the implementation were produced in the same reasoning session: they are not independent.

Review capture

A reviewer who participated in writing the code cannot evaluate it objectively. They know why each decision was made. They remember the alternatives that were ruled out. They are not checking the output against the requirements; they are checking it against their memory of the intent. A code review written by the same agent that wrote the code is less a review than it is a confirmation. Independent review primarily reduces anchoring and correlated blind spots; self-review cannot un-know its own intent.

Failure isolation

When the output is wrong, which phase produced the error? If one agent did everything, you cannot tell whether the spec was ambiguous, the plan was flawed, the implementation diverged from the plan, or the tests missed the case. Diagnosing the failure requires untangling the reasoning from a single long session. Separate agents produce separate output files, making it easier to trace a failure back through the phases that produced it.

Context accumulation

A long development session accumulates noise: intermediate decisions that were reversed, alternatives that were considered and dropped, tool call outputs from thirty minutes ago. Part 4 of this series covered compaction, the mechanism by which Claude’s memory layer compresses all of this into a summary, dropping the detail that mattered. Separate agents each start clean. Their compaction events are independent, and the output files they produce survive compaction completely, because files are outside the message array.

The Pipeline

Five agents. Each one has an input contract (what it receives), an output contract (what it produces), and a scope constraint (what it explicitly cannot do).

Phase Input Output Cannot do
Requirement Raw ask, existing docs Structured spec Write code, make architectural decisions
Plan Spec Implementation plan Write code, revise spec
Dev Plan + spec Code diff Revise plan, write tests, review
Test Code diff + spec Test suite, gap report Revise code, change spec
Review Code diff + spec + rubric Scorecard, blocking issues Revise code
Five-phase development pipeline: Requirement produces SPEC.md passed to Plan, which produces PLAN.md passed to Dev, which produces a code diff passed to Test, which produces a test suite passed to Review. A PIPELINE_STATE.md bar spans all phases as the shared coordination layer.

The “cannot do” column is as important as the input and output. An agent without scope constraints will drift. The plan agent that starts writing pseudocode has blurred the boundary with the dev agent. The review agent that starts rewriting code has become the dev agent again. The scope constraint lives in the system prompt: it is the agent’s job description. That is instruction, not enforcement. In a production pipeline, the boundary should hold at two levels: the prompt tells the agent what it is authorised to do, and the runtime controls which tools and files it can actually access. A test agent instructed not to modify production code can still do so if it has unrestricted write access.

Phase 1: The Requirement Agent

The requirement agent takes a raw feature request and produces a structured specification. The request might be a single sentence, a ticket description, or a thread of messages. Its job is disambiguation, not design.

You are a requirements analyst. You receive a feature request and produce a structured specification.

Your output is a SPEC.md file with four sections:
- Goal: one sentence describing what the feature does and why
- Acceptance criteria: numbered list of testable conditions that must be true
- Constraints: things the implementation must not do or change
- Open questions: ambiguities that require human clarification before development begins

You do not design the implementation. You do not suggest how the code should be structured.
You do not make architectural decisions. If you find yourself writing anything resembling
code or a technical approach, convert it to a constraint or an open question instead.

If an acceptance criterion cannot be expressed in testable terms, list it as an open question.

A well-formed spec:

# SPEC.md — Rate Limiting for the API

## Goal
Prevent any single API key from sending more than 1,000 requests per minute, returning
a 429 status with a Retry-After header when the limit is exceeded.

## Acceptance criteria
1. Requests beyond the rate limit return HTTP 429 with a Retry-After header
2. The Retry-After value is the number of seconds until the next window opens
3. Rate limits are tracked per API key, not per IP address
4. The rate limit resets at the start of each minute, not on a rolling window
5. A 429 response does not consume a request from the next window's quota

## Constraints
- Must not modify the existing API key validation logic in auth/keys.ts
- Must work without a new external dependency (no Redis, no third-party rate limit service)
- Scoped to single-process deployment; cross-instance rate-limit consistency is out of scope

## Open questions
1. Should rate limit counters persist across server restarts, or reset on restart?
2. What is the desired behaviour when an API key is deleted mid-window?

Notice what is absent: no mention of token bucket algorithms, no position in the middleware chain, no pseudocode. The requirement agent has produced a contract. The plan agent will make the design decisions with a clean context.

Phase 2: The Plan Agent

The plan agent receives SPEC.md and produces a plan: a sequence of specific, scoped changes to specific files. Its constraint is narrow: read the spec, produce a plan, write no code.

You are a software architect. You receive a structured specification and produce an
implementation plan.

Your output is a PLAN.md file with four sections:
- Approach: one paragraph describing the overall implementation strategy
- Files: a list of files to be created or modified, with a one-line description of each change
- Steps: a numbered sequence of discrete implementation tasks, each targeting one file
- Spec gaps: ambiguities in the spec that block the plan and require human clarification

You do not write code. You do not write pseudocode. You do not suggest variable names
or function signatures. Descriptions of what the code should accomplish, not how.

You may not change the spec. If you discover an ambiguity that blocks the plan,
list it in Spec gaps and stop. Do not resolve the ambiguity yourself.

The “Spec gaps” section is the key mechanism here. When that section is non-empty, a human reviews it and either updates the spec or confirms the plan agent’s interpretation. The ambiguity is surfaced rather than absorbed. A plan agent that resolves ambiguities silently produces a plan that satisfies a spec that nobody agreed to.

Phase 3: The Dev Agent

The dev agent receives SPEC.md and PLAN.md. It writes the code. Its constraint is the strictest: follow the plan, log every deviation, write no tests.

You are a software engineer. You receive a specification and an implementation plan
and you implement the plan exactly.

You work through the steps in order. For each step, you make the changes to the files
listed in the plan. You do not write tests; the test suite will be written separately.
You do not revise the spec. You do not change the plan.

If you encounter an ambiguity that materially affects an acceptance criterion, constraint,
or the overall architecture, document it in DEVIATIONS.md, mark the phase as blocked,
and stop. Do not resolve it by choosing an interpretation. For minor implementation-level
details with no impact on acceptance criteria, choose the simplest approach and log it
as a note in DEVIATIONS.md.

When you are done, write a DIFF_SUMMARY.md that lists every file changed, every function
added or modified, and every deviation logged.

DEVIATIONS.md is the signal that a human needs to see before the test and review agents run. A deviation that seemed minor to the dev agent might invalidate an acceptance criterion. Logging it rather than resolving it keeps the decision where it belongs: with a person, not with the implementation agent.

Phase 4: The Test Agent

The test agent receives the changed files and SPEC.md. It does not receive PLAN.md. This is intentional: the test agent’s job is to verify that the code meets the spec, not that the code matches the plan. If it could see the plan, it might test plan-compliance rather than spec-compliance — and the plan may already have diverged from the spec before the dev agent touched it. If the dev agent diverged from the plan in a way that still satisfies the spec, the tests should pass. If the divergence broke a criterion, the tests should catch it.

You are a test engineer. You receive a specification and the files changed by an implementation.
You write a test suite that verifies every acceptance criterion in the specification.

For each acceptance criterion, write at least one test. Label each test with its criterion
number (e.g., AC-3: Rate limits are tracked per API key).

You do not modify the implementation. If you discover that the implementation cannot
satisfy an acceptance criterion as written, log the finding in TEST_GAPS.md with the
criterion number and a description of what is missing. Do not modify the code to make
the test pass.

Your tests must be runnable without manual setup. If a test requires a mock or fixture,
create it.

TEST_GAPS.md is escalation, not failure. A non-empty TEST_GAPS.md means a human decides: update the spec, fix the implementation, or accept the gap. The test agent does not make that decision.

The Deterministic Review

The review agent is the phase where the “deterministic” framing does the most work. It is worth being precise about what the word means here.

A review agent asked to evaluate code with no constraints behaves as an LLM-as-judge: impressionistic, inconsistent, and contaminated by whatever it knows about the implementation context. Run the same code through it twice and you get different findings. Give it context about why the developer made a particular decision and it judges that decision more charitably. This is not useful as an engineering gate.

A review agent evaluated against a fixed rubric is a different thing — though it is worth being precise about what the rubric does and does not guarantee. The reviewer is still an LLM interpreting code, identifying evidence, and classifying findings: it can miss a defect on one pass and identify it on another. The rubric does not make the LLM’s judgment consistent. What it makes deterministic is the protocol around the reviewer: fixed criteria, structured findings, explicit blocking conditions, and a rule-based decision about whether the pipeline proceeds. The reviewer remains probabilistic; the gate logic is not. The rubric is a team artefact: version-controlled, improvable over time, capturing what the team actually cares about.

This is what the authors of OpenCodeReview call “deterministic engineering for uncertain agents”: rather than trying to make the LLM behave consistently through better prompting, you inject determinism at the architecture level. The rubric is one injection point. Restricting the reviewer’s context to exclude the implementation session’s reasoning — while allowing access to relevant repository files for security and consistency checks — is another.

Designing the rubric

A rubric is a structured document with categories, scoring criteria, and exit conditions:

# REVIEW_RUBRIC.md — v1.3

## Categories

### Spec compliance (blocking)
- Every acceptance criterion has a corresponding implementation
- No acceptance criterion is partially implemented
- The implementation does not contradict any constraint in SPEC.md

### Test coverage (blocking)
- Every acceptance criterion has at least one test labelled with its criterion number
- Tests cover at least one failure path per blocking criterion
- Tests are runnable without manual setup

### Security (blocking)
- No secrets or credentials in code or comments
- User-supplied input is validated before use
- New dependencies are pinned to an exact version

### Consistency (warn)
- New code follows the naming conventions of the files it touches
- Error handling follows the existing pattern in the affected module

### Hygiene (non-blocking)
- No commented-out code
- No TODO comments without a linked issue

## Scoring
Each category scores pass / warn / fail.
A blocking category fail blocks the review.
Three or more warn-category warns block the review.

## Exit criteria
Approved when: all blocking categories pass, and warn-category total is fewer than three.

The system prompt for the review agent:

You are a code reviewer. You evaluate a code diff against a rubric.

You receive three inputs:
- SPEC.md: the requirements the code must satisfy
- The changed files (code diff)
- REVIEW_RUBRIC.md: the criteria you evaluate against

You do not modify any code. You do not suggest alternative implementations.
You evaluate the diff against the rubric, category by category.

Your output is a REVIEW_SCORECARD.md with a score for each category, a specific
file and line for every warn or fail finding, and a final verdict: APPROVED or
CHANGES REQUESTED with a blocking issues list.

You do not receive context about why decisions were made. You do not receive the plan
or the implementation session's reasoning. Where the rubric requires consistency or
security checks, you may read relevant repository files. Your evaluation is grounded
in the diff and the spec.

The last two lines carry as much weight as the rest. The reviewer has no context about the implementation journey: not the alternatives considered, not the constraints the dev agent was working under. It evaluates the diff and the spec; it can read surrounding code for consistency and security checks. A decision that required context to justify looks exactly like any other decision to the reviewer. This is not a bug; it is the point. If a decision requires context to justify it, that context belongs in the code itself, in a comment or changelog entry, not in the reviewer’s conversation history.

A scorecard for the rate limiting feature might look like:

# REVIEW_SCORECARD.md

## Spec compliance: FAIL (blocking)
FAIL — AC-4 ("rate limit resets at the start of each minute, not a rolling window")
is not implemented. middleware/rate-limit.ts uses a sliding window algorithm (line 34).
The spec requires a fixed-window implementation.

## Test coverage: FAIL (blocking)
FAIL — AC-5 ("a 429 response does not consume a request from the next window's quota")
has no test. tests/rate-limit.test.ts covers AC-1 through AC-4 only.

## Security: PASS

## Consistency: PASS

## Hygiene: PASS

---

## Verdict: CHANGES REQUESTED

### Blocking issues
1. middleware/rate-limit.ts line 34: replace sliding window with fixed-window algorithm
   to satisfy AC-4.
2. tests/rate-limit.test.ts: add a test for AC-5 ("a 429 response does not consume a
   request from the next window's quota"). Test coverage is a blocking category.

This scorecard is precise and falsifiable. The finding in the blocking issues list is not a stylistic opinion: it is a specific line, a specific criterion number, and a specific description of the gap.

The rubric as a living document

A rubric that never changes becomes irrelevant. New codebases develop new conventions, teams learn from incidents, and categories that once mattered stop mattering. The rubric should have a version number and a changelog:

## Changelog
- v1.3 (2026-09-14): Added "dependencies pinned to exact version" to Security (blocking)
  after incident-2026-09-10 where a patch version bump introduced a regression
- v1.2 (2026-07-01): Moved "error handling consistency" from blocking to warn — too many
  false positives in modules with mixed patterns
- v1.1 (2026-04-15): Added test coverage category

The changelog tells you which rules came from incidents and which came from preference. When a rule generates too many false positives, you down-grade it. When an incident reveals a missing rule, you add it. The rubric improves over time because it is a versioned artefact, not a prompt you regenerate each session.

Handoffs: the Coordination Layer

The five agents pass state through files. No agent reads the prior agent’s conversation history. No agent receives a prose summary of what the previous agent decided. Every handoff is a structured document with a known schema.

This is the principle Anthropic uses in their multi-agent research system: subagents store their work in external systems and pass lightweight references back to the coordinator. The filesystem is the coordination layer, not the conversation.

A PIPELINE_STATE.md at the root of the working directory tracks what has been completed and where to find each output:

# PIPELINE_STATE.md

## Current phase: review
## Blocking: none

## Outputs
- SPEC.md: complete (2026-11-02 14:32)
- PLAN.md: complete (2026-11-02 14:45)
- DEVIATIONS.md: empty
- DIFF_SUMMARY.md: complete (2026-11-02 15:20)
- TEST_GAPS.md: empty
- REVIEW_SCORECARD.md: in progress

This file is a human-readable audit trail and the context a new agent receives when it starts. Not the reasoning session of the previous agent: just its outputs and their status.

This design also solves compaction gracefully. Each agent runs in its own session. If the dev agent’s session grows long and compaction fires, the summariser sees only the dev agent’s conversation. It preserves what matters to that phase: which files were changed, which steps were completed, which deviations were logged. Everything else is discarded. The test agent does not need the dev agent’s session to be intact. It needs DIFF_SUMMARY.md, which is on disk regardless of what happened to the session.

Running the Pipeline

Five system prompts and five output files describe what each agent does. The orchestrator connects them.

A note on execution: the Requirement and Plan agents are text-generation tasks well-suited to a single API call each. The Dev and Test agents are different — they need to read and write files, run commands, and verify outputs. Those phases require tool-enabled agent loops or application-side orchestration, not stateless messages.create() calls. The design described here is architectural; the execution harness depends on how you wire the agents to your filesystem and test runner.

The orchestrator reads PIPELINE_STATE.md to determine which phase to run next, passes the appropriate input files to that agent, and updates the state file when the phase completes. The requirement agent receives the raw feature request. The plan agent receives SPEC.md. The dev agent receives SPEC.md and PLAN.md. The test agent receives SPEC.md and the changed files (not PLAN.md). The review agent receives SPEC.md, the changed files, and REVIEW_RUBRIC.md.

Four files act as gates. After the requirement phase, if SPEC.md contains unanswered blocking questions, the orchestrator pauses before running the plan agent. After the plan phase, if PLAN.md lists any spec gaps, the same pause applies before the dev agent runs. After the dev phase, if DEVIATIONS.md is non-empty, the orchestrator pauses before the test agent. After the test phase, if TEST_GAPS.md is non-empty, the orchestrator pauses before review. Before the review agent runs, the test suite must be executed and its pass/fail results recorded separately from the agent’s gap report. Failed tests block the pipeline regardless of what TEST_GAPS.md contains; the review agent may inspect the failures, but it cannot approve a change while required tests are failing. The orchestrator does not resolve these gates automatically; surfacing the decision to a person is the point. Note that gates based on agent-reported files are only as reliable as the agent’s compliance: an orchestrator that finds DEVIATIONS.md empty has no independent confirmation that no deviations occurred.

PIPELINE_STATE.md is the checkpoint. If the process is interrupted between phases, the next run reads the state and resumes from the last completed phase without re-running earlier ones.

In Claude Code, each phase maps to an Agent tool invocation. The orchestrator session advances through phases, spawning one subagent per phase. Custom Claude Code subagents load applicable CLAUDE.md instructions by default, subject to configuration and built-in-agent exceptions.

Failure Modes

Phase bleed

The plan agent starts writing pseudocode. The test agent starts modifying implementation files. The usual cause is a system prompt that describes what the agent should produce without explicitly stating what it cannot produce. Add the “cannot do” constraint explicitly. If bleed continues, add a verification step after each phase: if the plan agent’s output contains code blocks, reject it and re-run with a stronger constraint.

Spec drift

The dev agent finds the spec ambiguous and resolves the ambiguity silently, without logging a deviation. The resulting code passes the tests but does not satisfy the actual requirement. DEVIATIONS.md is the mitigation: an agent instructed to log deviations rather than resolve them surfaces the ambiguity before the test and review phases run. A human sees it. The decision is made explicitly.

Review capture

The reviewer receives implementation context it should not have, typically through a system prompt that includes reasoning about why decisions were made. The review agent receives the diff, the spec, the rubric, and relevant repository files for security and consistency checks. What it should not receive is the implementation session’s reasoning: the alternatives considered, the constraints the dev agent was working under. If a decision requires that context to justify it, the context belongs in the code itself, not in the reviewer’s conversation.

Rubric ossification

A rubric that no one updates becomes a rubber stamp. Track coverage: when an incident post-mortem identifies a failure that the current rubric would not have caught, add a rule. When a rule generates more false positives than true positives over a quarter, remove or down-grade it. A rubric without a changelog is a rubric nobody is maintaining.

What This Isn’t

This pipeline is a design workflow, not a deployment workflow. There is no deployment phase, no integration test against a staging environment. The review agent evaluates the code diff against a rubric; it does not run the service under load. That is a different problem.

This is also not a replacement for human judgment at the requirement and review phases. The requirement agent produces a spec, but a human should review the open questions before the plan agent runs. The review scorecard is machine-generated, but a human should review the blocking issues before deciding to approve or reject. The pipeline makes the work faster and more consistent. It does not remove the judgment calls.

Finally, this pattern is not for every task. For a small bug fix or a one-line change, five agents and five output files is overhead. The pipeline earns its cost when the task is large enough that phase boundaries matter: when the distinction between understanding what to build and building it is meaningful, and when the review is a real gate rather than a formality.


Each post in this series has described one layer of the agent stack: conventions, then workflows, then the tool layer, then the memory layer, then coordination, and now the pipeline pattern. The pipeline in this post handles the predictable case: phases in order, each one completing before the next begins.

What it does not answer is what happens when phases fail and need to loop back. The test agent rejects the dev agent’s output. Does the dev agent re-run, does the plan get revised, or does a human decide? The dev agent logs three open ambiguities in DEVIATIONS.md. Does the requirement agent re-run, or does a human resolve them? That is the feedback loop problem, and it is what separates a pipeline from an autonomous system. It is coming next.

More in Automating with Claude 6 / 6
← Previous Multi-Agent Coordination
Published
Mastodon

Follow me on Twitter

I tweet about tech more than I write about it here 😀

Ritesh Shrivastav