Skip to content

09 · Who Gets to Veto an Agent Diff

Give an independent reviewer bounded tools and a contract for actionable findings

Chapter brief

Question to answer

When the generating agent cannot prove correctness, how should an independent reviewer veto a diff and return actionable findings?

By the end, you can

  • Separate generator, reviewer, and final adjudicator roles
  • Define evidence, severity, location, and remediation for each finding
  • Avoid reviewer rewrites, repeated noise, and loops with no exit
Read this now if
Engineers designing automated review, two-agent loops, or merge gates
Prerequisites
Understand diffs, test signals, and reviewer permission isolation
Deliverable
A reviewer output schema, severity rubric, and merge gate
Evidence boundary
Without a reliable oracle, both models may miss the same class of error

Review must be independent from generation

Section titled “Review must be independent from generation”

Scenario: the generating agent immediately reviews its own diff in the same context. It already spent 20 turns defending the design, so the review prompt inherits the same assumptions and checks style instead of architecture, missing tests, and failure paths.

Passing conditions: the reviewer reads an explicit base/head in an independent context; tools are read-only by default; every finding includes location, evidence, severity, impact, and a verifiable remediation; “no findings” still states coverage and blind spots.

Four review paths: dual-agent vs prompt-as-command vs remote pipeline vs no native build
Four review models coexist: constrained sub-agent, in-context prompt directive, remote bug-hunter, external plugin hook.

Coverage on four review responsibilities:

Dimension CodexClaude CodeOpenClawHermes
Review trigger `/review` command + 4 targets (uncommitted / base-branch / commit / custom) + guardian auto-audit`/review <PR>` local prompt + `/ultrareview` remote entryNot built-in; bring your own plugin or slash commandNot built-in; model runs `git diff` via terminal
Reviewer shape A sub-agent thread (`run_codex_thread_one_shot`) with its own system promptSame conversation; just a long prompt injected into contextRemote CCR session (the GitHub app clones and runs the repository, then writes back; measure duration for the target repository, pipeline, and model)No dedicated reviewer
Reviewer constraints Disables web_search / SpawnCsv / Collab / MultiAgentV2; `AskForApproval::Never` prevents recursive approvalGoes through BashTool to `gh pr`; no special constraintsRemote sandbox + quota + billing gateNone
Output handling `parse_review_output_event` parses structured findings; `ExitedReviewModeEvent` ends the modeModel returns markdown in chatRemote task completion piped back via task-notificationFree-form chat output
Engineering depth of treating review as a separate agent

Source evidence: isolation, targets, and output

Section titled “Source evidence: isolation, targets, and output”

Codex · Independent sub-agent runs review + 4 parameterized targets + guardian dual layer

Section titled “Codex · Independent sub-agent runs review + 4 parameterized targets + guardian dual layer”

Review and code generation pull in opposite directions: one looks for faults, the other tries to finish a task. Codex starts a separate thread, disables web search, delegation, and approval UI, then chooses a prompt for the requested diff.

The main conversation’s goal is “help the user complete their needs” (with bias, seeing full history, allowed any tool), and the review’s goal is “make an independent critical judgement” (no bias, no history, only the current diff, no state-changing tools).

If the same agent has to flip between the two roles, the model instinctively biases toward “defending what I just did”: it has seen too much context and knows the “why” behind each change, making it hard to critically look at the diff.

So Codex makes review a fully independent sub-agent from the start, with a clean context and a restricted capability set.

Concretely: when the user triggers review (via /review command or API), Codex doesn’t continue in the main conversation but instead spawns a brand new codex thread (the review sub-agent), with its config carefully trimmed: all unneeded features disabled, the reviewer-specific system prompt installed, and approval policy forced to Never:

Codex codex/codex-rs/core/src/tasks/review.rs:94-138 Reviewer sub-agent: dangerous features off + AskForApproval=Never + dedicated system prompt
async fn start_review_conversation(
session: Arc<SessionTaskContext>,
ctx: Arc<TurnContext>,
input: Vec<UserInput>,
cancellation_token: CancellationToken,
) -> Option<async_channel::Receiver<Event>> {
let config = ctx.config.clone();
let mut sub_agent_config = config.as_ref().clone();
// Carry over review-only feature restrictions so the delegate cannot
// re-enable blocked tools (web search, collab tools, view image).
if let Err(err) = sub_agent_config
.web_search_mode
.set(WebSearchMode::Disabled)
{
panic!("by construction Constrained<WebSearchMode> must always support Disabled: {err}");
}
let _ = sub_agent_config.features.disable(Feature::SpawnCsv);
let _ = sub_agent_config.features.disable(Feature::Collab);
let _ = sub_agent_config.features.disable(Feature::MultiAgentV2);
// Set explicit review rubric for the sub-agent
sub_agent_config.base_instructions = Some(crate::REVIEW_PROMPT.to_string());
sub_agent_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
let model = config
.review_model
.clone()
.unwrap_or_else(|| ctx.model_info.slug.clone());
sub_agent_config.model = Some(model);
(run_codex_thread_one_shot(
sub_agent_config,
// ...
SubAgentSource::Review,
/*final_output_json_schema*/ None,
/*initial_history*/ None,
)
.await)
.ok()
.map(|io| io.rx_event)
}

Three details in this config code each solve a specific concrete problem. The first is setting web_search to Disabled: reviewer should not be searching the web for docs, otherwise when it sees an unfamiliar API call it will instinctively go research “what does this API do,” several minutes of review time wasted on web search and the review drifting off-topic; forcing Disabled means reviewer can only judge from the diff itself.

The second is disabling the SpawnCsv / Collab / MultiAgentV2 trio: these are all “this agent can spawn more sub-agents” capabilities; if left on the reviewer could spawn reviewer-of-reviewer creating recursion, exploding tokens and latency; disabling these keeps reviewer to “single agent + tool calls.” The third is AskForApproval::Never: reviewer running commands (e.g. git diff, git log) should not pop approval dialogs, because the user’s attention is on the main agent and being interrupted by reviewer approvals is very annoying;

Never means reviewer never pops approvals, and if approval is needed it must throw an error to let the main agent handle it (reviewer is typically read-only and shouldn’t need approval).

The review_model field deserves a special mention: it allows reviewer to use a model different from the main agent.

The main agent and reviewer can use different models to meet separate quality, latency, and price targets. A cheaper reviewer is only cheaper if it still meets the finding and false-positive bar. The later 5-20K range is a capacity estimate in this design, not a published Codex measurement.

This design reflects the “different tasks deserve different cost models” mindset.

Review target is the user’s “what should I review?” parameter. Codex provides 4 targets, each with its own parameterized prompt template:

Codex codex/codex-rs/core/src/review_prompts.rs:15-37 Four review-target prompts
const UNCOMMITTED_PROMPT: &str = "Review the current code changes (staged, unstaged, and untracked files) and provide prioritized findings.";
const BASE_BRANCH_PROMPT_BACKUP: &str = "Review the code changes against the base branch '{{branch}}'. Start by finding the merge diff between the current branch and {{branch}}'s upstream e.g. (`git merge-base HEAD \"$(git rev-parse --abbrev-ref \"{{branch}}@{upstream}\")\"`), then run `git diff` against that SHA to see what changes we would merge into the {{branch}} branch. Provide prioritized, actionable findings.";
const BASE_BRANCH_PROMPT: &str = "Review the code changes against the base branch '{{base_branch}}'. The merge base commit for this comparison is {{merge_base_sha}}. Run `git diff {{merge_base_sha}}` to inspect the changes relative to {{base_branch}}. Provide prioritized, actionable findings.";
const COMMIT_PROMPT_WITH_TITLE: &str = "Review the code changes introduced by commit {{sha}} (\"{{title}}\"). Provide prioritized, actionable findings.";
const COMMIT_PROMPT: &str = "Review the code changes introduced by commit {{sha}}. Provide prioritized, actionable findings.";

