# 没有验收的自进化，会把错的教训写进 harness

![](https://leafw-blog-pic.oss-cn-hangzhou.aliyuncs.com/verse-self-evolution-cover.png)

用 Claude Code、Codex 等 Coding Agent 久了，多半都干过一件事：agent 犯了个错，我有可能在 `AGENTS.md` 或 `CLAUDE.md` 里补一条规则，“以后别这样xxx”。补完感觉下次它也许就不会犯了，不过说实话的确犯的概率变小了。但多数时候没关注后续带来的其他影响。

再往前想一步：既然规则是从失败里总结出来的，能不能让 agent 自己读失败记录、自己改规则，越改越强？这几年“self-evolving agent”的说法很多，听起来也确实诱人。

最近读了论文 *VERSE: Verified Self-Evolving Optimizer for Agent Harnesses*（https://arxiv.org/abs/2610.02616，作者来自 MIT 和 Amazon）。它研究的正是这件事，结论有点反直觉：没有执行验收，自进化没让结果变好，反而更差了。

接下来我们大致按照“问题—方法—实验—启发—局限”的顺序拆一遍，看看这篇论文到底研究了什么内容。

## 不验证，更差

### 背景：harness 和 harness evolution

harness 这个说法最近几个月已经很常见了，这里只补一句论文的口径：它指包在语言模型外面的 prompt、工具、记忆和控制流程。论文开头还提了一句，在 SWE-bench、Terminal-Bench 这类 agent 基准上，harness 的影响常常和模型本身差不多大。

harness evolution 就是让一个 LLM optimizer 读另一个 executor agent（模型权重冻结）的失败轨迹，再去改 executor 的 harness，迭代好几轮。Meta-Harness、AHE、Self-Harness、HarnessX 都属于这条路线。

问题在于，这些方法里 optimizer 自己的那套东西是固定的：要么是手搭的流水线，要么是现成的 coding agent。前几轮的教训会改变 executor 的下一版 harness，却不会改变 optimizer 自己干活的方式。VERSE 问的就是：optimizer 能不能连自己的 harness 一起改？

### 先划清数据切分

在讲实验之前，论文专门交代了数据切分，我觉得这段值得单独说。作者指出，很多 harness optimizer 在同一个基准上搜索又在同一个基准上报结果；他们引的另一篇工作认为，这种搜索更像 test-time scaling，收益可能来自对具体任务模式的过拟合。

所以 VERSE 用三份互不重叠的任务：从 SWE-rebench 2025 年 1 月到 2026 年 2 月的 593 个任务池里抽 110 个训练、50 个验证；测试集是 2026 年 3 月那批里能评分的 108 个任务。这三份都是 Python 仓库，算分布内。分布外（OOD）测试集用 2026 年 7 月那批：107 个能评分的任务，比前面所有任务都新，覆盖 Go、Java、Python、Rust、TypeScript，其中只有 20 个是 Python。

最终 harness 按验证集准确率选出（类似机器学习里留验证集上最好的 checkpoint），测试集只用来评估。每轮结束后，optimizer 能看到一份简短的验证摘要：每轮验证准确率、最近一次改动修好或弄坏了哪些验证任务的编号、这些变化重跑后是否还成立、harness 代码报错的频率。验证任务的内容和轨迹它看不到。

### Observation 1：只加自进化，反而掉分

第一组对照实验从 Meta-Harness 出发。optimizer 是 Qwen3.8-Flash-Next（总参数 176B，激活 6B），executor 是 Qwen3.8-27B，两者都冻结，在 SWE-rebench 上跑六轮。四种设置：原样、只加自进化、只加简单验证、两者都加。

“简单验证”很朴素：optimizer 提交改动前，可以先用改过的 harness 跑一个训练任务，看测试过没过、读一下 executor 的轨迹，在剩余调用预算内再改再试。

论文报告的 Table 1（测试集 avg@3 准确率，%）：

| 设置 | 自进化 | 验证 | 选中轮次 r* | 按验证集选出 | 最后一轮 |
| --- | --- | --- | --- | --- | --- |
| 初始 harness | – | – | – | 33.95 | 33.95 |
| Meta-Harness 原样 | ✗ | ✗ | 4 | 39.20 | 35.80 |
| 只加自进化 | ✓ | ✗ | 0 | 31.79 | 31.79 |
| 只加简单验证 | ✗ | ✓ | 5 | 39.81 | 37.65 |
| 自进化 + 简单验证 | ✓ | ✓ | 6 | 41.05 | 41.05 |

最值得看的是“只加自进化”那一行：验证集上没有任何一轮超过第 0 轮，最后被选中的就是初始 harness 本身，重新评估得到 31.79%。

论文给的解释是：optimizer 无法在提交前测试自己的改动，分不清哪些改动是错的；它只能从轨迹和分数里学，而且要等到下一轮才看到改动效果。作者据此推测，没有验证时，自进化的 optimizer 可能被有噪声的验证结果误导，把错误的教训写进自己的 harness。论文还引了别的工作：optimizer 读轨迹时会报告从未发生过的失败。

同一个 optimizer，一旦能在提交前跑草稿，就拿到了这组里最高的 41.05%。

## 方法拆解：VERSE 怎么做

### 两层循环

VERSE 要挂在一个已有的 harness optimizer 上运行，论文把被挂的这个叫 host。论文选了四个 host，它们的主要区别在于怎么消化失败轨迹：

- **Meta-Harness**：optimizer 直接读原始轨迹；
- **AHE**：先用 LLM 把轨迹提炼成逐任务的病因报告，再交给 optimizer；
- **Self-Harness**：挖掘某个 executor 模型特有的薄弱点；
- **HarnessX**：按需总结轨迹。

VERSE 保留 host 原来的这套失败分析，再往上加四个部件：归因、验证、训练审计、optimizer 自进化。

整个过程是双层的（bilevel）：

- **内环**：optimizer 在训练任务上编辑并检查 executor 的 harness，在预算内生成候选；
- **外环**：每轮结束后，optimizer 改自己的 harness，也就是它的 prompts、skills、tools、hooks 和 notes。

有一点要说清楚：optimizer 和 executor 两个模型的权重全程冻结。所谓“自进化”，动的只是脚手架。

### 外环能改哪五样东西

论文把 optimizer 可改写的部分列得很具体：

- Prompts：optimizer 的系统提示；
- Skills：每轮加进 prompt 的指令文件；
- Tools：它自己写、自己调用的函数；
- Hooks：在它自己循环的固定位置自动运行的代码，比如调用工具前检查一下、每轮结束加一条提醒；
- Notes：它对之前几轮的记忆。

每轮结束时，optimizer 最多花 30 个 turn 做自我更新：回顾本轮验证准确率、预期要修的任务修没修好、自己的工具使用情况和报错，然后改上面这五样。

### 归因与 trace 最小化

失败轨迹往往很长，病因藏在里面不好找。归因模块负责提出“失败轨迹里哪几步导致了失败”。为了让它好找，VERSE 加了 trace 最小化：重新执行轨迹步骤的子集，只留下仍能复现同一个错误的那几步。

论文的重放研究里，失败轨迹的中位长度从 129 步缩到 8 步。重放时每次从任务原始 commit 的新容器开始，按顺序重新执行保留下来的命令和文件编辑，工具输出来自真实环境，不来自录像。

### 三个验证工具

简单验证只跑一个训练任务、把结果丢给 optimizer 自己解读。VERSE 换成三个工具，每个在提交前回答一个问题：

| 工具 | 回答的问题 | 做法 |
| --- | --- | --- |
| verification | 草稿修好目标任务了吗？ | 在最多三个目标任务上跑草稿，报告失败的测试和 harness 代码报错；一个任务要过两次才算修好，单次通过可能是运气 |
| replay | 记录下来的失败能复现吗？ | 重新执行记录的轨迹 |
| perturbation | 失败是不是依赖某一步？ | 在可复现的轨迹里删掉或替换这一步，再重放剩下的部分 |

用论文自己的话概括：第一个工具检查修复，后两个检查诊断。

为什么要靠执行来查诊断？论文的重放研究里有个数字：让 LLM 读轨迹找人为注入的故障步骤，它只有 57.3% 的时候能指出那一步或相邻一步。读轨迹更容易认出失败的类型，定位到具体哪一步则需要执行。

理论部分也给了对应的说法：如果检查只是重新分析已有证据，选错修复的最小概率有一个下界，再怎么读也压不下去，自进化的 optimizer 也一样；能带来新执行证据、且结果依赖真实病因的检查，才能把这个上界往下压。

### 训练审计

Observation 2 里每次运行都自发记了逐轮结果，VERSE 就从第一轮开始替它记：对归因找到的每种失败模式，追踪每轮之后哪些训练任务仍因它失败。这样 optimizer 能看到哪些失败被之前的改动消掉了，哪些又回来了。

### Observation 2：optimizer 给自己造了什么

上面这些部件怎么来的？来自论文的第二个观察。作者让“自进化 + 简单验证”的 optimizer 从空工作区开始，分别配五个 executor（Qwen3.8-27B、Qwen3-Coder-480B、DeepSeek V3.2、GLM-5、Mistral Large 3），在 SWE-rebench 上各跑六轮，然后盘点它给自己写了什么。

五次运行一共产出 51 个产物，论文按主要功能分成四类：

- **归因**：找失败原因并排序的工具和 skill；
- **验证**：检查草稿能不能跑、是否真的改进 executor；
- **训练审计**：跨轮记录哪些任务修好了、哪些退步了、哪些修复后来又被撤销；
- **工作流**：改变 optimizer 自身做事方式的规则和 hook，比如预算提醒、给并行候选分工。

附录里的细节挺有意思。五次中有三次四类都覆盖；GLM-5 那次没写归因类，Mistral Large 3 那次没写验证类。每次都有训练审计和工作流类产物，但实现各不相同。DeepSeek V3.2 和 GLM-5 两次没写可调用的工具，把验证逻辑放进了提交时的 hook；Qwen3.8-27B 那次写的工具最多。有一份 skill 写的是“提交前用草稿跑 2–3 个失败任务和 1–2 个已通过任务”，这其实就是回归检查。

论文也提醒，有产物不等于产物正确或有用。Qwen3.8-27B 那次的笔记里就记着：某个分诊工具在一轮里每次调用都报错，某个探测工具误读轨迹，后来改了才好。

VERSE 的设计思路因此很直接：optimizer 自己反复造的那几类东西，从第一轮就提供给它，再让它自己决定怎么用。

### 安全边界

自己改自己的 optimizer 会引出监管问题，论文写了几条边界：

- 验证工具的内部实现固定，optimizer 只能在它们之上搭新工具；
- optimizer 自己写的代码要先过安全检查和一次试运行才加载，被拒的版本不会替换上一版；
- 所有 optimizer 操作都有日志、可审计；
- executor 只在没有网络的任务容器里行动，模型权重冻结；
- 评估与选择 harness 的方式、每轮的 turn 和执行预算也固定，保证方法之间可比。

## 实验逐项看

### 设置

主实验沿用上面的切分与模型，每次运行六轮，每轮每个方法生成三个候选 harness，提案 turn 上限相同。论文报告验证工具占演化期间全部 executor 运行的 2% 到 7%，不同方法的总算力并不相同。作者在同一协议下复现了四个 host，各自保留原来的证据接口，再分别加上 VERSE。指标是 avg@3：同一个 harness 评估三次，取解决率平均值。

### 主结果：四个 host

论文报告的 Table 2（按验证集选出的 harness，%）：

| Host | 设置 | 分布内 | OOD |
| --- | --- | --- | --- |
| 初始 harness | – | 33.95 | 27.41 |
| Meta-Harness | 基线 | 39.20 | 28.66 |
| | + VERSE（无自进化） | 34.57 | 28.35 |
| | + VERSE（自进化） | 42.28 | 37.69 |
| AHE | 基线 | 33.95 | 26.17 |
| | + VERSE（无自进化） | 37.96 | 35.51 |
| | + VERSE（自进化） | 35.19 | 30.22 |
| Self-Harness | 基线 | 29.63 | 29.28 |
| | + VERSE（无自进化） | 40.12 | 31.78 |
| | + VERSE（自进化） | 34.88 | 30.22 |
| HarnessX | 基线 | 32.72 | 27.41 |
| | + VERSE（无自进化） | 38.89 | 27.10 |
| | + VERSE（自进化） | 35.80 | 37.69 |

论文报告，带自进化的 VERSE 在 Table 2 全部 16 组对比里（两个测试集 × 选中轮/最后一轮 × 四个 host）都高于对应基线，高出 0.6 到 10.3 个点。最好的是 Meta-Harness + VERSE，分布内 42.28%，比最强基线高 3.1 个点。四个 host 分析失败的方式各不相同，VERSE 对每个都有提升，论文据此认为它不绑定某一种失败分析形式。

### OOD：Python 上学的能不能迁过去

训练、验证和分布内测试用的全是 Python 仓库，OOD 测试集则横跨 Go、Java、Python、Rust、TypeScript 五种语言，107 个任务里只有 20 个是 Python。在 Python 上改出来的 harness，换到别的语言还管不管用，这部分我觉得比分布内更有说服力。论文报告，按验证集选出的各基线在 OOD 上都和初始 harness 相差不到两个点；Meta-Harness 相对初始 harness 的提升从分布内的 5.2 个点掉到 OOD 的 1.2 个点。带自进化的 VERSE 在每个 host 上都比初始 harness 高 2.8 到 10.3 个点。按五种语言拆开，20 个“host × 语言”组合里有 16 个比对应 host 的基线更好。

### 自进化在不同 host 上并不一致

表里有个容易被忽略的地方：不加自进化的 VERSE，在 AHE、Self-Harness、HarnessX 分布内的选中分数反而比加了自进化的还高。

论文对此写得很坦白。VERSE 的工具本身就有用：不加自进化时，分布内四个 host 中有三个选出了比基线更好的 harness，高 4.0 到 10.5 个点。但不加自进化时，16 组对比里有 8 组低于基线，加了自进化则 16 组都在基线之上。和不加自进化的 VERSE 比，自进化只在 Meta-Harness 的两个测试集和 HarnessX 的 OOD 上提高了选中分数，其余五种情况下是降低的。HarnessX 上，选中轮和最后一轮在 OOD 上差了 5.0 个点。

论文的论点是：实际选 host、选轮次时拿不到测试分数，所以“每组都不低于基线”在实践中更重要。这个理由说得通，不过自进化的额外收益确实因 host 而异。

### 消融：归因报告让检查变得有意义

论文在 AHE 上做了消融（全部不带自进化，分布内，按验证集选出）：

| 配置 | 选中分数 |
| --- | --- |
| AHE + VERSE 完整 | 37.96 |
| 去掉演化中的验证（保留最小化） | 37.65 |
| 去掉归因和最小化（训练审计也随之去掉） | 35.80 |
| 只去掉训练审计 | 36.73 |
| AHE 原样 | 33.95 |

去掉归因和最小化，VERSE 给 AHE 带来的 4.0 个点里少了 2.2 个点；只去训练审计少 1.2 个点。没有这些报告时，optimizer 调用验证工具的次数是原来的三倍多，选出的 harness 反而更弱。论文的推测是：执行检查用来检验选得好的假设时最有用，而假设由诊断报告提供。

去掉验证工具几乎不影响这个 host 的选中分数，原因也写了：不带自进化的 optimizer 前九次调用全部否定了自己的草稿，第二轮之后它就不再调用了。在四个 host 上，自进化的 optimizer 调用这些工具的次数是固定 optimizer 的两倍多，最后三轮是三倍多。

Meta-Harness 上的反例也值得看：不带自进化的 VERSE 比基线和“只加简单验证”都低。它的 optimizer 一直在调用验证工具，但只测当前 harness 会失败的任务，所以永远看不出改动有没有弄坏原本能过的任务。自进化的 optimizer 在这个 host 上跑的 23 个草稿里，有 16 个把这类已通过任务也放进了目标。

附录还报告：随机或固定的实验安排、受限的工作区都会降低准确率；只允许 optimizer 改 prompts、或只改 notes 和 skills、或只改 tools，选中分数各掉三个点左右；每个候选只有一两个单位的探测预算时，大部分增益消失，四个单位起基本回来。

### 演化日志（§4.4）

我最在意的是这一节。论文汇总了 Table 1 到 Table 5 背后 18 次运行的日志：

- optimizer 每次提交改动时会写明预期修好哪些失败训练任务，结果下一轮训练里只有约十分之一真的过了；
- 验证工具做了 170 次草稿评估（含对同一草稿的重复评估），只有 17 次发现草稿修好了它瞄准的失败任务；另有 30 次，草稿只在当前 harness 已经能过的任务上通过，这些是 optimizer 为了确认没弄坏东西特意加的；
- 提交的改动在验证集上修好了 148 个任务，也弄坏了 134 个（只计重跑后能复现的变化）；
- 验证结果的变化有一半在重跑后不复现。

自进化改变了 optimizer 验证的方式：自进化组 81 次草稿评估里有 34 次带上了已通过的任务，固定组是 89 次里 2 次；检查显示草稿一个目标都没修好时，自进化组在 38 次里有 30 次（79%）先改草稿再提交，固定组是 74 次里 42 次（57%）。两组草稿修好目标的比例差不多（81 次里 9 次，89 次里 8 次）。论文的结论是，差别主要在测什么、测失败之后怎么反应。

换成我们熟悉的话：第一版补丁大多没修好；修好了的也常常顺手弄坏别的；一次“变绿”说明不了太多。

## 对日常改 harness 的启发

把这些实验数字搬到日常改 `AGENTS.md`、写 skills 的场景，我的收获有这么几条：

1. **验收先于自进化。** 让 agent 改自己的规则可以，前提是提交前能跑一遍。没有这一步，“自我改进”很可能只是把猜测越写越多。
2. **回归要带上原本能过的用例。** Meta-Harness 上的反例说明，只盯着失败用例测，看不出改动弄坏了什么。
3. **一次通过不算数。** 论文里一半的验证变化重跑后不复现，所以工具要求过两次才算修好。
4. **先把失败缩小，再下诊断。** 129 步缩到 8 步之后再问“哪一步出了问题”，比从头读整段轨迹靠谱。
5. **把修复记下来。** 训练审计本质上就是一本“哪些修好了、哪些又坏了”的账，手工维护规则文件时也可以照着记。

顺带提一句，同期还有一篇 *Self-Evolving Coding Rules for AI Coding Agents*（RuleEvolve，https://arxiv.org/abs/2610.00650），论文报告可以用 mutator 加 judge 自动进化 coding rules，judge 看正确性、代码长度和生成成本。规则文件确实可以进化，但同样离不开 judge 和测试。

## 局限

论文给出的边界，我整理成几条：

- **模型和基准有限。** 主实验的 optimizer 和 executor 都是特定的 Qwen 模型，任务来自 SWE-rebench（Terminal-Bench 结果放在附录）；Observation 1 每种设置只有一次演化运行。
- **验证集会泄漏信息。** 验证摘要既指导后续改动又用来选 harness，论文承认验证准确率可能高估测试表现，并在附录给了这个差距的上界。
- **自进化收益不稳定。** 前面说过，和不加自进化的 VERSE 比，自进化在八种情况里有五种降低了选中分数。
- **算力不等。** 不同方法的总算力不同，验证工具会额外占用 executor 运行。

从这些实验到“我改 `CLAUDE.md` 也该这么做”，中间还有不少距离。

## 写在最后

读完这篇，最让我在意的仍然是开头那件小事。我顺手补的那些规则，多数时候没关注后续带来的其他影响，和论文里“只加自进化”那一组的处境很像：只看得到失败，看不到改动之后发生了什么。

让 agent 自己改自己，这条路可以继续走。但论文日志里的数字（预期修好的任务只有约十分之一真过了，修好 148 个的同时弄坏 134 个）提醒我，改规则之前先跑一遍、改完之后再回归一遍，这一步省不得。回放和回归，往往是我们平时最先省掉的那一步。

## 参考资料

- VERSE: Verified Self-Evolving Optimizer for Agent Harnesses：https://arxiv.org/abs/2610.02616
- Self-Evolving Coding Rules for AI Coding Agents（RuleEvolve）：https://arxiv.org/abs/2610.00650

