ryos

把自进化闭环做好:从 Bad Case 到 Harness Patch

上一篇《Agent 自进化到底在进化什么》最后收束到一个闭环:

run -> trace -> failure attribution -> proposal -> controlled patch
-> train / selection / protected eval -> accept or reject
-> deploy or rollback -> continue collecting evidence

这篇接着讲下一步:怎么把这个闭环做好。

闭环画出来并不难,难的是每一环都不虚。trace 要能支持归因,归因要能指向具体 harness 层,proposal 要有边界,验证要能发现回归,accept / reject 要能留下证据。否则流程看起来完整,实际只是把 bad case 丢给模型,让它反思几句,再把反思写进 prompt 或 memory。

真正有价值的自进化闭环,应该像一个实验系统。它要能把失败轨迹变成可定位的问题,把问题变成有边界的修改,再用评估决定这个修改是否值得保留。

所以这篇文章真正想讨论的是这条链路怎么做实:

bad case -> failure attribution -> harness patch -> regression gate

而不是:

bad case -> reflection -> longer prompt

Bad Case 本身不是经验

很多系统都会收集 bad case。问题是,bad case 本身并不自动等于经验。

一个任务失败了,可能有很多原因。

模型可能没有看到关键信息。也可能看到了,但是没有用上。可能是工具描述太模糊,导致它选错工具。也可能是工具本身缺少校验。还有可能是验证器太弱,模型以为做完了,其实只是产物看起来像完成。

如果只是把这些失败打包给模型,让它总结“下次要更仔细”,通常不会有稳定收益。这类总结太宽,既不知道应该影响哪个环节,也不知道什么时候该触发,更不知道会不会伤害原来能做对的任务。

所以第一步不是总结经验,而是把失败结构化。

一个有用的 bad case,至少要回答三个问题:

最终失败原因是什么?
哪个 agent 行为和失败有因果关系?
背后的可复用机制问题是什么?

比如同样是 timeout,原因可能完全不同。

一个 case 是外部下载太慢,agent 没有设置合理超时。另一个 case 是 agent 一直探索文件,却迟迟不写结果。表面上都是 timeout,但一个应该改工具策略或网络退避,另一个应该改执行流程和停止条件。

如果把它们聚在一起,只会得到一句空泛建议:遇到超时要更谨慎。这样的“经验”没有什么用。

归因之后,先定位该改哪一层

失败被归因之后,下一步不是马上改 prompt,而是判断:这个问题应该落到 harness 的哪一层。

这里有一个重要的区分:归因回答的是“什么机制坏了”,分层回答的是“应该在哪修”。归因告诉你 agent 在哪个环节做出了错误行为,分层告诉你修改应该作用在 harness 的哪个位置才能稳定地改变这个行为。两个问题都答清楚,才能从“知道哪里不对”走到“知道怎么改”。

上一篇我把 harness 按组件拆成了 prompt、memory、tools、skill、workflow、workspace、policy、evaluation。那是为了说明 harness 里到底有什么东西可以被进化。

这一篇要讨论的是“在哪里修”,所以我换了一个视角,把 harness 按功能层来切:

Context: 模型看见什么,信息如何压缩和排序
Memory: 哪些历史经验被保存、召回和信任
Skill: 可复用流程和任务方法
Protocol: 工具、API、输出格式和状态机契约
Orchestration: 控制循环、重试、终止和子 agent 协作
Verification: 结果检查、测试、rubric 和回归验证
Governance: 权限、审批、审计、回滚和成本边界

这个切法也延续了上一篇提到的几类工作:GEPA 更偏 prompt 候选的演化和选择,SkillOpt 更偏 skill 文档的更新,Agentic Harness Engineering 则把 prompt、tool、middleware、memory 等组件都纳入可观察、可修改的 harness。这里我关心的是更工程化的一步:失败归因之后,修改到底应该落在哪个功能层。

这个分层的价值是,它逼迫我们不要把所有问题都塞进 prompt。

如果模型经常忘记验证结果,可能确实可以改 instruction。但如果它经常误用某个工具,更好的位置可能是 tool description 或 wrapper。如果它经常输出非法格式,应该加强 protocol 和 schema checker。如果它会删除关键产物,靠 prompt 提醒不够,应该在工具层加硬拦截。

经验应该落在最能稳定改变行为的位置。

有些经验适合写进 prompt,因为它是通用行为纪律。

有些经验适合沉淀成 skill,因为它是一套可复用流程。

有些经验适合写进 memory,因为它和某个项目、用户或历史决策有关。

有些经验应该变成 verifier,因为它本质上是合法性检查。