Each of the four targets maps to a clear use case. UNCOMMITTED is “I just made changes, take a look”: staged + unstaged + untracked all included, fastest result, suitable for local pre-review before PR submission.

BASE_BRANCH is “how much does this branch differ from the main branch”: classic PR-review precursor, letting reviewer see “if I open this PR now, what changes would the main branch absorb”; here a performance optimisation deserves dedicated explanation: the template has two versions.

BASE_BRANCH_PROMPT is the “main agent already computed merge_base_sha” version, directly handing the SHA to reviewer so reviewer can git diff {{merge_base_sha}} in one line;

BASE_BRANCH_PROMPT_BACKUP is the “main agent has not computed merge_base” backup, asking reviewer to run git merge-base HEAD upstream itself. Why split into two versions?

When the caller already knows the merge base, passing its SHA saves the reviewer one tool call; otherwise the reviewer computes it. The source provides no large-monorepo timing benchmark, so measure whether precomputation matters in the target repositories.

COMMIT_PROMPT is “review one specific commit”, used for historical retrospection; COMMIT_PROMPT_WITH_TITLE is the variant that also includes commit title (if the caller knows the title, pass it to reviewer for more context).

Codex’s second review system is guardian/review.rs, which is entirely different from code review: guardian does not review code changes, it reviews the dangerous actions the main agent is about to run.

Concretely: the main agent wants to run rm -rf /tmp/cache, which by rights should pop an approval dialog for the user; but the user may be having coffee or doing something else, and seeing approval dialogs every time is annoying.

So a guardian sub-agent looks first: given {tool, args, cwd, sandbox_state} context, it calls the model to judge “how risky is this command” and returns an allow / deny / timeout three-state decision.

If guardian deems risk low (e.g. rm -rf /tmp/cache is in a temp dir and the cache looks irrelevant), it auto-allows without disturbing the user; if it judges high risk, it denies and reports the reasoning to the main agent.

Two special guardian instructions deserve careful reading:

Codex codex/codex-rs/core/src/guardian/review.rs:45-58 Guardian, agent-reviewing-agent, with rejection + timeout instructions
const GUARDIAN_REJECTION_INSTRUCTIONS: &str = concat!(
"The agent must not attempt to achieve the same outcome via workaround, ",
"indirect execution, or policy circumvention. ",
"Proceed only with a materially safer alternative, ",
"or if the user explicitly approves the action after being informed of the risk. ",
"Otherwise, stop and request user input.",
);
const GUARDIAN_TIMEOUT_INSTRUCTIONS: &str = concat!(
"The automatic permission approval review did not finish before its deadline. ",
"Do not assume the action is unsafe based on the timeout alone. ",
"You may retry once, or ask the user for guidance or explicit approval.",
);

The first block, GUARDIAN_REJECTION_INSTRUCTIONS, is Codex’s explicit defense against agent-side jailbreak.

The concrete attack pattern: main agent wants to rm -rf /important, guardian denies it; main agent gives up but maybe thinks “let me get the same result a different way” (e.g. switch to find /important -delete, or cd /important && rm -rf .); if guardian doesn’t recognise these as workarounds, they bypass the denial.

So Codex writes into guardian’s prompt explicitly “agent must not attempt to achieve the same outcome via workaround, indirect execution, or policy circumvention,” so when guardian denies it also denies all variants that achieve the same effect, not just the literal command.

This is encoding a real engineering lesson directly into the prompt, very detailed.

The second block, GUARDIAN_TIMEOUT_INSTRUCTIONS, handles guardian itself failing: guardian calling the model API may timeout due to network issues (default 5 seconds); if the default is “timeout = unsafe” the system would be too conservative, denying every action; this block tells the main agent “timeout does not mean unsafe; you may retry once, or ask the user directly.” Beyond these two, guardian also has a circuit breaker: three consecutive denials in a row triggers a 30-second pause, preventing a model glitch from denying everything and freezing the main agent.

Claude Code · Local prompt-as-command + remote ultrareview dual track

Section titled “Claude Code · Local prompt-as-command + remote ultrareview dual track”

A local review optimises for feedback speed. Claude Code’s prompt-as-command runs gh pr view and gh pr diff; ultrareview moves deeper analysis to a remote service. The two paths share input, not execution context.

The two paths share no code, because their needs are completely different.

The local version is very simple, pure prompt-as-command:

Claude Code claude-code/src/commands/review.ts:9-31 /review is a written prompt; no separate reviewer agent
const LOCAL_REVIEW_PROMPT = (args: string) => `
You are an expert code reviewer. Follow these steps:
1. If no PR number is provided in the args, run \`gh pr list\` to show open PRs
2. If a PR number is provided, run \`gh pr view <number>\` to get PR details
3. Run \`gh pr diff <number>\` to get the diff
4. Analyze the changes and provide a thorough code review that includes:
- Overview of what the PR does
- Analysis of code quality and style
- Specific suggestions for improvements
- Any potential issues or risks
Keep your review concise but thorough. Focus on:
- Code correctness
- Following project conventions
- Performance implications
- Test coverage
- Security considerations
Format your review with clear sections and bullet points.
PR number: ${args}
`

The cited file is mostly an approximately 25-line prompt definition: the user types /review 123, Claude Code places LOCAL_REVIEW_PROMPT(123) in context, and the next model turn runs gh pr view, gh pr diff, and the analysis. That shows a thin entrypoint, not zero evaluation, error-handling, or output-contract work.

Several engineering benefits flow from “the command itself doesn’t call code, just injects a prompt.” First, the entrypoint is small: the cited /review prompt needs no separate wrapper for gh calls and parsing. Delivery effort still includes evaluation, error handling, and an output contract, so source line count does not establish a fixed speedup.

Claude Code uses the same prompt-as-command shape for /review, /refactor, /test, /docs, and /explain; their source size is an implementation detail, not a delivery estimate.

Second, users can see and modify: Claude Code’s slash command source is itself a markdown file in .claude/commands/, users can open it, see the prompt, and modify it.

Third, a prompt-as-command may benefit from a model upgrade without changing the prompt. Whether review quality improves must be checked on the same diff set and rubric; a new model can also change format adherence or false positives.

Fourth, cross-tool composition: one prompt sentence tells the model “use gh pr view → gh pr diff → analyze,” and the model itself composes the tool calls, far simpler than writing a TypeScript function that wraps gh CLI itself.

The cost is also clear: the local version has no reviewer role isolation. The model is in the same conversation context (has seen all main agent history), with the same feature set (web_search, sub-agent nesting all enabled), with the same approval policy.

If a user’s review standards require strict critical judgement (e.g. “is this code secure”), the local version might be too compromised by historical context to be neutral.

This is the fundamental limit of prompt-as-command: you can only tweak prompt content, not agent configuration.

