

[How to build a self-improving code review agent](https://x.com/zachlloydtweets/status/2077428025474355521) 提出了一种做法：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 和合并，并且拆分确实降低风险时，才建议拆分」才是一条可复用规则。

```mermaid
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 的主文件负责控制审查流程：

```text
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` 承担：

```text
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`](https://github.com/warpdotdev-demos/cloud-factory-demo/blob/main/.agents/skills/improve-review-pr/SKILL.md)。它包含一个 [`collect_review_feedback.py`](https://github.com/warpdotdev-demos/cloud-factory-demo/blob/main/.agents/skills/improve-review-pr/scripts/collect_review_feedback.py)，再由 [`improve-review-pr.yml`](https://github.com/warpdotdev-demos/cloud-factory-demo/blob/main/.github/workflows/improve-review-pr.yml) 每日执行。

实际流程是：

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

这里有一个容易忽略的限制：收集脚本使用关键词和 reaction 做初步分类。例如回复中出现 `fixed`、`wrong`、`prefer` 等词时，会得到不同标签；较长但没有命中特征词的回复会被归为 `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](https://x.com/ewhauser/status/2077487384464375965)：如果 Outer-loop 持续吸收仓库特有反馈，最后是否只会得到一个装满特例的巨大 Prompt？

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

[Ben Nevile 的回复](https://x.com/saoul/status/2077752177074872393) 可以归纳为四种治理方法：

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

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

### Outer-loop 也要负责删除规则

[Jean Ibarz 建议](https://x.com/_ibarz/status/2077545077631504766)定期生成一个更短的 Reviewer Prompt，再检查压缩是否影响结果。可以把它实现成一次反向更新：

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

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

- 已经被更通用规则覆盖；
- 没有明确触发条件或可执行动作；
- 属于已经结束的临时项目约束；
- 与现有规则冲突，只能依赖模型临场选择；
- 长期没有改善发现率，却持续增加 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 漏报，可能是规则中没有这项检查，也可能是文件没有被读取、验证命令失败或调用链搜索过早停止。[评论区中的一个本地实践](https://x.com/ferueda/status/2077437701947855275)就是通过定时任务和 CLI 提取 session insight，再分析工作流、评价结果并生成建议。

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

### 2. 描述失败行为

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

```text
输入条件：
  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: write` 和 `pull-requests: write`，因为它要创建分支和 PR。它读取的 PR 描述、代码和人类评论都是不可信输入，其中可能包含要求 Agent 修改 workflow、泄露 secret 或扩大权限的 prompt injection。

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

```mermaid
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 都能更新

[另一条评论](https://x.com/opwizardx/status/2077685538270609510)认为，这种机制应该应用到所有 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 的价值，不是让规则越来越多，而是让失败更少重复发生。一次完整更新应当形成这样的链路：

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

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