还有一些经验应该直接变成代码策略。比如路径权限、危险命令、API 参数约束、状态机流转,这些不应该靠模型“记得”,而应该由 harness 执行。

先出 Proposal,不要直接改生产系统

当我们知道应该改哪一层后,下一步很容易冲动:直接动手改 prompt、改配置、改工具描述。但这很危险。

直接修改生产系统,意味着你同时在做三件事:提出假设、执行修改、承担后果。一旦改坏了,你分不清是假设错了还是改法粗糙。更麻烦的是,如果同时改了多处,出了问题根本不知道回滚哪里。而且生产环境里的真实任务会被当成实验品。这对用户不公平,对系统也不安全。

所以正确的做法是先生成 proposal。

一个合格的 proposal,不只是一个 patch。它应该同时说明:

它针对哪个 failure pattern;
它修改哪个 harness 层;
它预期改变什么行为;
它可能带来什么回归;
它如何被验证。

这个区别很重要。

如果只看 patch,很容易陷入“这句话看起来挺合理”的判断。但自进化系统需要的是实验记录,而不是灵感记录。每个修改都应该能追溯到一个失败模式,也应该能在后续评估里被证实或证伪。

这也是为什么 proposal 要有边界。

一次修改最好只解决一个清晰问题。不要因为几个 bad case,就重写整段系统提示词、重排整个 workflow、顺手加一堆工具规则。那样即使分数涨了,也很难知道到底是哪一部分起作用。分数跌了,更不知道该回滚哪里。

好的自进化修改应该像小实验:

假设:某类任务失败,是因为 agent 太晚创建 required artifact。
修改:在 execution instruction 中提前 artifact 初始化要求。
预期:missing artifact 类失败减少。
风险:可能让 agent 过早写入低质量产物。
验证:检查 held-in 中相关失败是否修复,同时看 held-out 是否出现新回归。

这比“让 agent 更注意创建文件”有用得多。

验证比生成更重要

很多自进化讨论把重点放在“怎么生成更好的 proposal”。但从工程角度看,更重要的是:谁决定 proposal 是否保留?

模型可以提出建议,但不能自己说自己变好了。

一个 proposal 至少要过几道门:

schema 是否合法;
是否只修改允许编辑的 harness 层;
是否触碰 forbidden 边界;
patch 是否能干净应用;
harness 是否能 load / compile;
dry-run 是否通过;
train 上是否修复目标失败;
validation / protected set 是否没有明显回归。

这里的 protected set 很关键。它是一组不容退化的核心任务:这些任务上,任何 proposal 都不能让原来能过的 case 变成失败。

因为一个 proposal 可能真的学到了一点东西,但只适用于很窄的任务族。比如它提升了 broad research 类任务,却伤害了 local guidance、流程归因或精确推荐。只看平均分,可能会误以为它是好修改。

所以评估不能只看 aggregate score,还要看 case-level delta 和 task-family delta。

哪些 case 从 fail 变成 pass?哪些 case 从 pass 变成 fail?收益集中在哪类任务?退化又集中在哪类任务?成本、延迟、工具错误率有没有变差?

这也是 Harness Updating != Harness Benefit 那个区分在工程里的落点:会产生修改,只说明系统有 updating capability;修改在 protected set 和 held-out 上站得住,才说明它有 benefit。

这些问题不回答,自进化就很容易变成“看起来一直在更新,但不知道有没有真的变好”。

不要只选平均分最高

还有一个容易被忽略的问题:候选选择。

直觉上,我们会选平均分最高的 proposal。但在 harness evolution 里,这不一定对。

不同 proposal 可能解决的是不同 failure family。

一个 proposal 擅长让 agent 更早验证产物。另一个 proposal 擅长减少无效工具调用。还有一个 proposal 擅长处理格式和协议错误。它们的平均分可能差不多,甚至某个 proposal 平均分不最高,但在一组关键 case 上明显更好。

如果每轮都只保留平均分最高的候选,很容易过早收敛到一种策略,把其他有潜力的路线丢掉。

更好的方式是保留一组没有被完全支配的候选。也就是说:如果一个候选在所有维度上都不如另一个候选,并且至少一个维度更差,那它可以淘汰;但如果它在某些 case 或某些任务族上有独特优势,就应该暂时留下。

这就是 Pareto frontier 在这里的价值。

它不是为了让流程看起来更高级,而是为了避免平均分掩盖策略分化。

当然,Pareto 不能替代安全门槛。越权、破坏验证、污染数据、明显回归的 proposal,不应该因为在某几个 case 上有收益就被保留。正确顺序应该是:

先过 hard gate;
再进入 Pareto selection;
最后再决定 promote、merge 或继续观察。