The remote version is the opposite: it treats review as an independent product, runs in Anthropic’s remote sandbox:

Claude Code claude-code/src/commands/review/reviewRemote.ts:1-32 /ultrareview remote pipeline design notes
/**
* Teleported /ultrareview execution. Creates a CCR session with the current repo,
* sends the review prompt as the initial message, and registers a
* RemoteAgentTask so the polling loop pipes results back into the local
* session via task-notification. Mirrors the /ultraplan → CCR flow.
*
* TODO(#22051): pass useBundleMode once landed so local-only / uncommitted
* repo state is captured. The GitHub-clone path (current) only works for
* pushed branches on repos with the Claude GitHub app installed.
*/

The comments above have several key design points worth expanding. “Teleport” is a Claude Code abstraction: it allows a local session to migrate state to a remote CCR session, run there, and migrate results back.

The user’s experience is “I typed /ultrareview locally and the remote result later appeared in my local chat.” A RemoteAgentTask is registered into the local polling loop; when the remote task completes, task-notification writes results back into the local session. Duration depends on the repository, pipeline, and model; the source comment gives no fixed SLA.

CCR (Claude Code on the Web) is a remote agent runtime specifically designed for tasks beyond local capacity (see chapter 18 on cron and background tasks).

The remote version can detach a long-running pipeline from the local interactive lifecycle. Several agents can inspect security, performance, correctness, and style before aggregating structured findings. Actual runtime and willingness to wait locally need measurement; the source does not guarantee a fixed duration.

Second, the environment is more consistent: a remote sandbox can pin the base image, git version, and lint config. Model sampling, external services, and repository state can still change the result, so this is not full reproducibility.

Third, independent billing: /ultrareview runs through quota / billing checks (see ultrareviewQuota.ts), free quota exhausted triggers an overage dialog requiring confirmation before charging, team / enterprise users go through directly; this monetization mode is only possible remote, locally you can’t bill anyone.

Fourth, CI integration: remote review is essentially a cron job, can subscribe GitHub webhooks so PR open auto-runs review.

