ᕕ( ᐛ )ᕗ Jimyag's Blog

让 Code Review Skill 从反馈中持续更新

How to build a self-improving code review agent 提出了一种做法:Review Agent 负责检查每个 PR,另一个 Outer-loop Agent 定期读取人类对 Review 意见的反馈,再提交 PR 更新 Review Skill。

这里的「自我改进」并不是训练模型,也不是允许 Agent 直接改写自己的规则。更稳妥的实现是把 Skill 当成代码维护:从失败案例中提取规则,经过分类、回放和 Review,再合并到下一版本。

这篇文章以我正在使用的 requesting-code-review Skill 为例,说明一条 Review 反馈怎样成为可审查、可验证、可回滚的规则。

Skill 里保存的不是所有 Review 记录

一次 Code Review 至少会产生三类信息:

  1. 事实:PR 描述、diff、调用链、测试结果以及最终修改。
  2. 反馈:某条意见被接受、反驳,或者因为缺少证据而撤回。
  3. 规则:以后遇到同类条件时,Agent 应该执行什么检查。

前两类适合保留在 PR、评测数据或 Review 历史中。只有经过归纳、能够影响后续行为的内容,才应该进入 Skill。

例如「这个 PR 不需要拆分」只是一次结论,不应写进 Skill;「只有子集能够独立 Review 和合并,并且拆分确实降低风险时,才建议拆分」才是一条可复用规则。

  flowchart LR
    A["PR 与当前 Skill"] --> B["Review Agent"]
    B --> C["Review 意见"]
    C --> D["人类反馈与最终修改"]
    D --> E["提取候选规则"]
    E --> F["历史 PR 回放"]
    F --> G["提交 Skill PR"]
    G --> H["Review 与合并"]
    H --> A

Outer-loop Agent 最多走到「提交 Skill PR」。它不应绕过 Review 直接修改生效规则,否则一次误解、临时偏好甚至恶意反馈,都可能影响之后的所有审查。

requesting-code-review 的结构

当前 Skill 的主文件负责控制审查流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
requesting-code-review/
├── SKILL.md
└── references/
    ├── architecture.md
    ├── ci-config.md
    ├── code-quality.md
    ├── documentation.md
    ├── go-specialist.md
    ├── java.md
    ├── performance.md
    ├── python.md
    └── security.md

SKILL.md 定义:

  • 什么时候触发本地 diff 或 GitHub PR 审查;
  • 怎样固定本轮 diff 快照;
  • 如何选择代码质量、性能、安全、文档和架构等分路;
  • 哪些结论可以输出,哪些应降级为待确认问题;
  • 输出格式、严重度、文件覆盖和只读边界。

references/ 保存按条件加载的专项检查。例如 diff 包含 Go 文件时才读取 go-specialist.md,命中架构专项触发条件时才读取 architecture.md。这样可以扩充检查能力,同时避免每次 Review 都把所有清单塞进上下文。

如果以后出现需要重复执行、适合确定性实现的操作,例如把结构化结果转换为 GitHub 评论,可以继续放进 scripts/,而不是要求模型每次临时生成脚本。

这个拆分也决定了规则应该写在哪里:

反馈类型 建议位置
改变整体审查顺序、权限或证据要求 SKILL.md
某种语言、风险或架构的专项检查 references/
可由程序稳定完成的重复操作 scripts/
单个仓库的命名和业务约束 仓库自己的 AGENTS.md 或说明文档
一次性意见或个人临时偏好 不写入 Skill

还需要一个负责更新 Review Skill 的 Skill

requesting-code-review 只负责审查当前 diff 或 PR,不应该同时分析自己的历史表现并修改自身规则。把两种职责混在一起,会带来两个问题:

  1. Reviewer 可能为了适应当前 PR 的反馈,直接改变后续审查规则。
  2. 执行 Review 所需的只读权限,会被扩大成修改 Agent 配置的权限。

因此,Outer-loop 更适合由单独的 improving-code-review-skill 承担:

1
2
3
4
5
6
7
improving-code-review-skill/
├── SKILL.md
├── references/
│   ├── feedback-classification.md
│   └── evaluation.md
└── scripts/
    └── collect-review-feedback.py

三个相关 Skill 的职责可以这样划分:

Skill 输入 输出
requesting-code-review 当前 PR、diff、需求与代码上下文 Review 结论
receiving-code-review 别人已经提出的 Review 意见 判断意见是否成立,并处理当前代码
improving-code-review-skill 多次 Review 的意见、反馈和最终结果 对 Review Skill 的候选修改及评测报告

