目录

没有验收的自进化,会把错的教训写进 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