Chapter 6 of 9. Fanning out three subagents in parallel is the easy half. Deciding that they must run, when the thing making the decision is the same model whose work they are reviewing, is the half that needs a hook. Prerequisite: Chapter 2. You should already know why CLAUDE.md advises and a hook enforces.
Everything here is checked against Claude Code 2.1.209 on 2026-07-14. The subagent frontmatter fields and the hook exit-code contract are version-sensitive, so I say the version where a claim depends on it.
The pain that produces this chapter is slop debt. One agent writes a feature. The same agent, on a roll, writes the tests. The tests pass. Nothing in the loop is wrong in the way a typechecker recognises, and nothing in the loop is independent either. Run that pattern for a week and you have a codebase whose test suite certifies the author's own assumptions. My constitution carries a dated post-mortem about exactly this, and what it prescribes is a piece of multi-agent orchestration with a gate bolted to the front of it.
What a subagent actually buys
Anthropic's subagents documentation states the mechanic in one sentence: "Each subagent runs in its own context window with a custom system prompt, specific tool access, and independent permissions. When Claude encounters a task that matches a subagent's description, it delegates to that subagent, which works independently and returns results."
Two consequences fall out of that, and they pull in opposite directions.
Start with why you want subagents at all. The cost guidance puts it plainly: "Delegate these to subagents so the verbose output stays in the subagent's context while only a summary returns to your main conversation." Anything you leave sitting in the main window keeps charging you, because "stale context wastes tokens on every subsequent message". A grep that returns four hundred lines is a tax on every message after it. Push it into a child window and you pay for it once.
Orchestration is hard for the opposite reason. Only a text return value crosses back. The parent never sees the child's reasoning, its intermediate files, or the three dead ends it explored. You are buying isolation, and isolation is also amnesia.
A third property decides whether any of this holds up. Delegation is model-initiated. The docs are explicit: "Claude uses each subagent's description to decide when to delegate tasks." A subagent gets summoned when the parent judges that a paragraph of prose matches the task in front of it. That judgement is probabilistic. The parent reads the description, decides it does not apply, and spawns nobody. Nothing in the loop notices.
Here is a real definition from the estate, unedited:
---
name: security-auditor
description: Use this agent after implementing authentication, payment flows, file uploads, admin functionality, or any feature that handles user data. Use proactively before any PR that touches auth, data access, or external integrations. Also use when reviewing third-party dependencies.
tools: Read, Bash, Grep, Glob
model: opus
permissionMode: plan
---
Note what is missing from tools. No Write, no Edit. Strip a reviewer of its write tools and reporting becomes the only move available to it; there is no mechanism left for quietly patching the finding it just filed and then calling the file clean. Three agents in the estate carry that exact allowlist: constitution-checker, frontend-perf and security-auditor. Seventeen of the thirty-one have no write tool of any kind.
The coder-as-tester bias
On 2026-05-15 the constitution grew this entry. I quote it as it sits in the file:
# 2026-05-15: Global PreToolUse hook at ~/.claude/hooks/agent-audit-preflight.py
# blocks any `git commit` whose staged files span both code AND tests
# without an independent-agent audit. Closes the coder-as-tester weakness
# (BBBrain issue #66) — the SAME agent writing both code and tests is a
# bias: it tests what it built, not what it might have broken.
Read the last clause slowly. It tests what it built.
The agent writes good tests. That is the failure. They encode the implementation it already has in its head, so they pass on the first run and stay green through the regression they were meant to catch.
I put the fix in CLAUDE.md first: a rule that says always spawn an independent reviewer before committing. It got ignored, in the way the memory docs predict it will be ignored. "Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead." The same page names the escalation path outright. "If the instruction is something that must run at a specific point, such as before every commit or after each file edit, write it as a hook instead."
So the review checkpoint is a hook.
The review checkpoint: a PreToolUse hook that blocks the commit
agent-audit-preflight.py matches git commit in a Bash tool call, reads the staged file list, and classifies each path. If the set contains both a test file and a non-test code file, the commit is a coder-as-tester commit and it gets stopped. Four lines carry the decision:
has_test = any(_is_test_path(f) for f in files)
has_code = any(_is_code(f) and not _is_test_path(f) for f in files)
if not (has_test and has_code):
return 0 # not a test+code commit — let it through
Everything else in the file is classification (tests/ anywhere in the path, a test_ basename prefix, a _test. infix) and the message it prints on the way out. The block itself is one return value:
print(" To proceed: re-run with BBBRAIN_AUDITED=1 in env or command.", file=sys.stderr)
print(" To abort: investigate the audit gap.", file=sys.stderr)
print("═════════════════════════════", file=sys.stderr)
return 2
Stderr from a blocking PreToolUse hook is fed back to the model as an error message. That is what turns a gate into an orchestration primitive. Refusing the commit is only half of what the hook does; the other half is handing the model the instruction set for getting past it.
Three reviewers in one message
This is the text the blocked model receives, verbatim from the hook's own stderr:
Did you spawn INDEPENDENT specialist agents to review both
the code AND the tests (separately from the agent that wrote
them)? Recommended trio in parallel:
• qa-engineer — verify tests catch real regressions
• security-auditor — review for new attack surface
• pr-review-toolkit:code-reviewer — independent design pass
Spawn via the Agent tool (single message, 3 calls). Apply
any fixes, then re-spawn once for verification.
The whole architecture is in that block.
- Fan out, in one message, with three calls
Three subagents, three isolated context windows, zero shared state. Here, parallelism is the isolation: three windows that cannot see each other produce three opinions that cannot contaminate each other. Speed is a side effect.
- Apply the fixes in the parent
The children return summaries. The parent holds the code and does the editing.
security-auditorgetsRead, Bash, Grep, Globand nothing that writes, so its output is a report or it is nothing. - Re-spawn once to verify
One verification pass. Loop it, and the process converges on whatever the reviewers happen to be scoring, which re-invents the bias you set out to break, one level up.
- Commit with the token
BBBRAIN_AUDITED=1, as an env var or as a literal in the command string. The bypass is deliberate, typed by hand, and greppable across a shell history.
Now the drift. The hook's stderr names pr-review-toolkit:code-reviewer as the third reviewer. The GOTCHA in CLAUDE.md that documents the same hook names constitution-checker. Nothing reconciles them.
Model, tools, maxTurns
The AGENTS: rule in my constitution reads:
# AGENTS: Each subagent declares its `model:` in frontmatter. Cheapest
# model that can do the job: Haiku for deterministic mechanical
# work, Sonnet for synthesis + code, Opus for genuine reasoning.
# No global override — frontmatter is source of truth. Reviewers
# should question Opus selections that don't justify the cost.
Anthropic sets the same routing in its cost guidance: "Sonnet handles most coding tasks well and costs less than Opus. Reserve Opus for complex architectural decisions or multi-step reasoning." For simple subagent work the same page tells you to "specify model: haiku in your subagent configuration", and the subagents doc lists the move among the benefits of the whole mechanism: "Control costs by routing tasks to faster, cheaper models like Haiku."
Those three names map to Anthropic's model tiers: Haiku is the fastest and cheapest, Sonnet the mid tier, Opus the most capable and most expensive. Routing is picking the right rung per task. Across 31 agent files the distribution is five Haiku, twenty-one Sonnet, five Opus. The Opus five are api-architect, chief-architect, cloud-architect, pentest-agent and security-auditor. Every one of them is a role where being wrong is expensive and the error is silent. Not one implementer is Opus.
The cost ceiling and the danger ceiling get set together, per file, in five lines of YAML. Compare the cheapest thing in the estate against the most dangerous:
---
name: stuck
description: Use this agent whenever any other agent hits a problem it cannot resolve independently — an ambiguous requirement, a conflict between two valid approaches, a decision with significant cost implications, a security concern, or anything that requires human judgment. Use immediately rather than guessing.
tools: Read
model: haiku
maxTurns: 3
---
---
name: pentest-agent
description: Use this agent to actively probe a running application for security vulnerabilities before any public launch or major release. Requires a LOCAL or STAGING URL — never a production URL without typed confirmation. Use after security-auditor (static) and verify-app (E2E) have passed. Run proactively as the final gate in the pre-launch checklist.
tools: Bash, Read, WebFetch, Grep, Glob
model: opus
permissionMode: plan
maxTurns: 50
---
stuck is the escalation path, and it is the cheapest thing running: Haiku, three turns, read-only. Read-only is deliberate. An agent whose job is to hand a decision to a human should have no way of taking that decision itself. pentest-agent gets Opus, fifty turns and permissionMode: plan on top of its allowlist. maxTurns buys you a cost fuse, and I have never regretted setting one low.
What broke: the checkpoint was never wired
Here is the part the incumbent guides will never write, because they are documenting a shape rather than living inside one.
agent-audit-preflight.py exists on disk, carries 20 test functions, and my constitution calls it a "Global PreToolUse hook". Nothing in ~/.claude/settings.json mentions it. Nor the sibling preflight beside it.
A hook lives in a settings file. What I had on disk was a Python script with a good docstring. The gate sat there, tested and inert, while the document describing it kept asserting it was live. Nothing in the estate detects this, because the hooks layer has no test for its own registration and no CI. One surface would have caught it in a keystroke. The hooks guide says "Type /hooks to open the hooks browser. You'll see a list of all available hook events, with a count next to each event that has hooks configured." An empty count next to PreToolUse is the whole finding, and I never typed the command.
So the honest version of this chapter's thesis runs longer than the slogan. Agents are voluntary and hooks are not, but a hook stays non-voluntary only while it is wired, and the wiring is a fact about a JSON file rather than a property of the code you wrote. Verify the registration. On the machine that ships this course, grep -c preflight ~/.claude/settings.json still returns 0.
Orchestration you own
The other half of the honesty is knowing when a framework is the wrong answer. When my editorial pipeline needed an Opus rewrite layer, the general-purpose orchestrator was sitting right there and I skipped it. What shipped instead was an osascript harness the project owns outright, described in the one-harness-two-consumers article as "twenty-odd lines" with "No persistent daemon. No message broker. The harness is stateless between invocations."
Whether that was the right call became answerable only later, and the article sets the test in its own words: "The thesis generator was the first proof. It worked. That was a necessary condition, not a sufficient one. The sufficient condition was a second consumer that arrived independently and used the harness without modification." A second consumer, architecturally unrelated to the first, did exactly that, and the harness did not change to accommodate it. The same piece files the bill in the same breath: "The harness is also untested under load. It works for single invocations against one file."
Orchestration is a shape you discover. For a solo system that shape comes out smaller than the framework built for a team. Mine is twenty-odd lines and one AppleScript call.
The transferable rule
Parallel subagents are the cheap part. Spawning three reviewers in one message costs you five minutes of YAML and buys real independence, because the isolated context window that makes a subagent expensive is the same property that makes its opinion worth having.
The expensive part is the checkpoint. A review checkpoint counts as one only if something other than the reviewer decides when it fires. Put the trigger in a PreToolUse hook, return 2, and print the fan-out instructions to stderr so the blocked model knows how to satisfy you. Give it a named bypass token so a human can always get through in an emergency, and log the bypass so you can count them later.
Then go and check the hook is registered. I did not, for weeks, and the document that told me it was live was a document I wrote.
The next chapter leaves the local machine and looks at what changes when the tools your agents reach for live behind a protocol instead of a file.
Gate your own coder-as-tester bias
Find one place in your workflow where the same agent both produces work and certifies it, such as a migration and its rollback or copy and its style check, then build the checkpoint this chapter describes: a PreToolUse hook that detects the risky combination from observable state, blocks with exit 2, and prints the reviewer fan-out to stderr so the blocked model knows how to satisfy you.
Expected behaviour
- A PreToolUse hook that reads observable state, such as the staged file list, and returns 2 only when the risky combination is present
- Stderr that hands the blocked model its instruction set: which reviewer agents to spawn in one message, and how to re-run with the bypass token
- Reviewer agent definitions whose tools lists carry no Write or Edit, so filing a report is the only move available to them
- The hook registered in settings.json and visible in /hooks with a non-zero count next to PreToolUse
- The reviewer names in the stderr text checked against your actual agent roster, so the message and the estate cannot drift apart
PROVE IT Stage a violating change and attempt the commit. Paste the block, spawn the reviewers it names in one message, then re-run with your bypass token and show the token sitting in your shell history.
Why do three reviewers spawned in parallel give you independence rather than merely speed?
According to the cost docs quoted in this chapter, agent teams use approximately how many times more tokens than standard sessions when teammates run in plan mode?
agent-audit-preflight.py sat on disk with 20 test functions and blocked nothing for weeks. What was actually missing, and which single surface would have exposed it?
Show answer
The script was never registered in settings.json, and a hook only exists as a hook once its settings entry does; until then it is a Python script with a good docstring. The /hooks browser shows a count next to each lifecycle event, so an empty count beside PreToolUse would have exposed the gap in one keystroke. The wiring is a fact about a JSON file, never a property of the code.
↺ re-read: “What broke: the checkpoint was never wired”