There are costs: the remote version depends on push branch (GitHub app clones repo via git clone, can’t see locally uncommitted code), which is why the comment has TODO(#22051) saying they need useBundleMode to land for local code upload; the remote version needs network (offline users cannot use it); the remote sandbox sees all code, enterprise-sensitive scenarios may have privacy concerns.

OpenClaw · Don’t build review in, make it a hook in the plugin pipeline

Section titled “OpenClaw · Don’t build review in, make it a hook in the plugin pipeline”

Review policy varies by organisation, so a platform should leave room for local definitions. OpenClaw exposes an after_tool_call hook in its tool-policy pipeline; teams decide when to trigger review and how to consume findings.

Some teams define it as PR review (look at diff, give improvement suggestions), some as commit review (look at historical commits, find regressions), some as lint review (auto-run lint tools, aggregate reports), some as security review (specifically look for injection, auth holes), some as business-logic review (check whether code matches business rules); these “review” workflows share only the meta pattern “let another agent check,” but the implementation details are completely different.

So OpenClaw chooses not to build in any specific review implementation, but to provide hooks so users define it themselves in plugins.

The practical pattern is to attach a reviewer middleware to the after_tool_call hook in tool-policy-pipeline.ts; see Tool System.

Whenever the model finishes editing a file via fs.write, the middleware receives { tool_name, args, result }, runs a user-defined reviewer prompt (perhaps “check whether this change broke any existing tests,” “check whether this code follows company coding standards,” or “check whether SQL injection was introduced”), and the result is injected as a system message into the main agent’s context next turn.

This “review as pipeline side effect, not explicit command” model fits enterprise customization much better: teams can define any review logic according to their business, without conforming to a platform’s “standard review flow.”

OpenClaw’s choice here is completely consistent with its overall positioning: it is an agent control plane, not a coding tool; any hardcoded implementation of “review” (a function with highly variable business definition) would be wrong, and flexibility should be left to the user.

Hermes · Don’t abstract review at all, let the model run it through terminal

Section titled “Hermes · Don’t abstract review at all, let the model run it through terminal”

Hermes has no reviewer entry point. The user asks the model to run git diff and comment in the main conversation. It is cheap to ship, but the generator and reviewer have no boundary.

To review, the user directly tells the agent “look at the code I just changed and see if there are issues,” and the agent uses terminal_tool to run git diff to get the changes, then outputs review content in the main conversation.

This model’s benefit is zero infrastructure: no reviewer agent to maintain, no prompt templates to maintain, no review target parameterization to think about.

The cost is also large: review quality depends entirely on user prompts and the model’s attention: with no constrained system prompt locking the model into reviewer role, the model may drift into “let me fix it for you” (directly editing rather than just commenting); with no structured output format, review results may be markdown bullets one turn and prose paragraphs the next inconsistently; with no 4-target parameterization, users must figure out “what to review” and tell the agent accurately.

For Hermes’s positioning (multi-platform chat agent where code review is not a core scenario), this is reasonable: most users talking to Hermes are doing daily affairs, not code review, so forcing a reviewer abstraction on every user is unnecessary complexity.

But if a user really wants to use Hermes for serious code review, they have to write a full prompt + repeatedly align agent behaviour, far less efficient than using Codex directly.

The four systems differ greatly in review engineering depth (Codex two-layer sub-agents vs Hermes zero infrastructure), but if review is built at all, three things are consensus.

The first decision is whether review needs an explicit reviewer role. Codex uses an independent sub-agent plus REVIEW_PROMPT, Claude Code local opens with “You are an expert code reviewer,” and OpenClaw leaves the reviewer prompt to users. Hermes’s path has no dedicated reviewer configuration. The source establishes missing role isolation and output constraints, not lower review quality; compare misses, false positives, and format stability on the same diff set and rubric.

The second is that the final product of review should be structured findings. Codex uses parse_review_output_event to parse reviewer output into structured {findings: [{severity, file, line, message, suggestion}]} lists, which downstream IDEs can use directly for inline highlights; Claude Code’s prompt explicitly demands “Format your review with clear sections and bullet points” to ensure at least clean markdown; OpenClaw lets users define output schema. Structured output’s benefit is machine-consumability: it can feed IDE highlights, feed the next agent turn for auto-fix, and aggregate across multiple reviews for statistics. If output is free-form prose paragraphs (Hermes default), downstream can only show it to humans, and the agent automation path is broken.

The third is reviewer should not pop approval dialogs when running commands. Codex explicitly sets AskForApproval::Never, Claude Code local lets reviewer only use BashTool to run read-only commands, OpenClaw lets users control the tool set in hooks; all follow the “reviewer is read-only” principle. The consideration is UX: the user’s attention is on the main agent, being interrupted by reviewer approvals is very annoying; if reviewer truly needs approval (meaning it wants to do side-effecting operations), then it should not be reviewer, it should be a different agent.

Four code reviews on a 2D plane: reviewer isolation × cost to onboard
Hermes / Claude Code local sit top-left (lightweight but same conversation); Codex sub-agent and ultrareview sit bottom-right (independent agent, deep engineering).

“How deep review should go” is the real determinant of which approach fits which scenario. From “what kind of agent are you building,” each trade-off maps to a different product positioning.

If you are building a coding-first agent and need serious review, Codex’s sub-agent model is a useful starting point. Constrained independent config (disabling web_search, SpawnCsv, Collab, MultiAgentV2; AskForApproval=Never) + 4 parameterized targets (uncommitted, base-branch, commit, custom) + an optional independent model (review_model field) + structured output (parse_review_output_event). The cost is high abstraction overhead; compare misses, false positives, tokens, and latency on a fixed diff set before deciding whether it pays back.

If your agent already runs commands and you only want a convenient review entry point, Claude Code’s local /review is a lightweight starting point. The cited implementation is mostly a prompt string; validation, evaluation, error handling, and an output contract still determine delivery effort. The reviewer has no role isolation, which may be acceptable when review is one of many functions rather than the product’s core.

If you want deep review through a bug-hunter pipeline, study Claude Code’s /ultrareview remote model. Treat review as an independent product: a remote sandbox can run security, performance, correctness, and style checks in parallel, with quota, billing, and GitHub webhook integration. The cited material does not establish a portable duration; measure wait time, resource use, and whether users need the local agent during the run. A smaller scope or managed runtime may be a better fit than reproducing the whole stack.

If you are building an enterprise internal agent platform where review rules vary by team, study OpenClaw’s “don’t build it in, make review a plugin pipeline hook” model as one option. Let teams define what “review” means; the platform only provides hooks and orchestration. The cost is low out-of-the-box: users must write their own reviewer middleware, which can burden small teams.

If code review is not a core use case (for example, a chat or customer-support agent), Hermes’s no-dedicated-reviewer path can fit that scope. It avoids a reviewer abstraction; if serious review later matters, add a prompt, output contract, and benchmark. The source comparison itself does not establish quality.

There is no star rating here. First decide whether review needs a separate context, then choose the amount of orchestration.

ConstraintStart withCost or boundary
Code changes need a second view and stable findingsCodex isolated thread, bounded tools, and target promptsOne more model call; approval and result contracts need care
You only need a quick local PR entry pointClaude Code /review prompt-as-commandThe reviewer shares context with the generator
Each team defines its own review policyOpenClaw after_tool_call hookTeams must author prompts and result handling
Code review is outside the product’s scopeHermes terminal pathNo dedicated reviewer or output schema

Fix the reviewer’s input, output, and rejection contract first; use a reproducible diff set to decide whether an isolated sub-agent is warranted.

Build recipe

最小可行

  • Start with a fixed prompt (borrow from Claude Code's local /review): user inputs PR number / commit SHA / diff, model itself runs gh pr view / gh pr diff / gh pr comments three steps to gather context then starts review; this is the simplest implementation, single agent runs
  • Force the reviewer to emit five fixed sections (overview / quality / suggestions / risks / coverage): structured output lets users / downstream tools stably parse; forcing each section to be filled prevents the reviewer from being lazy and only checking one or two dimensions
  • Split review from fixing into two turns: reviewer outputs findings only ("here's a problem, that needs changing"), next turn lets main agent fix based on findings; separation prevents reviewer from being "tempted to quickly fix" and ignoring real design flaws

进阶

  • Extract reviewer into standalone sub-agent (borrow from Codex's run_codex_thread_one_shot): sub-agent has its own trajectory + own prompt + own tool set, completely isolated from main agent; this is the key to "reviewer truly thinks independently rather than echoing main agent"
  • Constrained config: disable web_search / SpawnCsv / Collab / recursive sub-agents; AskForApproval=Never: reviewer shouldn't web-search (easily distracted by news), shouldn't spawn its own sub-agents (recursion bottomless), shouldn't prompt for approval (interrupts UX)
  • Dedicated reviewer system prompt (borrow from Codex's REVIEW_PROMPT) independent from main agent prompt: main agent prompt is "do things proactively", reviewer prompt is "think critically"; mixing causes reviewer to lose critical perspective
  • Offer 4 targets (borrow from Codex's ReviewTarget): uncommitted (local uncommitted changes) / base-branch (all changes vs main) / commit (specified commit SHA) / custom (user-defined); different scenarios need different scope
  • Let reviewer use cheaper model (borrow from Codex's review_model field): main agent uses GPT-5, reviewer uses mini or Haiku; reviewer task is relatively simple (input diff output findings), cheap model suffices; saves cost
  • Add a guardian agent-audits-agent layer (borrow from Codex's guardian/review.rs): risk level + timeout + circuit breaker triplet; reviewer running long / output looking weird / giving high-risk assessments, guardian intervenes
  • Structured output (borrow from Codex's parse_review_output_event): make reviewer emit JSON schema-compliant results (each finding has severity / file / line / suggestion fields), downstream tools (CI / dashboard / Slack bot) can consume

一开始别做

  • Don't let reviewer share the main agent's conversation history: reviewer seeing "what main agent thought before" actually biases toward "the direction already taken" (cognitive anchoring bias); reviewer should "cold start" looking only at code
  • Don't let reviewer modify code: reviewer outputs findings only, fixing is main agent's next turn; letting reviewer modify = letting referee play, losing independence
  • Don't let reviewer spawn its own sub-agents: forms reviewer-of-reviewer-of-reviewer recursion bottomlessly; hard-limit sub-agent from re-spawning
  • Don't use reviewer to replace lint / tests: reviewer is supplement not replacement; CI's hard verifier (lint / test / type check) must still run, reviewer only looks at "human-perspective issues"
  • Don't forget reviewer's approval_policy must be Never: reviewer prompting for approval = UX broken (user expects reviewer to run automatically, popup interrupts flow)
Four review models side by side
Codex runs a dual-layer sub-agent (code review + guardian); Claude Code injects a prompt locally / runs a pipeline remotely; OpenClaw hooks the tool policy; Hermes lets the model use terminal directly.

Lining them up shows the progression of “another agent checks this agent”: from same-context prompt, to standalone sub-agent, to remote pipeline, to pipeline hook. Depth grows monotonically.

Where to verify reviewer boundaries and output

Section titled “Where to verify reviewer boundaries and output”

What to carry forward and the next experiment

Section titled “What to carry forward and the next experiment”

Review earns value through independent context, bounded authority, and structured evidence—not a second free-form generation. A reviewer should not casually rewrite code or block merge with comments that lack location and impact.

Next experiment: seed known defects into ten diffs and compare generator self-review, an independent thread with the same model, and a different model. Record precision, recall, duplicate findings, invalid severities, tokens, and human confirmation time. Two-agent review is worthwhile only when the independent path improves useful recall at an explainable cost.

Open the exercises and ten review questions
  1. 🟢 Write a /review prompt. Five fixed sections (overview / quality / suggestions / risks / coverage), forcing the model into reviewer mode in the main conversation. Verify: same diff produces the same output shape twice.
  2. 🟠 Standalone reviewer sub-agent. Use a framework (LangGraph / openai-agents) to spin a separate thread. Constrained config (cannot spawn sub-agents, cannot modify files). Parameterize review target (uncommitted / branch / commit / custom).
  3. 🟠 Structured findings. Have the reviewer emit JSON schema: {findings: [{severity, file, line, message, suggestion}]} so downstream IDE highlights can consume.
  4. 🔴 Agent-audits-agent. Build a guardian middle layer: when the main agent wants to run a tool, send {tool, args} to a guardian sub-agent for a risk level + decision. Add 5s timeout + circuit breaker after 3 consecutive denials.
Q1 · Concept: Why is Codex’s reviewer a standalone sub-agent rather than just a prompt in the main conversation?

Making review a sub-agent solves three specific problems that a same-conversation prompt cannot.

1. Context pollution

The main agent has already run 30 turns of conversation; context is full of “I just grepped for X, I changed file Y, here’s why.” Asking it to switch to reviewer mode means the model instinctively defends its own implementation: it has seen too much context and knows every change’s “motivation.” A reviewer needs critical distance.

A standalone sub-agent gets a clean context, sees the diff only, no history. This is the largest single factor in review quality.

2. Feature isolation

The main agent may have web_search / SpawnCsv / Collab / MultiAgentV2 enabled. Reviewer shouldn’t use any of them: its task is read the diff, not search the web (drifts), not spawn more sub-agents (recursion), not collab with unrelated agents (off-topic).

Codex’s sub-agent config explicitly .disable(Feature::SpawnCsv) etc.

3. Approval isolation

If the main agent’s approval_policy is on-failure or on-request, reviewer running tools triggers approvals.

Problem: reviewer prompts approval → user approves → reviewer continues → next prompt… user’s attention is meant for the main agent, hijacked by reviewer.

Codex sets AskForApproval::Never so reviewer never prompts; if approval is needed, raise to main agent.

Claude Code local /review solves none of these because the reviewer shares main agent context. That’s why Anthropic built /ultrareview: to put review in a remote standalone session, back to the sub-agent design.

Engineering choice: when a reviewer needs model-context, feature-set, or approval isolation from the main agent, evaluate a standalone sub-agent. If several boundaries are needed at once, a sub-agent, constrained worker, or remote job may all fit; choose from bias, permission, token, and latency measurements rather than treating one design as the only option.

Source: codex/codex-rs/core/src/tasks/review.rs:94-138 (the start_review_conversation function + the three feature disables).

Follow-up: “Sub-agent adds tokens. Worth it?” Measure it first. The 5-20K turn range and the extra system-prompt estimate are design estimates here. Run the same diffs with shared and isolated context, then record tokens, latency, hit rate, and false positives before choosing.

Q2 · Architecture: Codex maintains both a “code review” and a “guardian approval review” sub-agent. Over-engineered?

Whether this is over-engineering depends on whether the two tasks actually have different inputs, budgets, and output contracts. The pinned Codex snapshot separates them into two pipelines:

Code review reviewer:

  • Trigger: user /review
  • Input: a diff
  • Output: findings list (structured issues + suggestions)
  • Time: depends on diff size, model, and tool calls; measure it on a target task set
  • Key constraint: standalone context, avoid main agent contamination

Guardian reviewer:

  • Trigger: main agent wants a risky command
  • Input: tool name + args + current cwd / sandbox state
  • Output: allow / deny / timeout (three-state)
  • Time: use a bounded guardian decision budget calibrated on the target model and network
  • Key constraint: fast and breakable by circuit breaker

Common ground? Just the meta-pattern “another agent audits.” The specific prompts, timeouts, output shapes, constraints, and recovery strategies all differ. Forcing them into one reviewer means neither works:

  • A full code review may exceed the guardian’s single-decision budget
  • Guardian doesn’t need 4 targets

When code review and guardian work genuinely differ in budget, input, or output contract, keeping them separate is easier to verify. A shared meta-task does not require a shared implementation.

Guardian’s special designs:

  1. GUARDIAN_REJECTION_INSTRUCTIONS explicitly say “agent must not attempt to achieve the same outcome via workaround.” This is anti-reverse-jailbreak: main agent denied by guardian might think “let me do the same bad thing differently.” Codex bakes the defense into the prompt.
  2. GUARDIAN_TIMEOUT_INSTRUCTIONS say timeout ≠ unsafe. Guardian might just be slow because model API hung. Main agent shouldn’t auto-treat as unsafe.
  3. Circuit breaker: 3 consecutive denies trigger a 30s cooldown. Stops guardian bugs from blanket-denying everything.

This “agent audits agent” depth is unique to Codex among the four. Claude Code goes with permission mode + canUseTool hook (lighter, more policy + callback than full sub-agent).

Source: codex/codex-rs/core/src/guardian/review.rs:1-200.

Follow-up: “Why doesn’t Anthropic’s Claude Code do guardian?” Claude Code’s permission mode is user config (plan / acceptEdits / bypassPermissions); canUseTool is a sync callback. No “another agent audits” component. Anthropic’s call: in most scenarios policy + callback suffices; extra agent layer adds latency + token costs.

Q3 · Concept: What’s the relationship between REVIEW_PROMPT and BASE_BRANCH_PROMPT?

REVIEW_PROMPT is reviewer sub-agent’s system prompt: defines the reviewer’s role, output format, evaluation axes. Lives throughout the sub-agent.

BASE_BRANCH_PROMPT etc. are task prompts: the first user message that says “this time review this target.” One of four is picked per launch.

Traditional system + user analogy:

  • REVIEW_PROMPT ≈ “You are an expert code reviewer. Emit findings in 5 sections…” (role + rules)
  • BASE_BRANCH_PROMPT ≈ “Review the current branch vs base branch foo, merge base SHA is abc123” (concrete task)

Why two layers?

  1. Task templating: 4 review targets share the reviewer role but vary task params (base branch name, commit SHA, custom prompt). Templates can be parameterized.
  2. Target swap doesn’t require restart: theoretically one reviewer sub-agent can review multiple targets (Codex currently does only one) without re-init.
  3. A/B testable: change reviewer role only via REVIEW_PROMPT; change a target’s wording only via its template. Independent evolution.

The 4 specific targets:

  • UNCOMMITTED_PROMPT: current staged + unstaged + untracked. Fastest, good for “I just changed something, take a look.”
  • BASE_BRANCH_PROMPT: current branch vs base. Local pre-PR review.
  • COMMIT_PROMPT: a specific commit. For looking back at history.
  • CUSTOM_PROMPT: user-defined. Most flexible, easiest to drift.

Engineering philosophy: prompts are code: reusable, parameterizable, diffable. Codex puts review prompts in review_prompts.rs rather than inline in review.rs because more prompts (SECURITY_PROMPT, PERFORMANCE_PROMPT) are coming.

Source: codex/codex-rs/core/src/review_prompts.rs:1-130.

Follow-up: “What’s the difference between BASE_BRANCH_PROMPT and BACKUP?” The former gives reviewer a fixed merge_base_sha (main agent already computed it); BACKUP makes reviewer run git merge-base itself when the SHA wasn’t precomputed. Saves a tool call when possible.

Q4 · Concept: What is prompt-as-command and why does Claude Code use it heavily?

Prompt-as-command is “implement a command as a polished prompt.” User types /review 123, Claude Code injects LOCAL_REVIEW_PROMPT(123) into chat context, and the model runs gh pr view, gh pr diff etc. itself to complete review.

The command has no code, just prompt text injection.

Why use it heavily?

1. Extremely cheap to implement

The cited /review entrypoint is mostly a prompt string and does not separately wrap gh calls. That keeps the entry code small, but evaluation, error handling, and output contracts remain implementation work; line count does not prove a fixed delivery multiplier.

2. User-visible, user-editable, diffable

Claude Code’s slash commands are markdown files (in .claude/commands/). Users can open them, see the prompt, tweak it. Command-source-transparency beats black-box functions.

3. Compounds with model capability

A slash command may benefit from a model upgrade without a prompt change. Verify that claim on a fixed diff set and rubric because format adherence and false positives can also move.

4. Tool composition

/review prompt tells the model “run gh pr view → gh pr diff → analyze.” Model composes tool calls itself. If implementing as TypeScript, you’d write runGhPrView() / runGhPrDiff() / parseAndAnalyze(), each needs BashTool integration.

Prompt one-liner.

When NOT to use prompt-as-command:

  1. Strong consistency required: same output format / same SHA / same number every time; functions beat prompts.
  2. Side-effect sensitive: prompts let the model run commands; model can misroute. Use functions when side effects must be tight.
  3. Latency sensitive: prompt-as-command adds at least one model call. Its latency depends on model, context, and network, making it a poor fit for paths such as an IDE hover that require immediate response.

Claude Code uses prompt-as-command for fuzzy tasks (review / planning / docs) and functions for precise tasks (BashTool / FileEdit). Complementary modes.

Source: claude-code/src/commands/review.ts:9-31 (25-line LOCAL_REVIEW_PROMPT) + other commands in src/commands/.

Follow-up: “Does Codex use prompt-as-command?” Codex puts all review prompts as Rust string constants, but review itself is a sub-agent. Codex doesn’t treat “user-triggered prompt injection” as a slash command; it triggers a sub-agent. Different style, both fine.

Q5 · Engineering: /ultrareview runs remotely on CCR, not locally. Concretely, what’s the benefit?

/ultrareview isn’t just /review plus deeper; it treats review as its own product. Concrete benefits:

1. Doesn’t use user’s machine

A long local review competes for CPU, disk IO, and network with the interactive session. A remote review moves that work into a sandbox and returns a task notification; the actual duration and resource savings need measurement on the chosen pipeline.

2. Runs deep pipelines

The source describes a bug-hunter pipeline: several independent agents take dimensions such as security, performance, correctness, and style, with stages that may run for minutes. A local product can run the same shape, but it must measure the waiting, resource, and interaction budgets instead of treating it as impossible.

3. Narrower environment variance

A remote sandbox can pin the base image, git version, and lint config. Model sampling, external services, and repository state still vary, so this reduces one source of variance without making the run fully reproducible.

4. Independent billing

/ultrareview goes through quota and billing checks. Remote execution makes centralized usage metering natural, but local products can still charge through licenses, subscriptions, or metered API calls.

5. CI integration

Remote review can attach to a GitHub webhook and run when a PR opens. CI can also invoke a local or self-hosted reviewer; the difference is who owns the runtime, credentials, concurrency, and maintenance.

Downsides exist:

  • Needs pushed branch: remote pulls via GitHub app + clone; local uncommitted code invisible. The source TODO mentions “useBundleMode” for uploading local code.
  • Network dependency: offline users can’t use it; local /review still works.
  • Privacy concerns: remote sandbox sees all code; enterprise sensitive code may resist. Anthropic offers enterprise deployment options to mitigate.

Remote is not inherently better. It can separate resource use and monitoring from the user’s machine when a review is long-running, compute-heavy, or centrally audited. Uncommitted code, offline use, and data-residency constraints can make local execution the better fit.

Source: claude-code/src/commands/review/reviewRemote.ts:1-100 (teleport entry) + src/services/api/ultrareviewQuota.ts + src/utils/teleport.ts.

Follow-up: “Does Codex have anything like ultrareview?” Codex’s app-server exposes APIs to the frontend, but review itself is still local sub-agent. No “remote deep pipeline.” OpenAI’s product positioning is CLI; SaaS path not taken.

Q6 · Practical: Your agent needs code review. Implement a minimum viable version from scratch.

Four scopes: prompt → sub-agent → structured → workflow. Gate each scope on a fixed diff fixture before adding the next one.

Stage 1 · fixed prompt

Borrow Claude Code’s pattern. Write a /review slash command:

const reviewPrompt = (target: string) => `
You are an expert code reviewer. Review changes in: ${target}.
Output exactly 5 sections:
1. **Overview** (1-2 sentences)
2. **Code quality** (bullet list)
3. **Suggestions** (bullet list with file:line)
4. **Risks** (bullet list with severity)
5. **Test coverage** (assessment + gaps)
`;

Inject into main agent context; next turn it runs git diff or gh pr diff and emits review.

Acceptance gate: output on the fixed diff set contains the agreed sections, and the same input reproduces the target range and findings shape.

Stage 2 · standalone sub-agent

Prerequisite: the Stage 1 prompt can be evaluated on a fixed sample.

Switch reviewer to a separate thread (via LangGraph, openai-agents, or your own). New thread has no main agent history; only:

sub_messages = [
{"role": "system", "content": REVIEW_SYSTEM_PROMPT},
{"role": "user", "content": f"Review this diff:\n{diff_text}"},
]

Effect: reviewer can’t be biased by main agent’s history.

Acceptance gate: compare the main agent and isolated thread on the same diff, recording context leakage, duplicate findings, and failure propagation.

Stage 3 · constrained config

Prerequisite: the reviewer’s tool boundary is explicit and it does not need writes or browsing.

Disable web search, file write; set approval=Never. With LangGraph, sub-agent’s tool list only has git_diff:

reviewer_tools = [git_diff] # cannot write files, cannot browse
reviewer = create_agent(tools=reviewer_tools, system_prompt=REVIEW_SYSTEM_PROMPT)

Acceptance gate: threat fixtures for file writes, browsing, and recursive delegation are rejected while normal diff reads pass.

Stage 4 · structured output

Reviewer emits JSON not markdown:

class ReviewFinding(BaseModel):
severity: Literal["info", "warning", "error"]
file: str
line: int | None
category: Literal["correctness", "performance", "security", "style"]
message: str
suggestion: str | None
class ReviewOutput(BaseModel):
overview: str
findings: list[ReviewFinding]
test_coverage_gaps: list[str]
reviewer = create_agent(..., output_type=ReviewOutput)

Downstream can consume programmatically: auto-post inline comments, sort by severity, group by file.

Acceptance gate: schema tests cover missing fields, invalid line numbers, and unknown severities; parse failures cannot become empty findings.

Stage 5 · parameterized target

Borrow Codex’s four targets:

def make_review_target(kind: Literal["uncommitted", "branch", "commit", "custom"], **kwargs):
if kind == "uncommitted":
return f"git diff HEAD"
elif kind == "branch":
return f"git diff {kwargs['base']}..HEAD"
elif kind == "commit":
return f"git show {kwargs['sha']}"
elif kind == "custom":
return kwargs["prompt"]

User: /review --target=branch --base=main or /review --target=commit --sha=abc123.

Acceptance gate: uncommitted, branch, commit, and custom targets resolve to the expected diff on a fixed repository fixture, with explicit errors for out-of-range arguments.

Stage 6 · workflow integration

Push results to GitHub PR:

findings = reviewer.run(target_diff)
for f in findings.findings:
gh_post_inline_comment(pr=pr_number, file=f.file, line=f.line, body=f.message)

Or wire it into CI as a step (chapter 18 background agent).

Acceptance gate: duplicate webhooks do not duplicate comments, comment failures can retry or enter a manual queue, and CI failures do not swallow findings.

Extend only when needed:

  • Guardian-style agent-audits-agent (see Q2)
  • Remote review pipeline (if SaaS)
  • Multi-reviewer collaboration (security + correctness)

Engineering disciplines:

  1. Don’t build sub-agents before the evidence justifies them. Validate review quality with prompt-as-command first.
  2. Don’t let reviewer modify code. Output findings only; main agent fixes next turn.
  3. Don’t review entire PR history. Review diff only; history irrelevant.

Sources: simple to fancy: Claude Code commands/review.ts:9-31 → Codex tasks/review.rs:94-138 → Codex guardian/review.rs:1-200.

Follow-up: “How long should the review prompt be?” There is no portable token range in the cited evidence. Specify scope, severity rules, and the output schema, then compare misses, false positives, tokens, and latency on a fixed diff set. The Codex prompt’s size describes one snapshot; it does not establish a budget for other reviewers.

Q7 · Architecture: OpenClaw makes review an after_tool_call hook; Codex makes it a sub-agent. What’s the underlying difference?

The two models differ on three axes: trigger timing, model role, output destination.

Trigger timing

  • Codex sub-agent: user /review or main agent post-task. Point-trigger, each is a complete task.
  • OpenClaw pipeline hook: every fs.write auto-fires. Stream-trigger, bound to tool call frequency.

Model role

  • Codex sub-agent: standalone agent gets clean context, focused on review.
  • OpenClaw hook: could be the main agent itself (same conversation) or another model configured by user; context decided by plugin.

Output destination

  • Codex sub-agent: findings parsed into structured data via parse_review_output_event; user/UI decides usage.
  • OpenClaw hook: findings usually injected as system message in next turn. Main agent sees them and decides whether to fix.

Suitable scenarios

  • Codex mode fits “periodic review”: user wrote a batch of commits, wants overall quality check. Clear task boundary.
  • OpenClaw mode fits “continuous review”: every tool call passes through; finds issues and feeds main agent immediately. Like linter-on-save in an IDE.

Engineering trade-off

  • Codex mode: token cost bounded (each review is a discrete task), feedback delayed (user must trigger).
  • OpenClaw mode: feedback immediate (write triggers review), token cost spikes (reviewer runs on every tool call).

Recommendations:

  1. Most cases: Codex mode, user-triggered, isolated sub-agent, structured findings.
  2. Special cases: OpenClaw hook: security-sensitive scenarios where every fs.write goes through a sec reviewer; or compliance scenarios where every git push goes through a policy reviewer.
  3. Coexistence is fine: daily Codex-style /review, critical paths OpenClaw-style hook as backstop.

Engineering philosophy: review’s shape depends on its location in the agent loop. Point task = sub-agent; stream filter = pipeline hook; remote deep pipeline = SaaS product (ultrareview).

Three positions, three implementations, none interchangeable.

Source: openclaw/src/agents/tool-policy-pipeline.ts (after_tool_call hook, see chapter 04).

Follow-up: “Concrete example of OpenClaw plugin doing review?”

const reviewHook: ToolPolicy = {
name: 'inline-review',
after_tool_call: async ({ tool, result, context }) => {
if (tool === 'fs.write') {
const findings = await reviewerAgent.run({ diff: result.diff });
if (findings.severity === 'error') {
return { inject_system: `Review found issues:\n${findings.summary}` };
}
}
return null;
},
};

Simple and flexible, but every user writes their own. OpenClaw intentionally leaves it to the ecosystem.

Q8 · Engineering: What’s the goal of Codex’s parse_review_output_event? Why not just emit markdown?

parse_review_output_event parses reviewer output into structured findings:

struct ReviewFinding {
severity: Severity, // Info / Warning / Error
file: PathBuf,
line_range: Option<Range>,
category: Category, // Correctness / Performance / Security / Style
message: String,
suggestion: Option<String>,
}

Not markdown. Three reasons:

1. Downstream consumers need structure

Review results feed multiple consumers:

  • TUI rendering (sort by severity, group by file, filter by category)
  • VS Code integration (turn findings into inline diagnostics, share LSP channel)
  • CI reports (aggregate issue counts by category)
  • Auto-fix pipeline (find severity=error and feed main agent to fix)

Each consumer would have to re-parse from markdown. parse_review_output_event parses once, multiplexes.

2. Cross-model consistency

Reviewer on GPT-5 vs Claude Sonnet emits subtly different markdown (heading styles, bullet symbols, emphasis). JSON schema forces uniform structure. Model upgrades / switches don’t break UI.

3. Evolvable

Want to add ai_confidence (per-finding confidence)? Update schema + reviewer prompt; downstream consumers pick up new field, others ignore. Markdown forces each consumer to update its parser.

Implementation

Codex tells reviewer in prompt to emit JSON; parse_review_output_event uses serde to deserialize. Failures fall back to wrapping the whole output as an Unknown finding to avoid crashing.

Similar patterns:

  • OpenAI tools.function.parameters is structured schema, not prose.
  • LangChain StructuredOutputParser defaults to JSON schema.
  • Any scenario needing “consumable” model output should be schema-first, not markdown-first.

Engineering philosophy: LLM output has two uses, for humans vs for programs. Humans get markdown; programs get JSON / YAML. Mixing them (“markdown with embedded JSON”) is an anti-pattern (hard to parse, unstable format).

Source: codex/codex-rs/core/src/review_format.rs (parse_review_output_event + render_review_output_text).

Follow-up: “Claude Code’s /review emits markdown; how does downstream consume?” Claude Code’s local /review is mostly for humans, with no programmatic downstream. When structured output matters, use /ultrareview: the remote pipeline emits JSON via task-notification, another schema-first path.

Q9 · Practical: Your team complains agent-written code reviews miss too many bugs. Systematically improve quality.

Four scopes: observability → targeted prompts → multi-role → auto-fix. Freeze a diff set and rubric first, then use each scope’s acceptance result to decide whether to continue.

Stage 1 · audit every review

Each review writes: reviewer model, prompt version, diff size, findings count, severity distribution, user feedback (accept / reject). After a representative fixed sample:

  • Are certain categories systematically missed? (e.g., security findings underrepresented)
  • Certain file patterns under-handled? (e.g., SQL files yield high error rate)
  • What do users typically reject? (false positive vs over-suggestion)

Not “improve quality” yet, just “find where the gaps are.” No data, no fix.

Acceptance gate: every review links the model, prompt version, diff, and user feedback; the fixed sample meets the predeclared field-completeness threshold.

Stage 2 · targeted prompt tweaks

Prerequisite: the audit sample shows a repeatable missed or false-positive category.

Based on audit data, add specific instructions to reviewer:

When reviewing SQL:
- Always check for SQL injection risks
- Always check parameter binding
- Always check transaction boundaries
When reviewing security-sensitive code (auth, crypto, file I/O):
- Always check input validation
- Always check error handling reveals no internal state
- Always check resource cleanup paths

These let reviewer dig deeper in specific contexts. Cost: longer prompts, more tokens per review.

Acceptance gate: compare hit rate, false-positive rate, and schema adherence before and after on the same diff set; do not promote the next scope without a measurable improvement.

Stage 3 · multi-reviewer roles

Prerequisite: coverage gaps remain with one reviewer and the diff can be split by risk dimension.

Split reviewer into roles:

  • correctness reviewer: logic bugs
  • security reviewer: security holes
  • performance reviewer: perf
  • style reviewer: conventions

Each gets a dedicated prompt + tool set. Codex’s sub-agent model supports this: spawn 4 reviewers in parallel, each on one axis, then aggregate findings.

Cost: if four reviewers all read the full diff, model calls and tokens can approach 4×; that is an upper-bound example, not a quality guarantee. Anthropic’s ultrareview uses a similar parallel split; validate the benefit against its stated eval or your own sample.

Acceptance gate: compare finding quality, duplication, and call cost for one versus many reviewers on the same diff and rubric; keep parallelism only when the gain reproduces.

Stage 4 · auto-fix + feedback loop

Prerequisite: findings have a stable schema, and the fix path has runnable tests and a rollback boundary.

After findings, let main agent attempt fixes. Run tests, commit if passes. Feedback points:

  • Findings main agent can’t fix → reviewer prompt too vague. Tighten prompts.
  • Findings main agent fixed but tests broke → reviewer false positive. Reduce.
  • Findings main agent fixed, tests passed, user still rejected → wrong fix direction. Adjust suggestion style.

Acceptance gate: fixes on the fixed diff set pass tests and retain user rejection, rollback, and failure reasons; any regression stops automatic commits.

Do not present “60% to 90% over six months” as a site result. To test the loop, freeze a diff set, publish the rubric, and track hit rate, false positives, and user acceptance by version.

Engineering disciplines:

  1. Don’t treat “deeper reviewer” as a silver bullet. Some bugs are only visible at runtime; static review can’t catch them. Accept the miss rate.
  2. Quantify review quality. Define metrics (hit rate, false positive rate, user acceptance); don’t rely on “feels worse.”
  3. Run controlled experiments. Before/after prompt change, run 50 same diffs and compare findings.
  4. Open feedback channels. When users reject a finding, surface options (false positive / style mismatch / fix too expensive / other). These are the gold for prompt iteration.

Real precedent: GitHub Copilot Review went through this exact path, from single reviewer to multi-reviewer to feedback loop. A year.

Follow-up: “Sensitive internal code can’t be sent to Anthropic / OpenAI. What now?” (1) Self-host a model (Llama / Mistral / DeepSeek) as reviewer. (2) Critical review local, common review SaaS. (3) Enterprise tier typically offers data residency.

Q10 · Open-ended: Design a “universal review framework” pulling the best of each system.

Layered design, light to heavy, enable layers as needed:

Layer 1 · Prompt-as-command (required)

const review = createSlashCommand({
name: '/review',
args_schema: { target: 'uncommitted | branch | commit | custom' },
prompt: (args) => REVIEW_PROMPT_TEMPLATE(args),
});

Borrow Claude Code local /review. No standalone agent, zero overhead.

Layer 2 · Standalone sub-agent (recommended)

const reviewer = createSubAgent({
name: 'reviewer',
system_prompt: REVIEW_SYSTEM_PROMPT,
features_disabled: ['web_search', 'spawn_subagent', 'collab'],
approval_policy: 'never',
model: opts.review_model ?? opts.model,
output_schema: ReviewOutputSchema,
});
await reviewer.review({ target: 'branch', base: 'main' });

Borrow Codex sub-agent. Isolated context, constrained config, structured output.

Layer 3 · Multi-reviewer collaboration (advanced)

const review_team = createReviewTeam({
reviewers: [
{ name: 'correctness', prompt: CORRECTNESS_PROMPT },
{ name: 'security', prompt: SECURITY_PROMPT },
{ name: 'performance', prompt: PERFORMANCE_PROMPT },
],
aggregation: 'merge-by-severity',
});

Borrow ultrareview’s bug-hunter pipeline. Multiple reviewers in parallel, merged by severity.

Layer 4 · Pipeline hook (special cases)

const policyHook = createToolPolicyHook({
on: 'after_tool_call',
filter: (tool) => tool.name === 'fs.write',
handler: async ({ result }) => {
const findings = await reviewer.run({ diff: result.diff });
return { inject_system: formatFindings(findings) };
},
});

Borrow OpenClaw. Stream-trigger, reviewer per tool call.

Layer 5 · Guardian (security-critical)

const guardian = createGuardian({
on: 'before_tool_call',
filter: (tool) => tool.dangerous,
reviewer_prompt: GUARDIAN_PROMPT,
timeout_ms: 5000,
circuit_breaker: { max_consecutive_denies: 3, cooldown_ms: 30_000 },
});

Borrow Codex guardian. Agent-audits-agent, anti-workaround, anti-jailbreak, anti-hang.

Layer 6 · Remote deep pipeline (SaaS)

const ultrareview = createRemoteReview({
endpoint: 'wss://your-saas.com/ultrareview',
github_app: 'your-app-id',
pipeline: 'bug-hunter',
quota_check: true,
});
await ultrareview.start({ pr: 123, callback: postToLocalSession });

Borrow /ultrareview. Local push triggers; remote pipeline runs; callback to local.

Structured output schema (required)

interface ReviewFinding {
severity: 'info' | 'warning' | 'error';
file: string;
line_range?: { start: number; end: number };
category: 'correctness' | 'performance' | 'security' | 'style';
message: string;
suggestion?: string;
confidence?: number;
}
interface ReviewOutput {
overview: string;
findings: ReviewFinding[];
test_coverage_gaps: string[];
reviewer_id: string;
prompt_version: string;
}

Borrow Codex parse_review_output_event. One schema, multiple consumers.

Audit log (required)

interface ReviewAuditEvent {
ts: number;
reviewer: string;
target: { kind: string; ... };
diff_size_bytes: number;
findings_count: number;
severity_breakdown: Record<Severity, number>;
user_feedback?: { accepted: string[]; rejected: string[]; reason?: string };
tokens_used: number;
duration_ms: number;
}

JSONL on disk; feeds review quality improvements (Q9 Stage 1).

API example

import { createReviewSystem } from '@your-org/review';
const review = createReviewSystem({
prompt_template: REVIEW_PROMPT,
reviewers: ['correctness', 'security'],
output_schema: ReviewOutputSchema,
audit_sink: jsonlFileSink('./review-audit.log'),
});
const result = await review.run({ target: 'branch', base: 'main' });
console.log(result.findings);

Vs four systems:

  • Codex + layered API + pipeline hook integration.
  • Claude Code + standalone sub-agent route (not just prompt-as-command).
  • OpenClaw + sub-agent model (not just hooks).
  • Hermes + everything.

Re-estimate effort from model count, execution environments, evaluation corpus, and IDE/CI integration scope; the four source snapshots do not support a fixed person-month figure. The value is “tiered layers by user maturity” rather than forcing the heaviest mode immediately.

Cross-language: core API JSON in/out, schema cross-language shared. Reviewer prompts are strings, reusable anywhere.

Source composition: Codex tasks/review.rs:94-138 + guardian/review.rs:1-200 + Claude Code commands/review.ts:9-31 + OpenClaw tool-policy-pipeline.ts. Stitch the highlights = review framework v0.1.

Follow-up: “How to keep the framework’s own reviewer from getting buggy?” Maintain regression diffs with known findings and size the corpus by risk coverage. Every reviewer prompt change records hit rate, false positives, and schema adherence; roll back when a predeclared regression threshold is crossed.