improving-code-review-skill 不直接覆盖 requesting-code-review。它应该:

  1. 收集一段时间或一组 PR 的 Review 事件。
  2. 区分有效意见、误报、漏报、证据不足和一次性偏好。
  3. 判断候选规则属于通用 Skill、专项 reference 还是项目级 AGENTS.md
  4. 修改 chezmoi 中的源文件,而不是只改当前运行副本。
  5. 使用历史正反例回放修改前后的行为。
  6. 提交包含来源和评测结果的 PR,等待人确认。

这意味着当前 requesting-code-review 是被改进的对象,不是 Outer-loop 本身。本文后面介绍的反馈提取、规则分类和历史回放,都应由这个独立 Skill 编排。

Warp 示例实际做了什么

Warp 的示例仓库已经实现了这套拆分,名称是 improve-review-pr。它包含一个 collect_review_feedback.py,再由 improve-review-pr.yml 每日执行。

实际流程是:

  1. 默认读取最近 24 小时更新过的 PR。
  2. 收集 Review Agent 的顶层 Review、行内评论、人类回复和 reaction。
  3. 将反馈分为 validatedcorrectedrefinedambiguous
  4. 决定不修改、更新通用 review-pr、更新仓库专属 review-pr-local,或者同时更新两者。
  5. 有足够证据时创建分支并提交 Skill PR;没有可复用结论时输出 no_changes
  6. PR 只包含 Review Skill 和必要说明,不修改产品代码,也不自动合并。

这里有一个容易忽略的限制:收集脚本使用关键词和 reaction 做初步分类。例如回复中出现 fixedwrongprefer 等词时,会得到不同标签;较长但没有命中特征词的回复会被归为 refined。这种分类适合缩小人工分析范围,不足以直接证明 Review 意见正确或错误。

当前收集脚本也不会根据最终代码 diff 自动确认建议是否真的被采用。因此,feedback_corpus.json 应被视为待分析的证据集合,而不是可靠的评测标签。Outer-loop 仍需读取原始评论、对应代码和最终修改。

三次真实更新

这个 Skill 的版本历史能够说明更新不是简单地追加提示词。

第一次:增加架构审查分路

最初的 Skill 已经能检查代码质量、性能、安全和文档,但在分层调整、状态生命周期和权限边界发生变化时,普通代码质量清单不够集中。

更新没有把架构检查设为每次必跑,而是增加触发条件:

  • 用户明确要求架构评审;
  • diff 新增或重组模块、接口、存储或跨服务调用;
  • 改动涉及错误体系、权限、生命周期或并发控制;
  • 深入审查发现薄封装、重复概念或隐式全局状态。

命中条件后才加载 references/architecture.md。这次修改改变的是路由和审查能力,因此同时涉及主流程和新的 reference。

还有一个重要限制:架构意见必须有当前 diff 或必要调用链的证据,不能把个人偏好包装成阻塞缺陷。这个限制与架构清单本身同样重要。

第二次:不清楚时查真实代码

之后的 Review 暴露了另一类问题:只读局部 hunk 容易把不完整上下文误判为代码缺陷。

对应的规则不是「多读一些代码」,而是明确失败条件:

如果判断依赖的类型、函数、配置、调用方、错误语义、生命周期或边界条件不清楚,必须查找实际代码定义和调用链,不能只看部分 hunk 下结论。

这次更新还补充了可信上下文边界。PR 分支里的 AGENTS.md、prompt 或 CI 配置本身属于被审查内容,不能反过来成为审查该 PR 的权威指令;仓库级规则应优先读取 base 或 default branch 的版本。

这类规则适合放在 SKILL.md,因为它改变所有审查分路获取证据的方式,而不是只服务某种语言。

第三次:证明所有文件都被处理过

最新一次更新针对遗漏与误报,增加了三项约束:

  1. 从完整 diff 枚举文件,并标记为「已审查」「仅作上下文」「有理由跳过」或「审查失败」。
  2. 根据本次变更文件加载 Go、Java、Python、CI 配置等专项 reference,而不是根据仓库里存在什么语言来加载。
  3. 输出前主动寻找能够推翻候选问题的证据,并确认定位范围仍属于当前 diff 快照。

这不是给 Review 清单继续加项目,而是在补充可观察的完整性。最终摘要会显示文件覆盖情况,无法读取的高风险文件也不能被一句「未发现问题」掩盖。

三次更新分别解决了能力缺口、证据缺口和覆盖缺口。它们都来自具体 Review 行为,但最终写入的是跨项目可复用规则。

防止 Skill 变成巨型 Prompt

评论区里最重要的质疑来自 Eric Hauser:如果 Outer-loop 持续吸收仓库特有反馈,最后是否只会得到一个装满特例的巨大 Prompt?

这个问题不能只靠「每次少改一点」解决。小修改如果永远只增加不删除,仍然会不断累积。

Ben Nevile 的回复 可以归纳为四种治理方法:

