09 · 谁来否决 Agent 的 Diff
让独立 reviewer 在受限权限下审查 Agent diff,并输出可行动 findings
本章任务
要回答的问题
生成代码的 Agent 不能自证正确时,独立 Reviewer 如何否决 Diff 并给出可行动结论?
读完你能
- 把生成者、审查者和最终裁决者拆成独立角色
- 定义 finding 的证据、严重度、位置和修复建议契约
- 避免 Reviewer 复写代码、重复噪声和无出口循环
- 适合现在读
- 正在设计自动审查、双 Agent 回路或合并门禁的工程师
- 先修知识
- 理解 Diff、测试信号与 Reviewer 权限隔离
- 实践产物
- 一个 Reviewer 输出 Schema、严重度 Rubric 与合并门禁
- 证据边界
- 没有可靠 Oracle 时,两个模型仍可能共同漏掉同一类错误
代码审查要独立于代码生成
Section titled “代码审查要独立于代码生成”场景:生成代码的 Agent 在同一上下文里立刻审查自己的 Diff。它已经花了 20 轮证明这套实现合理,Reviewer Prompt 继承了相同假设,于是只检查风格,不再质疑架构前提和遗漏测试。
通过标准:Reviewer 从独立上下文读取明确的 base/head;默认只有只读工具;每个 finding 必须包含位置、证据、严重度、影响和可验证修复建议;“未发现问题”也要列出检查范围与盲区。
先定 reviewer 的边界
Section titled “先定 reviewer 的边界”四家在 4 件 review 相关事情上的覆盖度:
| 维度 | Codex | Claude Code | OpenClaw | Hermes |
|---|---|---|---|---|
| Review 触发 | `/review` 命令加 4 种 target(uncommitted、base-branch、commit、custom)加 guardian 自动审批 | `/review <PR>` 本地 prompt 加 `/ultrareview` 远程入口 | 不内建,可用 plugin 或自写 slash 命令 | 不内建,模型自己跑 `git diff` 走 terminal |
| Reviewer 形态 | 一个 sub-agent thread(`run_codex_thread_one_shot`),独立 system prompt | 同一个对话,只是塞一段长 prompt 进上下文 | 远程 CCR session(GitHub 应用拉取仓库并执行,完成后回写本地;耗时需按仓库、pipeline 和模型测量) | 不存在专门 reviewer |
| Reviewer 约束 | 关掉 web_search、SpawnCsv、Collab、MultiAgentV2。`AskForApproval::Never` 防止递归审批 | 走 BashTool 调 `gh pr`,没有专门约束 | 远程 sandbox 加 quota 限制加计费门槛 | 无 |
| 输出处理 | `parse_review_output_event` 解析结构化 findings。`ExitedReviewModeEvent` 退出 | 模型直接在 chat 里返回 markdown | 远程 task 完成后通过 task-notification 回写 | 模型自由格式输出 |
源码证据:隔离、目标和输出
Section titled “源码证据:隔离、目标和输出”Codex · 独立 sub-agent 跑 review 加 4 种参数化 target 加 guardian 双层架构
Section titled “Codex · 独立 sub-agent 跑 review 加 4 种参数化 target 加 guardian 双层架构”Review 的任务和生成代码的任务相反:前者要找问题,后者要完成目标。Codex 为此启动独立 thread,关闭 web search、递归委派和审批 UI,再按 diff 类型选择 prompt。
所以 Codex 决定把 review 做成一个完全独立的 sub-agent,从一开始就有干净的 context 和受限的能力集。
具体的实现是这样的:当用户触发 review(通过 /review 命令或 API),Codex 不在主对话里继续,而是建一个全新的 codex thread(叫 review sub-agent),把它的 config 精心剪裁一遍:关掉所有不该用的 feature、换上 reviewer 专属的 system prompt、强制 approval policy 为 Never:
Codex codex/codex-rs/core/src/tasks/review.rs:94-138 reviewer sub-agent:关掉危险 feature + 强制 Never approval + 专属 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)}这段配置代码里有三个工程细节是为了解决三个具体问题。第一个细节是把 web_search 设成 Disabled:reviewer 不应该联网搜文档,否则它看到一段陌生 API 调用会本能跑去搜「这个 API 是用来做什么的」,几分钟过去 review 跑偏到完全无关的方向,token 全浪费在搜索上。强制 Disabled 让 reviewer 只能看 diff 本身做判断。
第二个细节是关掉 SpawnCsv、Collab、MultiAgentV2 这一组 feature:这三个都是「这个 agent 能 spawn 更多子 agent」的能力,如果不关 reviewer 可能 spawn 出 reviewer-of-reviewer 形成递归,token 和 latency 都爆炸。关掉这一组让 reviewer 只能用「单 agent 加工具调用」的能力。
第三个细节是 AskForApproval::Never:reviewer 跑命令时(比如 git diff、git log)不应该弹审批框,因为用户的注意力本来在主 agent 上,被 reviewer 不停弹框打断会很烦。设成 Never 让 reviewer 永远不弹审批,要审批就抛错让主 agent 看着办(reviewer 通常只读,不需要审批)。
特别值得讲的是 review_model 这个独立字段:它允许 reviewer 用跟主 agent 不同的模型。主 agent 与 reviewer 可以按各自的质量、延迟和价格目标选型;是否换成便宜模型,应在固定 diff 集上同时测漏报、误报、token 和延迟。本文后面的 5-20K token 只是容量估算,不是 Codex 的公开测量结果。
Review target 是用户触发 review 时要回答的问题:「你要 review 什么?」Codex 提供了 4 种 target,每种对应一段参数化的 prompt 模板:
Codex codex/codex-rs/core/src/review_prompts.rs:15-37 4 种 review target 的 prompt 模板
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.";这四种 target 各自对应一个明确的使用场景。UNCOMMITTED 是「我刚改完,帮我看一眼」:staged 加 unstaged 加 untracked 全包括,最快出结果,适合 PR 提交前的本地预审。BASE_BRANCH 是「这条 branch 跟主干的差距」:典型的 PR review 前置步骤,让 reviewer 看「如果我现在提 PR 合并,主干会接收哪些改动」。
这里有个性能优化值得专门讲:模板分了两个版本,BASE_BRANCH_PROMPT 是「主 agent 已经算过 merge_base_sha」的版本,直接把 SHA 塞给 reviewer,让 reviewer 一行 git diff 就能拿到 diff。BASE_BRANCH_PROMPT_BACKUP 是「主 agent 没算过 merge_base」的备份版本,让 reviewer 自己跑 git merge-base HEAD upstream 解析出来。
为什么要分两版?调用方已经知道 merge base 时,直接传 SHA 可以省掉 reviewer 的一次工具调用;不知道时再让 reviewer 自己计算。源码没有提供大型 monorepo 的耗时基准,是否值得预计算应在目标仓库测量。
COMMIT_PROMPT 是「review 一个特定的 commit」:用于回顾历史,COMMIT_PROMPT_WITH_TITLE 是带 commit title 的版本(如果调用方知道 title 就传进来给 reviewer 更多上下文)。
Codex 的第二套 review 系统是 guardian/review.rs,它跟代码 review 完全不同:guardian 不是 review 代码改动,是 review 主 agent 在跑的危险动作。具体场景:主 agent 想跑 rm -rf /tmp/cache 这种动作,按理说要弹审批让用户决定,但用户可能在喝咖啡或者忙别的,让用户每次都看到审批框很烦。
这时候 guardian 这个独立 sub-agent 先帮你看一眼:它拿到 {tool, args, cwd, sandbox_state} 这些上下文,调用模型判断「这条命令风险有多大」,返回 allow、deny、timeout 三态决策。如果 guardian 判断风险低(比如 rm -rf /tmp/cache 在临时目录里,且 cache 看起来是无关紧要的内容),就自动 allow 不打扰用户。如果判断高风险,就拒绝并把决策原因告诉主 agent。
guardian 的两段特别指令值得仔细看:
Codex codex/codex-rs/core/src/guardian/review.rs:45-58 guardian 是 agent 审 agent 的 reviewer,带 rejection + timeout 两种特殊提示
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.",);第一段 GUARDIAN_REJECTION_INSTRUCTIONS 是 Codex 团队对「agent 反向越狱」的明确防御。具体的攻击模式是:主 agent 想 rm -rf /important 被 guardian 拒了,主 agent 不放弃可能想「换个写法干同样的事」(比如改成 find /important -delete、或者 cd /important && rm -rf .),这种迂回方式如果 guardian 没识别出来就被绕过。
所以 Codex 在 guardian 的 prompt 里明文写「agent must not attempt to achieve the same outcome via workaround, indirect execution, or policy circumvention」,让 guardian 拒绝时同时拒绝所有「实质达到同样结果」的变体,而不是只看字面命令。这是把一个真实工程教训直接写进 prompt 里,做得很细致。
第二段 GUARDIAN_TIMEOUT_INSTRUCTIONS 是处理 guardian 自己挂掉的情况:guardian 调模型 API 可能因为网络问题超时(默认 5 秒),如果默认认为「超时等于 unsafe」会过于保守把所有动作都拒了。这段告诉主 agent「timeout 不代表 unsafe,可以 retry 一次,或者直接问用户」。
除了这两段,guardian 还有 circuit breaker 机制:3 次连续 deny 触发熔断,guardian 暂停 30 秒,防止 guardian 模型在某段时间内异常地把所有动作都 deny 拖死主 agent。
Claude Code · 本地 prompt-as-command 加远程 ultrareview 双轨制
Section titled “Claude Code · 本地 prompt-as-command 加远程 ultrareview 双轨制”本地 review 最在意反馈速度。Claude Code 用 prompt-as-command 调 gh pr view 和 gh pr diff;远程 ultrareview 则把深审放到独立服务。两条路径共享输入,不共享运行上下文。
本地版的设计很简洁:就是 prompt-as-command 模式:
Claude Code claude-code/src/commands/review.ts:9-31 /review 是一段写好的 prompt,没有独立 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} `引用文件里主要是一段约 25 行的 prompt 定义:用户输入 /review 123,Claude Code 把 LOCAL_REVIEW_PROMPT(123) 塞进 chat context,让模型在下一轮自己跑 gh pr view、gh pr diff 和分析。它说明入口可以很薄,不代表评测、错误处理和输出契约没有成本。
第一是实现表面很小:引用的 /review 主要是一段 prompt,不需要另外封装 gh 调用和解析函数。prompt 是否比 TypeScript 函数更快交付,取决于评测、错误处理和输出契约,不能从源码行数推导固定倍数。
第二是用户可见可改:Claude Code 的 slash 命令源码本身放在 .claude/commands/ 目录的 markdown 文件里,用户可以打开看 prompt 写的什么、自己改。第三是命令可以受益于底层模型升级,但质量是否提升、提升多少,仍要在同一组 review 样本上回归,不能由「没有改 prompt」直接推出。
第四是跨工具组合:prompt 一句话告诉模型「用 gh pr view 走 gh pr diff 走分析」,模型自己组合调用工具,比写 TypeScript 函数自己 wrap gh CLI 简洁得多。
代价也很明显:本地版没有 reviewer 角色隔离。模型还是同一个对话上下文(看过主 agent 的所有历史),同样的 feature set(web_search、子 agent 套娃全开),同样的 approval policy。如果用户的 review 标准要求严格的批判性判断(比如「这段代码安全吗」),本地版可能因为见过太多上下文导致判断不够中立。这是 prompt-as-command 模式的根本局限:你只能调整 prompt 内容,调整不了 agent 配置。
远程版的设计完全相反:把 review 当独立产品做,跑在 Anthropic 的远程 sandbox 里:
Claude Code claude-code/src/commands/review/reviewRemote.ts:1-32 /ultrareview 远程 pipeline 的设计文档
/** * 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. */这段注释里有几个关键设计点值得展开。「Teleport」是 Claude Code 的一个抽象:它允许本地 session 把任务迁移到远程 CCR session,远程跑完再把结果回写本地。耗时取决于仓库、pipeline 和模型,源码注释没有给出固定 SLA。
这个 teleport 的实现细节是:本地的 RemoteAgentTask 被注册到 polling loop 里,每隔一段时间 poll 远程任务的状态,远程任务完成时通过 task-notification 机制把结果回写到本地 session 的 message 流里,跟用户用本地命令完成的体感完全一致。
CCR 是 Claude Code Web 的别名,它是一个能跑长任务的远程 agent runtime,专为「超过本地能跑的任务深度」设计的(参见 18 章 cron 跟 background task)。
远程版能把长任务从本机交互生命周期里拆出去。bug-hunter pipeline 可以让多个 agent 分看 security、performance、correctness、style,再汇总成结构化 findings;实际耗时和用户是否愿意本地等待都需要测量,源码没有给出 10-20 分钟的保证。
第二是环境更一致:远程 sandbox 可以固定 base image、git 版本和 lint config,减少环境差异;模型采样、外部服务和仓库状态仍会造成结果变化,所以这不等于完全可重复。
第三是计费模式独立:/ultrareview 走 quota、billing 检查(参见 ultrareviewQuota.ts),免费配额耗尽触发 overage dialog 让用户确认才扣钱,团队、企业用户直通。这种 monetization 模式只能在远程实现,本地版谁也收不了费。第四是跟 CI 整合:远程 review 本质上是个 cron job,可以接 GitHub webhook 让 PR open 时自动跑。
代价也有:远程版依赖 push branch(GitHub app 拉代码靠 git clone,本地未提交的代码看不到),所以源码注释里有 TODO(#22051) 说要等 useBundleMode 落地才能支持本地代码上传。远程版需要网络(用户离线时不可用)。远程 sandbox 看到全部代码,企业敏感场景可能有隐私顾虑。
OpenClaw · 不内建 review,把它做成 plugin pipeline 的 hook
Section titled “OpenClaw · 不内建 review,把它做成 plugin pipeline 的 hook”Review 标准随组织变化,平台不宜预设一套答案。OpenClaw 只在 tool-policy-pipeline 留 after_tool_call hook,团队自己决定何时触发和怎样消费 findings。
这些「review」共享的只是「另一个 agent 来检查」这个 meta 模式,具体实现细节完全不同。所以 OpenClaw 选择不内建任何具体的 review 实现,而是提供 hook 让用户自己在 plugin 里定义。
实际写法是这样的:用户在 tool-policy-pipeline.ts(详见工具系统)的 after_tool_call hook 里挂一个自己写的 reviewer middleware。
每当模型用 fs.write 改完一个文件,middleware 拿到 { tool_name, args, result },跑一遍用户自定义的 reviewer prompt(可能是「检查这次修改有没有破坏既有测试」、「检查这段代码符不符合公司编码规范」、或者「检查有没有引入 SQL 注入」),结果作为下一轮 system message 注入到主 agent 的 context 里。
这种「review 作为 pipeline 副作用而不是显式命令」的模式更适合企业自定义场景:团队可以根据自己的业务定义任何 review 逻辑,不用迁就平台提供的「标准 review 流程」。
OpenClaw 这种选择跟它的整体定位完全一致:它是 agent 控制面而不是 coding 工具,给出 review 这种「业务定义高度可变」的功能任何 hardcode 都是错的,把灵活性留给用户。
Hermes · 完全不抽象 review,让模型自己用 terminal 跑
Section titled “Hermes · 完全不抽象 review,让模型自己用 terminal 跑”Hermes 没有 reviewer 入口。用户让模型执行 git diff,再在主对话里给意见;实现成本低,但生成者与审查者没有边界。
这种模式的好处是零基础设施:不用维护 reviewer agent、不用维护 prompt 模板、不用考虑 review target 的参数化。代价也很大:review 质量完全取决于用户写的 prompt 和模型当时的注意力。没有受约束的 system prompt 强制 reviewer 角色,模型可能跟 review 跑偏到「我帮你修一下吧」(直接动手改而不是给建议)。
没有结构化输出格式,review 结果可能这次是 markdown bullet 下次是段落散文不一致。没有 4 种 target 的参数化,用户需要自己想清楚「我要 review 什么」并准确告诉 agent。
对 Hermes 的定位(多平台 chat agent,code review 不是核心场景)这种取舍是合理的:大部分用户跟 Hermes 聊的是日常事务而不是代码 review,给所有用户硬塞一个 reviewer 抽象层是不必要的复杂度。但如果有用户想用 Hermes 做认真的 code review,他得自己写较全的 prompt 加反复对齐 agent 行为,远不如直接用 Codex。
Review 流程共享的底线
Section titled “Review 流程共享的底线”虽然四家在 review 的工程深度上差距巨大(Codex 双层 sub-agent 对比 Hermes 零基础设施),但只要做了 review,三件事是共识。
第一件是先决定是否需要显式 reviewer 角色。Codex 用独立 sub-agent 加 REVIEW_PROMPT,Claude Code 本地版在 prompt 里明确写「You are an expert code reviewer」,OpenClaw 则把 reviewer prompt 留给用户。Hermes 的这条路径没有专属 reviewer 配置;源码只能证明角色隔离和输出约束缺失,不能据此推出 review 质量更差。是否影响漏报、误报或格式稳定性,需要在同一组 diff 和 rubric 上对比。
第二件是 review 的最终产物应该是结构化的 findings。Codex 用 parse_review_output_event 把 reviewer 输出解析成 {findings: [{severity, file, line, message, suggestion}]} 这种结构化 list,下游 IDE 可以拿来直接做行内高亮。Claude Code 在 prompt 里明确要求「Format your review with clear sections and bullet points」让输出至少是规整的 markdown。OpenClaw 允许用户自定义输出 schema。结构化输出的好处是可被机器消费:可以接 IDE 高亮、可以喂给下一轮 agent 自动修复、可以聚合多次 review 跑统计。如果输出是自由格式段落散文(Hermes 默认行为),下游只能给人看,agent 自动化路径就断了。
第三件是 reviewer 跑命令时不应该弹审批框。Codex 显式设 AskForApproval::Never,Claude Code 本地版让 reviewer 只用 BashTool 跑只读命令,OpenClaw 让用户在 hook 里控制工具集,都遵循「reviewer 只读不写」的原则。这背后的考虑是 UX:用户的注意力本来在主 agent 上,被 reviewer 弹审批框打断会很烦。如果 reviewer 需要审批(说明它要做副作用操作),那它就不该是 reviewer,应该是另一个 agent。
按反馈时限选择路径
Section titled “按反馈时限选择路径”「review 应该做多深」是实际决定四家适合什么场景的分歧。从「你在做什么类型的 agent」这个角度看,四种取舍对应不同的产品定位。
如果你在写一个专门做 coding 的 agent,要做认真的 review,那 Codex 的 sub-agent 模式可以作为起点。受约束的独立 config(关 web_search、SpawnCsv、Collab、MultiAgentV2,AskForApproval=Never)加 4 种参数化 target(uncommitted、base-branch、commit、custom)加独立的便宜模型(review_model 字段)加结构化输出(parse_review_output_event)。代价是抽象成本高:要维护一整套 reviewer 系统;是否值得,要用固定 diff 集比较漏报、误报、token 和延迟。
如果你的 agent 已经能跑命令,只想加一个 review 入口让用户能方便地用,那 Claude Code 的本地 /review 是轻量的起点。引用的实现主要是一段 prompt 字符串;真正的交付还要补参数校验、评测、错误处理和输出契约,不能从源码行数或一天的估算推出上线时间。代价是 reviewer 没有角色隔离,但对「review 只是众多功能之一」的产品(不是 coding-first),这个代价可能可以接受。
如果你想做认真的深度 review(要跑 bug-hunter pipeline),可以研究 Claude Code 的 /ultrareview 远程模式。把 review 当独立产品做:远程 sandbox 承担多维度并行(security、performance、correctness、style),并配 quota、billing gate 和 GitHub webhook。引用材料没有给出可迁移的固定时长;是否值得远程化,要测等待时间、资源占用和用户是否需要本地继续工作。代价是要建一整套远程 agent runtime,通常产品需要缩小范围或复用托管能力。
如果你做的是企业内部 agent 平台,review 规则因团队而异,可以研究 OpenClaw 的「不内建,把 review 做成 plugin pipeline hook」模式。让团队自己定义「review 是什么」,平台只提供 hook 跟编排能力。代价是开箱即用度低:用户要自己写 reviewer middleware,对小团队负担较重。
如果 code review 不是核心场景(如 chat agent、客服 agent),Hermes 的「无专门 reviewer」路径可以适配这个范围。它省去 reviewer 抽象;以后真要做 review,则需补 prompt、输出契约和基准测试。源码对比本身不证明质量高低。
选择:让谁拥有否决权
Section titled “选择:让谁拥有否决权”这里不做星级评分。先看 reviewer 是否需要独立上下文,再决定投入多少编排。
| 你的约束 | 可先读 | 代价与边界 |
|---|---|---|
| 代码变更需要第二个视角和稳定 findings | Codex 的独立 thread、受限工具和 target prompt | 增加一次模型调用;审批与结果协议要维护 |
| 只需一个本地入口快速看 PR | Claude Code 的 /review prompt-as-command | reviewer 仍共享主对话上下文,不能当隔离审计 |
| 规则由各团队定义,平台只提供扩展点 | OpenClaw 的 after_tool_call hook | 开箱需要自己写 prompt 与结果处理 |
| 产品不以代码审查为目标 | Hermes 的 terminal 路径 | 没有专门 reviewer,也没有稳定输出 schema |
从最小 Review 流程开始
Section titled “从最小 Review 流程开始”先固定 reviewer 的输入、输出和拒绝条件,再用一组可复现的 diff 评估是否需要独立 sub-agent。
复刻方案
最小可行
- 从一段固定 prompt 起步(参考 Claude Code 的本地 /review):用户输入 PR 号、commit SHA、diff,模型自己跑 gh pr view 走 gh pr diff 走 gh pr comments 三步收集上下文然后开始 review。这是最简实现,单 agent 就能跑起来
- 在 prompt 里强制 reviewer 输出 5 个固定 section(overview、quality、suggestions、risks、coverage):结构化输出让用户、下游工具能稳定 parse。强制每个 section 都填能避免 reviewer 偷懒只看一两个维度
- 把 review 跟修复分两轮:reviewer 只输出 findings(「这里有问题、那里要改」),下一轮才让主 agent 根据 findings 修。分离让 reviewer 不被「快速了事」诱惑而忽略实际的设计缺陷
进阶
- 把 reviewer 抽成独立 sub-agent(参考 Codex 的 run_codex_thread_one_shot):sub-agent 有独立 trajectory 加独立 prompt 加独立 tool 集,跟主 agent 完全隔离。这是「reviewer 真的独立思考而非附和主 agent」的关键
- 受约束 config:关 web_search、SpawnCsv、Collab、子 agent 套娃,AskForApproval=Never。reviewer 不该联网搜索(容易被新闻分散)、不该自己 spawn 子 agent(递归无底)、不该弹审批框(中断用户体验)
- 专门的 reviewer system prompt(参考 Codex 的 REVIEW_PROMPT)独立于主 agent prompt:主 agent prompt 是「主动做事」reviewer prompt 是「批判性思考」,混用会让 reviewer 失去批判视角
- 提供 4 种 target(参考 Codex 的 ReviewTarget):uncommitted(本地未 commit 改动)、base-branch(对比 main 的所有改动)、commit(指定 commit SHA)、custom(用户自定义)。不同场景需要不同范围
- 让 reviewer 用便宜模型(参考 Codex 的 review_model 字段):主 agent 用 GPT-5,reviewer 用 mini 或 Haiku。reviewer 任务相对简单(输入 diff 输出 findings),便宜模型够用,省成本
- 加 guardian 这种 agent 审 agent 的二层(参考 Codex 的 guardian/review.rs):risk level、timeout、circuit breaker 三件套。reviewer 跑久了、输出怪了、给出高风险评估,guardian 介入
- 结构化输出(参考 Codex 的 parse_review_output_event):让 reviewer 输出符合 JSON schema 的结果(每条 finding 带 severity、file、line、suggestion 字段),下游工具(CI、dashboard、Slack bot)可消费
一开始别做
- 别让 reviewer 共享主 agent 的对话历史:reviewer 看到「主 agent 之前怎么想的」反而会偏向「已经在做的方向」(认知锚定 bias)。reviewer 应该「冷启动」只看代码
- 别让 reviewer 改代码:reviewer 只输出 findings,修复留给主 agent 下一轮。让 reviewer 改代码等于让裁判踢球,丧失独立性
- 别让 reviewer 自己 spawn 子 agent:会形成 reviewer-of-reviewer-of-reviewer 递归无底。硬限制 sub-agent 不能再 spawn
- 别用 reviewer 替代 lint、test:reviewer 是补充不是替代。CI 里的 hard verifier(lint、test、type check)还得跑,reviewer 只看「人类视角的问题」
- 别忘了 reviewer 的 approval_policy 需要是 Never:reviewer 弹审批框等于用户 UX 崩(用户期待 reviewer 自动跑完,弹框打断流程)
把路径差异放回场景
Section titled “把路径差异放回场景”把 4 种放一起就能看清「另一个 agent 来检查」这个模式的演化:从「同对话塞 prompt」到「独立 sub-agent」到「远程 pipeline」到「pipeline 里的 hook」,深度逐渐增加。
Reviewer 边界与输出的核验入口
Section titled “Reviewer 边界与输出的核验入口”本章带走什么与下一步实验
Section titled “本章带走什么与下一步实验”Review 的价值来自独立视角、受限权限和结构化证据,不来自第二次自由生成。Reviewer 不应顺手改代码,也不应把没有位置和影响的意见塞进合并门禁。
下一步实验:在 10 个 Diff 中植入已知缺陷,分别让生成者自审、同模型独立线程审和另一模型审。记录 precision、recall、重复 finding、无效严重度、Token 与人工确认时间。只有独立路径在可接受成本下提高有效召回,双 Agent Review 才值得。
附录:练习与复盘
Section titled “附录:练习与复盘”按需展开练习和十道复盘题
- 🟢 写一段
/reviewprompt:5 个固定 section(overview、quality、suggestions、risks、coverage),让模型在主对话里把自己切到 reviewer 模式。验证:同一段 diff 跑两遍输出格式一致。 - 🟠 独立 reviewer sub-agent:用 LangGraph、openai-agents 这类框架,建一个独立 thread。受约束 config(不能 spawn 子 agent、不能改文件)。把 review target 参数化(uncommitted、branch、commit、custom)。
- 🟠 结构化 findings:让 reviewer 输出 JSON schema:
{findings: [{severity, file, line, message, suggestion}]},下游可解析后高亮 IDE。 - 🔴 agent 审 agent:实现一个 guardian 中间层:主 agent 想跑某个工具时,先把
{tool, args}发给 guardian sub-agent,让它判 risk level 加给 decision。加 5s timeout 加 3 次连续 deny 触发 circuit breaker。
Q1 · 概念:为什么 Codex 的 reviewer 是独立 sub-agent,而不是在主对话里塞 prompt?
把 review 做成 sub-agent 解决三个具体问题,每个都是同对话塞 prompt 解决不了的。
1. 上下文污染
主 agent 已经跑了 30 轮对话,context 里全是「我刚才用 grep 找了什么、改了哪个文件、为何这么改」。让它现在切到 reviewer 模式,模型本能会偏向「为自己刚才的实现辩护」:它见过太多上下文,知道每个改动的「动机」。Reviewer 应该带着批判眼光看 diff,独立 sub-agent 拿到一个干净 context,只看 diff 不看历史。这一条是 review 质量最大的影响因素。
2. Feature 隔离
主 agent 可能正在用 web_search、SpawnCsv、Collab、MultiAgentV2 这些 feature。Reviewer 不该用这些:它的任务是看 diff,不该联网搜文档(容易跑偏)、不该 spawn 更多子 agent(递归)、不该跟其他 collab agent 协作(无关)。Codex 在 sub-agent config 里显式 .disable(Feature::SpawnCsv) 等,把整个 feature plane 关掉。
3. Approval 隔离
主 agent 的 approval_policy 可能是 on-failure 或 on-request,reviewer 跑命令时会触发审批。问题:reviewer 弹审批 走 用户审批 走 reviewer 继续 走 弹下一个审批…用户的注意力本来在主 agent 上,被 reviewer 截胡。Codex 设 AskForApproval::Never 让 reviewer 永远不弹审批,要审批就抛错主 agent 看着办。
这三点 Claude Code 本地 /review 都没解决,原因是 Claude Code 的 reviewer 跟主 agent 共享 context。这是为什么 Anthropic 又造了 /ultrareview:把 review 放远程独立 session,回到独立 sub-agent 的设计模式。
工程选择:如果 reviewer 需要与主 agent 隔离 model context、feature set 或 approval 行为,可以评估独立 sub-agent。需要同时隔离多项时,sub-agent、受限 worker 或远程 job 都可能提供边界;用偏差、权限、token 与延迟数据决定实现,不把其中一种写成唯一方案。
源码:codex/codex-rs/core/src/tasks/review.rs:94-138(start_review_conversation 函数加三类 feature disable)。
追问:「独立 sub-agent 多花 token,值得吗?」先测再定。5-20K 和额外 system prompt 的数字只是这份设计里的估算;把同一批 diff 分别交给共享上下文与独立 reviewer,记录 token、延迟、命中率和误报率,再决定是否值得。
Q2 · 架构:Codex 同时维护「代码 review」和「guardian 审批 review」两套子 agent。是不是过度工程?
是否过度工程取决于两类任务的输入、预算和输出契约是否真的不同。当前 Codex 快照把它们分成两条 pipeline:
代码 review reviewer:
- 触发:用户主动
/review - 输入:一段 diff
- 输出:findings list(结构化 issue 加 suggestion)
- 时长:由 diff 规模、模型和工具调用决定;请用目标任务集测量
- 关键约束:需要独立 context,避免主 agent 上下文污染
guardian reviewer:
- 触发:主 agent 想跑危险命令
- 输入:tool name 加 args 加当前 cwd、sandbox state
- 输出:allow、deny、timeout 三态决策
- 时长:需要一个有界的 guardian 决策预算;具体阈值应在目标模型和网络上测量
- 关键约束:需要快、需要可被 circuit breaker 切断
两个任务有什么共同点?仅仅是「另一个 agent 来审一下」这个 meta 模式。具体的 prompt、超时、输出格式、约束条件、错误恢复策略全都不同。强行合并到一套 reviewer 会两边都做不好:
- 代码 review 通常可能超过 guardian 的单次决策预算,因此不能共用同一个超时契约
- guardian 不需要 4 种 target
如果代码 review 和 guardian 的预算、输入或输出契约确实不同,把它们分开维护更容易验证;meta 任务相似,并不意味着两条 pipeline 应该共用实现。
guardian 的几个特殊设计:
GUARDIAN_REJECTION_INSTRUCTIONS:明确写「agent must not attempt to achieve the same outcome via workaround」。这是防「agent 反向越狱」:主 agent 被 guardian 拒了以后,可能想「那我换个方式干同样的坏事」。Codex 团队把这条防御明文写进 guardian 的 prompt。GUARDIAN_TIMEOUT_INSTRUCTIONS:明确说 timeout 不等于 unsafe。guardian 可能因为模型 API 卡顿超时,主 agent 不应自动认为危险,而是 retry 或问用户。- Circuit breaker:3 次连续 deny 触发熔断,guardian 暂停 30 秒。防止 guardian 出 bug 把所有动作都 deny。
这种「agent 审 agent」的设计在四家里只有 Codex 做到这么深。Claude Code 用 permission mode 加 canUseTool hook 做轻量版(更像 policy 加 callback 而非较全 sub-agent)。
源码:codex/codex-rs/core/src/guardian/review.rs:1-200(rejection、timeout、circuit breaker 实现)。
追问:「Anthropic 的 Claude Code 为何没做 guardian?」答:Claude Code 走 permission mode 加 canUseTool hook 的轻量路线。permission mode 是用户配置(plan、acceptEdits、bypassPermissions),canUseTool 是同步 callback。没有「另一个 agent 来审一下」的成分。Anthropic 的判断是:大部分场景 policy 加 callback 够用,多一层 agent 审 agent 增加 latency 和 token,不划算。
Q3 · 概念:Codex 的 REVIEW_PROMPT 跟 BASE_BRANCH_PROMPT 关系是什么?
REVIEW_PROMPT 是 reviewer sub-agent 的 system prompt:定义 reviewer 的角色、输出格式、评估维度。整个 sub-agent 生命周期里这个 prompt 都在。
BASE_BRANCH_PROMPT 等 4 个是 task prompt:第一句 user message,告诉 reviewer「这次具体审什么 target」。每次启动 reviewer 选其中一个。
类比传统 system 加 user 设计:
REVIEW_PROMPT约等于「你是一个资深 code reviewer,按以下 5 个 section 输出 findings…」(角色加规则)BASE_BRANCH_PROMPT约等于「请 review 当前 branch 相对 base branch foo 的 diff,merge base SHA 是 abc123」(具体任务)
为什么分两层?因为:
- 任务模板化:4 种 review target 共享同一个 reviewer 角色,但任务参数不同(base branch name、commit SHA、custom prompt)。模板可参数化。
- 更换 target 不切 agent:理论上一个 reviewer sub-agent 能 review 多个 target(虽然 Codex 当前只 review 一个),不用反复初始化 sub-agent。
- A/B 测试方便:想换 reviewer 角色 prompt 只改
REVIEW_PROMPT,想优化某种 target 描述只改对应模板。两件事独立演进。
具体的 4 种 target:
UNCOMMITTED_PROMPT:当前 staged 加 unstaged 加 untracked。最快,适合「我刚改完,帮我看一下」。BASE_BRANCH_PROMPT:当前 branch 对比 base branch。是 PR review 之前的本地 review。COMMIT_PROMPT:specific commit 的 review。用于回顾历史 commit。- 还有
CUSTOM_PROMPT:用户自定义指令,最自由但最容易跑偏。
工程哲学:Prompt 是代码,要可重用、可参数化、可 diff。Codex 把 review prompts 放 review_prompts.rs 而不是 inline 到 review.rs 是因为这个文件以后还要加 prompt(比如 SECURITY_PROMPT、PERFORMANCE_PROMPT),独立文件便于维护。
源码:codex/codex-rs/core/src/review_prompts.rs:1-130(4 种 target prompt 全部加模板字符串)。
追问:「BASE_BRANCH_PROMPT 跟 BACKUP 版的区别?」答:BASE_BRANCH_PROMPT 是「主 agent 知道 merge_base_sha 了」的版本,直接告诉 reviewer SHA。BACKUP 是「主 agent 不知道 merge base」时让 reviewer 自己跑 git merge-base 命令。两个版本是为了避免在主 agent 已经算过 merge base 时让 reviewer 重新算一遍,省 reviewer 一次 tool call。
Q4 · 概念:什么是 prompt-as-command,为什么 Claude Code 大量使用?
Prompt-as-command 是「把一段精心写好的 prompt 作为命令实现」的模式。用户输入 /review 123,Claude Code 把 LOCAL_REVIEW_PROMPT(123) 塞进 chat context,让模型在下一轮自己跑 gh pr view、gh pr diff 等命令完成 review。命令本身不调代码,只是把 prompt 文本注入。
为什么大量用?
1. 实现成本极低
引用的 /review 主要是一段 prompt 字符串,没有另外封装 gh 调用。这样的入口代码很短,但评测、错误处理和输出契约仍是实现成本,不能用源码行数推出固定的交付倍数。
2. 用户可见、可改、可 diff
Claude Code 的 slash 命令源码本身是 markdown 文件(在 .claude/commands/ 目录)。用户可以打开看 prompt 写的什么,自己微调。这种「命令源码透明」的设计比黑盒函数对用户友好得多。
3. 模型能力的复利
模型升级时,slash 命令可能在 prompt 不变的情况下受益。是否真的提升 review 质量,需要在固定 diff 集和 rubric 上回归;新模型也可能改变格式遵循或误报率。
4. 跨工具组合
/review prompt 里告诉模型「用 gh pr view 走 gh pr diff 走分析」,模型自己组合调用工具。如果写 TypeScript 函数实现 /review,你得自己写 runGhPrView()、runGhPrDiff()、parseAndAnalyze(),每个都得跟 BashTool 集成。Prompt 一句话就够。
什么时候不该用 prompt-as-command?
- 强一致性结果:每次需要输出同样格式、同样 SHA、同样数字时,函数比 prompt 稳。
- 副作用敏感:prompt 让模型自己跑命令,模型可能跑错。需要副作用绝对受控时用函数。
- 延迟敏感:prompt-as-command 至少增加一次模型调用;具体延迟由模型、上下文和网络决定,不适合要求即时响应的 IDE hover 等路径。
Claude Code 把 review、planning、docs 这些「模糊任务」做成 prompt-as-command,把 BashTool、FileEdit 这些「精确任务」做成函数。两种模式互补。
源码:claude-code/src/commands/review.ts:9-31(25 行 LOCAL_REVIEW_PROMPT)加 src/commands/ 目录其他 prompt-as-command 实现。
追问:「Codex 也用 prompt-as-command 吗?」答:Codex 走另一个极端:所有 review prompts 都是 Rust 字符串常量,但 review 本身是独立 sub-agent。Codex 不把「用户输入触发的 prompt 注入」当 slash 命令,而是触发 sub-agent。两种风格不同但都正常。
Q5 · 工程:Claude Code 的 /ultrareview 走远程 CCR 而不是本地。具体好在哪?
/ultrareview 不是 /review 的简单增强,是把 review 当独立产品做。具体好处:
1. 不占用户机器资源
长 review 在本地会占用 CPU、磁盘 IO 和网络带宽,也会与当前交互会话竞争资源。远程 review 把这部分工作移到 sandbox,用户机器等待任务通知;实际节省多少资源和时间需要测量。
2. 跑深度 pipeline
远程版的源码描述了 bug-hunter pipeline:几个独立 agent 各看一个维度(security、performance、correctness、style),每个阶段可能运行数分钟。本地也能实现同样的流程,但需要把等待、资源和交互预算作为产品选择测量,而不是写成「不可能」。
3. 收窄环境差异
远程 sandbox 可以固定 base image、git 版本和 lint config,减少环境差异;模型采样、外部服务和仓库状态仍会让结果变化,不能因此写成完全可重复。
4. 计费模式独立
/ultrareview 走 quota、billing 检查。免费配额耗尽触发 overage dialog(用户确认才扣钱),团队、企业用户走不同配额路径。远程任务更容易集中计量,但本地功能同样可以按许可证、订阅或调用量收费。
5. 跟 CI 整合
远程 review 可以接 GitHub webhook,在 PR open 时自动触发。CI 也能调用本地或自托管 reviewer;差别在运行环境、凭据、并发和维护责任,不在是否“能跑”。
坏处也有:
- 依赖 push branch:远程 review 拉代码靠 GitHub app 加 clone,本地未提交的代码看不到。这是为何源码注释里写「useBundleMode」TODO,未来想支持本地代码上传。
- 网络依赖:用户离线时不可用,本地
/review还能跑。 - 隐私顾虑:远程 sandbox 看到全部代码,企业敏感代码可能不愿走。Anthropic 提供了 enterprise 部署对这点有缓解。
这里的选择不是“远程天然更好”。当任务耗时长、资源占用高,或需要统一审计时,远程运行可以把资源和监控从用户机器解耦;未提交代码、离线使用和数据驻留要求则可能让本地执行更合适。
源码:claude-code/src/commands/review/reviewRemote.ts:1-100(teleport 入口)加 src/services/api/ultrareviewQuota.ts(quota 检查)加 src/utils/teleport.ts(状态转移)。
追问:「Codex 有类似 ultrareview 的远程能力吗?」答:Codex 的 app-server 暴露 API 给前端,但 review 本身还是本地 sub-agent。没做 ultrareview 这种「远程深度 pipeline」。OpenAI 的产品定位是 CLI 工具,没走 SaaS 路线。
Q6 · 实战:你的 agent 要加 code review,怎么从 0 实现一个最小可用版?
按 prompt → sub-agent → 结构化 → 工作流 四个 scope 推进;每层先用固定 diff fixture 验收,再决定是否增加下一层。
阶段 1 · 固定 prompt
参考 Claude Code 模式。写一个 /review slash 命令:
const reviewPrompt = (target: string) => `You are an expert code reviewer. Review the 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)`;把 prompt 塞进主 agent context,让它在下一轮自己跑 git diff 或 gh pr diff 然后输出 review。
验收门槛:固定 diff 集上的输出包含约定的 section,且同一输入能重现 target 范围和 findings 格式。
阶段 2 · 独立 sub-agent
前置:阶段 1 的 prompt 输出已经能被固定样本评估。
把 reviewer 切到独立 thread(用 LangGraph、openai-agents、或自己写)。新 thread 不带主 agent 历史,只有:
sub_messages = [ {"role": "system", "content": REVIEW_SYSTEM_PROMPT}, {"role": "user", "content": f"Review this diff:\n{diff_text}"},]效果:reviewer 不会被主 agent 上下文带偏。
验收门槛:对照同一 diff 的主 agent 与独立 thread,记录上下文泄漏、重复 findings 和失败回传。
阶段 3 · 受约束 config
前置:reviewer 的工具边界已明确,且不需要修改代码或联网。
给 sub-agent 关掉 web search、关掉 file write、设 approval=Never。如果用 LangGraph,在 sub-agent 的 tool list 里只放 git_diff 一个工具,其他工具不暴露:
reviewer_tools = [git_diff] # 不能写文件、不能联网reviewer = create_agent(tools=reviewer_tools, system_prompt=REVIEW_SYSTEM_PROMPT)验收门槛:尝试写文件、联网和递归委派的 threat fixture 都被拒绝,正常 diff 读取仍通过。
阶段 4 · 结构化输出
让 reviewer 输出 JSON 而不是 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)下游可以编程消费:自动 post inline comments、按 severity 排序、按 file 聚合。
验收门槛:schema 校验覆盖缺字段、错误行号和未知 severity;解析失败不会被当成空 findings。
阶段 5 · 参数化 target
参考 Codex 4 种 target:
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"]用户可以 /review --target=branch --base=main 或 /review --target=commit --sha=abc123。
验收门槛:uncommitted、branch、commit、custom 四类 target 在固定仓库 fixture 上指向预期 diff,越界参数明确报错。
阶段 6 · 工作流集成
把 review 结果发到 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)或者把 review 当 CI step,PR open 时自动跑(变成 background agent,参见 18 章)。
验收门槛:重复 webhook 不产生重复评论,评论失败可重试或进入人工队列,CI 失败不会吞掉 findings。
按需扩展:
- guardian 风格的 agent-审-agent(参见 Q2)
- 远程版的 review pipeline(如果你做 SaaS)
- 多 reviewer 协作(security reviewer + correctness reviewer)
关键工程纪律:
- 不要在证据不足时建 sub-agent。先用 prompt-as-command 验证 review 质量值不值。
- 不要让 reviewer 修代码。让它只输出 findings,修复留给下一轮主 agent。
- 不要 review 整个 PR 历史。只 review diff,历史无关。
源码参考:从简到繁,Claude Code commands/review.ts:9-31 走 Codex tasks/review.rs:94-138 走 Codex guardian/review.rs:1-200。
追问:「review prompt 该多长?」没有可直接照抄的 token 区间。先写清范围、严重度标准和输出 schema,再用固定 diff 集比较漏报、误报、token 与延迟。Codex REVIEW_PROMPT 的长度只能说明该快照如何实现,不能证明别的 reviewer 也应采用同一预算。
Q7 · 架构:OpenClaw 把 review 做成 after_tool_call hook,跟 Codex 的 sub-agent 路线本质差异是什么?
两种 review 模型在「触发时机 + 模型角色 + 输出去向」三个维度完全不同。
触发时机
- Codex sub-agent:用户主动
/review,或主 agent 完成任务后主动调。点状触发,每次都是一次较全 review 任务。 - OpenClaw pipeline hook:每次
fs.write之后自动跑。流式触发,跟 tool call 频次绑定。
模型角色
- Codex sub-agent:独立 agent 拿干净 context,专心 review。
- OpenClaw hook:被 hook 调到的可能是主 agent 自己(用同一个 conversation),也可能是用户配置的另一个模型,context 由 plugin 决定。
输出去向
- Codex sub-agent:findings 通过
parse_review_output_event解析成结构化数据,由用户、UI 决定怎么用。 - OpenClaw hook:findings 通常作为
system message注入下一轮主 agent。主 agent 看到 review 结果会自己决定要不要回去修。
适合的场景
- Codex 模式适合「周期性 review」:用户写完一批 commits 想看一下整体质量。任务边界清晰。
- OpenClaw 模式适合「持续 review」:每个 tool call 都过一遍,发现问题立刻给主 agent 反馈,主 agent 修完继续。更像在 IDE 里 linter on-save 的体验。
工程取舍
- Codex 模式 token 成本可控(每次 review 是独立任务),但反馈延迟(用户得主动触发)。
- OpenClaw 模式反馈即时(写完就 review),但 token 成本飙升(每个 tool call 多花 reviewer 一份 token)。
实战建议:
- 大部分场景用 Codex 模式:主动触发、独立 sub-agent、结构化 findings。
- 特殊场景用 OpenClaw hook:例如安全敏感的场景,每次
fs.write都过一遍 sec reviewer。或企业合规场景,每次 git push 都过一遍 policy reviewer。 - 可以两种并存:日常用 Codex 风格
/review,关键路径用 OpenClaw 风格 hook 兜底。
工程哲学:review 的「形态」取决于它在 agent loop 里的位置。点状任务等于独立 sub-agent,流式过滤等于 pipeline hook,远程深度 pipeline 等于 SaaS 产品(ultrareview)。三种位置三种实现,互相不可替代。
源码:openclaw/src/agents/tool-policy-pipeline.ts(after_tool_call hook 定义,详见 04 章)。
追问:「OpenClaw 用户实际怎么用 hook 做 review?」答:典型 plugin 长这样:
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; },};简单灵活,但每个用户都得自己写一份。OpenClaw 故意不内建,留给生态。
Q8 · 工程:Codex 的 parse_review_output_event 设计目标是什么?为什么不直接吐 markdown?
parse_review_output_event 把 reviewer 的输出解析成 结构化 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>,}而不是一段 markdown。三个原因:
1. 下游消费需要结构化
review 结果会被多种 consumer 用:
- TUI 渲染(按 severity 排序、按 file 聚合、按 category 过滤)
- VS Code 集成(把 findings 转成 inline diagnostics,跟 LSP 共用通道)
- CI 报告(按 category 统计 issue 数量)
- 自动修复 pipeline(找 severity=error 的 findings 喂给主 agent 修)
每个 consumer 都要从 markdown 解析回结构化数据。parse_review_output_event 一次解析,多次复用。
2. 跨 model 一致性
reviewer 用 GPT-5 跟用 Claude Sonnet,输出 markdown 格式会略有差别(标题样式、bullet point 符号、强调方式)。但 JSON schema 强制都吐成统一结构。模型升级、切换不破坏 UI。
3. 可演进
未来想加 ai_confidence 字段(每条 finding 的可信度)?只改 schema 加 reviewer prompt 即可,下游 consumer 拿到新字段就用,没拿到就忽略。如果是 markdown 就要每个 consumer 改解析逻辑。
实现细节
Codex 让 reviewer 在 prompt 里明确要求输出 JSON,然后 parse_review_output_event 用 serde 反序列化。失败时 fallback 到把整段输出当成 Unknown 类型的 finding,避免崩溃。
类似设计:
- OpenAI
tools.function.parameters是结构化 schema 而非 prose。 - LangChain
StructuredOutputParser默认就是 JSON schema。 - 任何要让模型输出「可消费」内容的场景都该走 schema-first,不是 markdown-first。
工程哲学:LLM 输出有两种用途:给人看对比给程序看。给人看的可以是 markdown,给程序看的需要是 JSON、YAML 等结构化格式。混在一起的「markdown 里夹 JSON」是反模式(解析麻烦、格式不稳定)。
源码:codex/codex-rs/core/src/review_format.rs(parse_review_output_event 加 render_review_output_text 两个函数,分别处理「解析」和「渲染给人看」)。
追问:「Claude Code 的 /review 输出 markdown,下游怎么消费?」答:Claude Code 本地 /review 主要给人看,不强制下游消费。需要结构化时用 /ultrareview,远程 pipeline 输出 JSON 通过 task-notification 回传,本质上是另一种 schema-first 路径。
Q9 · 实战:你的团队抱怨 agent 写的代码 review 漏洞太多。如何系统性提升 review 质量?
按 可观测 → 定向提示 → 多角色 → 自动修复 四个 scope 推进;先冻结 diff 集和 rubric,再用每一层的验收结果决定是否继续。
第 1 阶段 · 把 review 输出落 audit log
每条 review 落盘:reviewer model、prompt version、diff size、findings count、severity distribution、用户反馈(接受、拒绝 finding)。积累一批固定样本后看:
- reviewer 是不是某些 category 漏得多?(比如 security 类 finding 普遍漏掉)
- reviewer 是不是某种 file pattern 不擅长?(比如 SQL 类文件错误率高)
- 用户拒绝的 finding 主要是哪种?(false positive 还是 over-suggest)
这一步不是「提升 review 质量」,是「弄清楚 review 在哪不够」。没数据就无法对症下药。
验收门槛:每次 review 都能关联 model、prompt 版本、diff 和用户反馈;固定样本的字段完整率达到预先声明的门槛。
第 2 阶段 · 针对性改 prompt
前置:审计样本已经暴露出可重复的漏检或误报类别。
基于 audit 数据,给 reviewer 加专项 instruction:
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这些 instruction 让 reviewer 在特定 context 下更深入。代价:prompt 变长,每次 review token 多一点。
验收门槛:在同一 diff 集上对比修改前后的命中率、误报率和 schema 遵循率,未达到目标就不晋级下一层。
第 3 阶段 · 多 reviewer 分工
前置:单 reviewer 的覆盖缺口仍存在,且 diff 能按风险维度拆分。
把 reviewer 拆成几个角色:
- correctness reviewer:看逻辑 bug
- security reviewer:看安全漏洞
- performance reviewer:看性能问题
- style reviewer:看代码规范
每个 reviewer 用专门 prompt 加专门工具集。Codex 的 sub-agent 模式支持这个:并行 spawn 4 个 reviewer,各看一个维度,最后汇总 findings。
代价:如果四个 reviewer 都读取完整 diff,模型调用和 token 可能接近 4 倍;这是成本上界的示例,不是质量保证。Anthropic 的 ultrareview 采用了类似的并行分工,收益仍需按其 eval 或你的样本验证。
验收门槛:在相同 diff 和 rubric 上比较单 reviewer 与多 reviewer 的发现质量、重复率和调用成本;只有收益可复现才保留并行层。
第 4 阶段 · 自动修复加反馈循环
前置:finding 已有稳定 schema,修复路径有可运行的测试和回滚边界。
reviewer 输出 findings 后,让主 agent 自动尝试修复。修完跑测试,测试过了就 commit。这里有个反馈点:
- 主 agent 修不了的 finding,说明 reviewer 写得不够具体。回去补 reviewer prompt。
- 主 agent 修了但测试挂了,说明 reviewer 误判。reduce false positive。
- 主 agent 修了测试也过了,但用户 reject,说明修复方向错了。改 reviewer 的 suggestion 风格。
验收门槛:固定 diff 集上的修复必须通过测试,并保留用户拒绝、回滚和失败原因;任何回归都停止自动提交。
不要把「6 个月从 60% 到 90%」当成本站结果。要验证反馈循环,固定一组 diff,按版本记录命中率、误报率和用户接受率,并公开样本与 rubric。
关键工程纪律:
- 不要把「reviewer 更深入」当万能药:有些 bug 本质上只有运行时才能发现,靠静态 review 抓不到。这种漏报要接受。
- 要量化 review 质量:定义 metrics(命中率、误报率、用户接受率),别只靠「感觉 review 不够好」。
- 要做对照实验:改 prompt 前后跑 50 个相同 diff,对比 finding 差异。
- 要给用户反馈通道:用户拒绝 finding 时让他选拒绝原因(误报、风格不一致、修复成本太高、其他),这些反馈是改 prompt 的金矿。
类似实践:GitHub Copilot Review 经历过相同路径:从单一 reviewer 到多 reviewer 到反馈循环。一年时间。
追问:「公司内部敏感代码不能给 Anthropic、OpenAI 看怎么办?」答:(1) 部署 self-hosted 模型(Llama、Mistral、DeepSeek 等)作为 reviewer。(2) 关键审查放本地,普通 review 走 SaaS。(3) Enterprise tier 通常提供「data residency」选项。
Q10 · 开放:设计一套”通用 review 框架”,吸收四家精华。
分层设计,从轻到重,让用户按需启用:
Layer 1 · Prompt-as-command(需要)
const review = createSlashCommand({ name: '/review', args_schema: { target: 'uncommitted | branch | commit | custom' }, prompt: (args) => REVIEW_PROMPT_TEMPLATE(args),});参考 Claude Code 本地 /review,无独立 agent,零开销。
Layer 2 · 独立 sub-agent(推荐)
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' });参考 Codex sub-agent 模式。独立 context、受约束 config、结构化输出。
Layer 3 · 多 reviewer 协作(高级)
const review_team = createReviewTeam({ reviewers: [ { name: 'correctness', prompt: CORRECTNESS_PROMPT }, { name: 'security', prompt: SECURITY_PROMPT }, { name: 'performance', prompt: PERFORMANCE_PROMPT }, ], aggregation: 'merge-by-severity',});参考 ultrareview 的 bug-hunter pipeline 思路。并行跑多个 reviewer,按 severity 合并。
Layer 4 · Pipeline hook(特殊场景)
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) }; },});参考 OpenClaw 模式。流式触发,每个 tool call 都过 reviewer。
Layer 5 · Guardian(安全场景)
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 },});参考 Codex guardian。agent 审 agent,防 workaround、防越狱、防卡死。
Layer 6 · 远程深度 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 });参考 /ultrareview。本地 push branch 触发,远程跑深度 pipeline,结果 callback 回本地。
结构化输出 schema(需要)
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; // 0.0 - 1.0}
interface ReviewOutput { overview: string; findings: ReviewFinding[]; test_coverage_gaps: string[]; reviewer_id: string; prompt_version: string;}参考 Codex parse_review_output_event。一处 schema,多处消费。
Audit log(需要)
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 落盘,方便后续做 review 质量改进(参见 Q9 第 1 阶段)。
API 示例
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); // 结构化 findings对比四家:
- 比 Codex 多了 layered API(用户按需启用)和 pipeline hook 集成。
- 比 Claude Code 多了独立 sub-agent 路径(不只 prompt-as-command)。
- 比 OpenClaw 多了 sub-agent 模型(不只 hook)。
- 比 Hermes 多了所有东西。
工程投入需要按模型数量、执行环境、评测集和 IDE/CI 集成范围重新估算;四个源码快照不能支持固定人月。这套框架的价值在于「按用户成熟度提供不同 layer」,而不是逼用户一开始就上最重模式。
跨语言:核心 API JSON in/out,schema 跨语言共享。Reviewer prompt 也是字符串,可在任何语言里复用。
源码组合:Codex tasks/review.rs:94-138 加 guardian/review.rs:1-200 加 Claude Code commands/review.ts:9-31 加 OpenClaw tool-policy-pipeline.ts。把四家精华抽出来组装,就是 review framework v0.1。
追问:「这个框架如何防止 review 自己出 bug?」答:维护一组带已知 finding 的回归 diff,并按风险覆盖来定样本规模。每次改 reviewer prompt 都记录命中率、误报率和格式合规性;超过预先约定的回归阈值再 rollback。