Merge 需要重新验证,组合可能互相抵消

保留多个候选之后,还有一个问题:能不能把几个好 proposal 合并?

答案是可以,但要谨慎。

Harness 组件之间不是线性相加。举一个例子。

假设有两个 proposal,各自在独立评估中都表现不错:

两个 proposal 各自有效,于是有人想把它们合并。

合并之后问题出现了:Proposal A 的每步检查让对话轮次快速膨胀,很快触发 Proposal B 的压缩阈值。压缩后的摘要丢失了产物完整性信号,检查器频繁误报,agent 反复重试,进一步推高轮次,触发更多压缩。最终 timeout 率反而比 baseline 更高,成本也涨了。

这个例子说明:两个各自有效的修改,组合后可能通过重复验证、上下文变长、信号丢失、工具调用增加和超时形成干扰,把单点收益吃掉。

所以 merge 之后不能默认更好,必须重新跑验证。必要时还要做 component ablation:只打开其中一个组件,看收益来自哪里;再组合起来,看是否互相抵消。

这也是很多自进化系统容易变成技术债的原因。每次修改看起来都合理,但长期累积后,prompt 变长,memory 变脏,workflow 变复杂,工具规则互相覆盖。系统不是进化了,而是长胖了。

被拒绝的 Proposal 也有价值

自进化系统不应该只记录 accepted proposals。被拒绝的 proposal 同样重要。

被拒绝的原因可以归为两类。

第一类是工程门禁失败。修改在到达评估之前就应该被拦下:

schema failure: 格式不合法;
boundary failure: 越过 editable / forbidden 边界;
application failure: patch 无法应用;
activation failure: 没有识别出该进化;

第二类是学习质量失败。修改通过了工程门禁,但评估证明它没有价值或有害:

attribution failure: 归因错了;
generalization failure: train 有效,validation 退化;
duplicate failure: 和之前拒绝过的方向高度相似;
benefit failure: 合法也能跑,但没有稳定收益。

这些记录会形成 reject registry。它的作用不是给系统记黑账,而是避免反复探索同一条无效路线。

如果几轮 proposal 都在重复“加更多检索”“加更长 planning”“加更严格输出格式”,但每次都伤害相邻任务,那问题可能不在 proposal 写得不够好,而在可优化空间、评估设计或失败归因本身。

会拒绝,才说明系统真的在学习。

最终测试必须隔离

训练集用来产生反馈,验证集用来选择候选。多轮迭代之后,验证集本身也会被间接过拟合。

所以最终还需要一个不参与选择的 test set。更严格一点,还应该有 challenge set、跨任务族评估、跨模型评估,甚至换不同 timeout、成本预算和工具环境再跑一次。

这听起来麻烦,但它决定了我们能不能区分两件事:

系统修复了当前 benchmark 的常见失败;
系统真的学到了更通用的执行结构。

很多自进化结果,本质上证明的是 in-domain repair。这个价值已经不小,但它不是开放式自我进化。把前者讲成后者,会让系统设计变得危险。

把闭环每一环做实

把这些串起来,上一篇那条自进化闭环可以展开成更具体的工程版本:

run baseline
-> save full trace
-> mine failure patterns
-> locate harness layer
-> generate bounded proposal
-> validate legality
-> evaluate train / validation / protected set
-> select with hard gates + Pareto frontier
-> promote / reject / merge
-> log accepted and rejected candidates
-> final held-out test

这套流程的重点不在于多了几个步骤,而在于每一环都有明确产物。

trace 不是日志堆积,而是能支持失败归因的证据。

failure attribution 不是一句“模型不够仔细”,而是能说清楚哪个行为、哪个机制、哪个 harness 层出了问题。

proposal 不是灵感,而是带有目标失败模式、修改位置、预期收益和回归风险的小实验。

evaluation 不是跑一个平均分,而是看 case-level delta、task-family delta、protected set、成本和工具错误。

accept / reject 也不是拍脑袋,而是把通过的修改、失败的修改和重复的方向都写进 registry。

所以,做好自进化闭环的关键不是让系统更会反思,而是让失败轨迹稳定地转化为可验证的 harness patch。闭环真正成立的标志,是每一次修改都能回答:

这个失败模式是什么?
应该改在哪一层?
proposal 的边界是什么?
验证证明了什么?
有没有伤害原来能做对的任务?
如果错了,怎么回滚?

这些问题回答清楚,自进化闭环才不是一张流程图,而是一个可以长期运行的工程系统。

说到底,自进化首先是反馈、评估和治理问题。把 bad case 变成 harness patch 的每一步,都是在为这个判断补上工程细节。

所有文章