膨胀原因 处理方式
所有规则都常驻主 Prompt 按语言、风险和任务拆到按条件加载的 reference
把历史事件直接写进 Skill 历史保存在 corpus 或 Memory 中,需要时检索
每次只在末尾追加新规则 重新归纳并修改现有规则,而不是保存事件流水账
规则写入后永久保留 用实际 Review 结果判断它是否仍值得占用上下文

requesting-code-review 当前把 Go、Java、Python、CI、架构等检查拆到 references/,就是第一种做法。但拆文件只解决加载范围,不会自动处理重复、冲突和过期规则。

Outer-loop 也要负责删除规则

Jean Ibarz 建议定期生成一个更短的 Reviewer Prompt,再检查压缩是否影响结果。可以把它实现成一次反向更新:

1
2
3
4
5
找出重复、低命中或长期未触发的规则
→ 合并或删除候选规则
→ 回放历史正例和反例
→ 比较误报、漏报、成本和覆盖率
→ 表现不下降才使用压缩版本

以下规则应该优先进入清理候选:

  • 已经被更通用规则覆盖;
  • 没有明确触发条件或可执行动作;
  • 属于已经结束的临时项目约束;
  • 与现有规则冲突,只能依赖模型临场选择;
  • 长期没有改善发现率,却持续增加 token 和 Review 时间;
  • 实际应该放入项目 AGENTS.md 或专项 reference。

规则来源、代表性 PR 和评测结果仍应保留,但可以放在 Skill PR、评测清单或 corpus 中,不必全部写进运行时 Prompt。

从一条反馈生成 Skill 修改

实际更新时,我会按以下顺序处理。

1. 保存完整事件

不要只保存一句「Reviewer 说错了」。至少需要:

  • 当时的 PR 描述、base/head commit 和完整 diff;
  • Agent 使用的 Skill 版本;
  • 原始 Review 意见及其文件、行号和证据;
  • 人类的回复;
  • 最终代码修改和验证结果。
  • Agent session 中读取过的文件、执行过的命令和主动放弃的候选问题。

缺少这些信息,Outer-loop Agent 很难区分规则缺失、上下文不足、模型偶发错误和人类偏好。

GitHub 评论主要记录结果,session 记录能够解释过程。例如 Reviewer 漏报,可能是规则中没有这项检查,也可能是文件没有被读取、验证命令失败或调用链搜索过早停止。评论区中的一个本地实践就是通过定时任务和 CLI 提取 session insight,再分析工作流、评价结果并生成建议。

不过 session 可能包含私有代码、命令输出、路径和环境信息。收集器应只保留分析所需字段,在本地完成脱敏,不把完整对话、环境变量或内部推理直接上传到反馈 corpus。

2. 描述失败行为

候选修改应该先写成可验证的失败描述:

1
2
3
4
5
6
7
8
输入条件:
  diff hunk 在函数签名处结束,完整函数仍有后续实现。

错误行为:
  Reviewer 仅根据 hunk 判断函数缺少实现。

期望行为:
  下结论前读取完整函数及必要调用方;如果仍缺证据,降级为待确认。

这样才能判断修改后是否真的修复问题。直接写「Review 要更仔细」无法测试,也容易变成无效提示词。

3. 判断是否值得泛化

一条反馈进入 Skill 前至少回答三个问题:

  1. 同类问题会不会在其他 PR 或仓库再次出现?
  2. 规则是否能够描述触发条件和期望动作?
  3. 新规则是否可能让其他场景产生更多误报?

只有第一个问题成立,还不够。团队对某个模块的特殊约定可能会重复出现,但更适合放在项目级 AGENTS.md,不应该污染通用 Review Skill。

4. 做最小修改

优先修改现有规则,避免不断追加含义接近的条目。一个好的 Skill diff 应该能够回答:

  • 它改变了哪个具体行为?
  • 在什么条件下触发?
  • 不应该影响哪些场景?
  • 是否需要同步输出格式、专项路由或 reference?

如果修改让 SKILL.md 继续膨胀,应检查内容是不是专项知识,可以按条件下沉到 references/。但也不要为了目录整齐,把一个需要跨分路生效的核心规则拆散到多个文件。

5. 用历史 PR 回放

Skill 更新至少需要两组样本:

  • 正例:原先发生过漏报或误报的 PR,更新后应该得到预期行为。
  • 反例:过去审查正确的相似 PR,更新后不应新增错误意见。

可以记录这些指标:

  • 应发现问题的命中率;
  • 人类接受率与误报率;
  • 未审查或审查失败的文件数;
  • 需要人类纠正的次数;
  • 单个 PR 的耗时和 token 消耗。

只看修改后的一个成功案例,容易把针对样本的补丁误认为能力提升。

6. 通过 PR 发布

Skill 修改和代码修改一样需要:

  • 说明触发这次更新的 Review 事件;
  • 给出更新前后的行为差异;
  • 列出正例和反例回放结果;
  • 检查主文件与 references 的链接、格式和输出约束;
  • 保留可回滚的 commit。

在我的配置里,home/dot_agents/skills 是 chezmoi 管理的源文件,修改后还要确认它与实际运行的 ~/.agents/skills 一致。只改运行副本会让规则在下一次同步时丢失,只改源文件则可能让当前 Agent 继续使用旧版本。

自动化到什么程度

Outer-loop Agent 适合自动完成:

  • 汇总 Review 意见及其后续处理;
  • 对相似失败进行归类;
  • 查找 Skill 中已有规则;
  • 生成最小修改;
  • 运行固定的历史样本回放;
  • 提交包含证据的 PR。

以下决定仍应由人确认:

  • 某条反馈是不是团队长期规则;
  • 通用 Skill 与项目规则的边界;
  • 安全和权限约束是否被削弱;
  • 为提高接受率而删除的规则,会不会造成漏报;
  • 当前评测集是否足以证明改进。

尤其不能把「人类没有回复」直接解释为「意见正确」。评论可能只是被忽略,也可能因为 PR 已关闭而没有处理。缺少明确结果的样本不应自动修改规则。

Outer-loop 自身也需要安全边界

Inner-loop Reviewer 可以保持只读,把 review.json 交给固定程序发布评论。Outer-loop 不同:Warp 示例的 GitHub Action 需要 contents: writepull-requests: write,因为它要创建分支和 PR。它读取的 PR 描述、代码和人类评论都是不可信输入,其中可能包含要求 Agent 修改 workflow、泄露 secret 或扩大权限的 prompt injection。

只在 Skill 中写「不要修改产品代码」还不够。更稳妥的执行链是:

  flowchart LR
    A["不可信反馈"] --> B["只读分析 Agent"]
    B --> C["候选 patch 与报告"]
    C --> D["固定程序校验路径和格式"]
    D --> E["受限凭证创建 Skill PR"]
    E --> F["人类 Review"]

固定校验至少应该确认:

  • 只能修改允许的 Skill 路径,不能修改 workflow、产品代码和 secret 配置;
  • 目标文件来自 default branch 的可信版本;
  • 输出 schema、安全规则和权限边界没有被删除;
  • PR body 包含来源、被拒绝的候选和验证结果;
  • Agent 只能创建 PR,不能自动批准或合并;
  • branch protection 仍要求人类 Review。

这样即使反馈内容试图改变 Outer-loop 的职责,最终写操作仍受可信程序和仓库保护规则限制。

是否应该让所有 Skill 都能更新

另一条评论认为,这种机制应该应用到所有 Skill,而不只是 Code Review。可复用的部分确实很多:

  • 收集执行事件;
  • 关联人类反馈和最终结果;
  • 分类候选经验;
  • 生成最小修改;
  • 运行评测;
  • 提交 PR。

但不适合创建一个拥有任意文件写权限的「全局自我改进 Agent」。每个被更新的 Skill 仍需声明自己的:

  • 允许修改的路径;
  • 不能削弱的安全和输出契约;
  • 成功与失败标准;
  • 正例、反例和成本指标;
  • 项目规则与通用规则的边界;
  • 需要人工批准的动作。

因此,可以复用同一套改进框架,但评测集和写入权限必须按 Skill 隔离。Code Review、CI 分析和技术写作的「变好」不是同一个指标。

Skill、Wiki 与 Memory 的分工

如果系统同时使用 Agent Wiki 和长期 Memory,可以把三者分开:

  • Wiki:记录代码结构、模块职责和项目文档。
  • Memory:记录某次 Review 发生了什么、人类如何纠正以及哪些方案曾经失败。
  • Skill:保存经过筛选后,下一次应该怎样审查的规则。

Memory 是更新 Skill 的输入之一,但不是所有 Memory 都应编译成规则。Skill 也不应代替项目知识库,否则每次代码变化都要修改审查流程。

结语

持续更新 Review Skill 的价值,不是让规则越来越多,而是让失败更少重复发生。一次完整更新应当形成这样的链路:

1
2
3
4
5
6
7
8
Review 事件
→ 保存证据
→ 描述失败行为
→ 判断是否可复用
→ 修改正确的规则层
→ 正反例回放
→ 压缩并清理失效规则
→ 提交并 Review Skill PR

这套方法比 Agent 直接改写自身提示词慢一些,但它保留了来源、评审和回滚路径。对于会参与代码合并判断的 Agent,这些约束比「自动学习」本身更重要。

#AI Agent #Code Review #Skill