arXiv 2605.22148 · 2026.05 · arXiv preprint 📄 Xing Zhang 等(AWS / HSBC) 🔧 Claude Opus 4.7 · Cohere embed-v4

让 agent 自己维护"经验手册"

一本越攒越烂的手册不如没有。Ratchet 用一连串消融实验,找出"自动管理"这本手册时真正不能少的几个动作。

一句话

让 LLM 把自己的踩坑经验写成可复用的"技能"攒成一个库,听起来很美,但有个反常识的事实:LLM 自己写技能,提升约等于 0;换成人来编同样的库,提升 +16.2 个百分点

差距不在"谁更会写笔记",而在"有没有人在管理这个库"——没人管,库会混进坏经验、无节制膨胀,最后比没有还差。Ratchet 把这件事拆成一个个"管理动作"逐一消融,结论是:冻结模型不动,只把"图书管理员"这个角色做扎实,自动管理的库就能追上人工编的水平,把 MBPP+ 难题的 pass@1 从 0.258 推到 0.658

+0.0pp
LLM 自己写技能
(SkillsBench 对照)
+16.2pp
人来策划同样的库
(提升来自"管理")
+0.328
Ratchet 自动管理后
100 轮平均增益
0.258→0.658
pass@1
(起点 → 峰值)

让 agent 自己写"经验手册",为什么几乎没用?

自进化 agent 的一个朴素想法:让模型每次做完题,把"这类题怎么解、容易踩什么坑"写成一条可复用的技能,攒进一个技能库;下次遇到相似的题,先翻库里有没有现成经验可参考。模型权重不动,靠这本越攒越厚的手册让自己变强。

但对照基准 SkillsBench 给出了一个泼冷水的结果:让 LLM 自己写这些技能,对成绩的贡献是 +0.0pp;而换成人来编一套同样格式的技能库,提升有 +16.2pp。同一个模型、同样的题、同样的"翻手册再解题"流程,唯一的差别是谁在维护这个库

Ratchet 的诊断是:LLM 写出来的单条技能其实质量不差——给它一个具体失败案例,它能准确说出原因、写出格式清晰的经验。问题不在"写得好不好",而在"没人管这个库"。库一旦放任自流,就会以三种方式烂掉:

✍️ 单条技能:LLM 已经够用

给定一个具体的失败样例,LLM 能:

  • 准确归纳出"这次为什么错"
  • 写出字段规整、逻辑清楚的经验卡片
  • 所以瓶颈不在"会不会写"

📚 整个库:没人管就会烂

  • 坏经验不下架 → 帮倒忙的技能一直被翻出来用,持续拖累成绩
  • 无节制膨胀 → 库越来越大,"挑到对的经验"越来越难
  • 同一个坑写出 N 条 → 风格各异、语义重复,互相干扰
这就是"手册退化"(library drift):库里"有效经验的占比"被慢慢稀释、污染,直到自进化的效果还不如一开始什么经验都没有。SkillsBench 里 +0.0pp 的根因就是它。

所以真正的问题被换了一个方向:不是"怎么让 LLM 写出更好的技能",而是"怎么自动管理这个库,让它别退化"。Ratchet 就是冲着这个问题去的。

冻结模型,只把"图书管理员"做扎实

Ratchet 的设定很克制:同一个冻结的 LLM 扮演所有角色,不改一丝权重,整个系统的"成长"只发生在外部的库和账本里(用 SQLite 持久化)。它把流程分成三条带子——上面"解题"、中间"存东西"、下面"复盘+管理库"。下图是全貌,看不懂没关系,紧接着用人话拆解每个角色。

Ratchet 系统架构:推理带 / 记忆带 / 反思带三层
Ratchet 系统全貌:蓝色"推理带"负责做题,黄色"记忆带"存技能与战绩,绿色"反思带"复盘并维护库。实线是数据流,虚线是读/写记忆。▶ 逐层解读
  • 推理带(蓝,每道题走一遍):题目 → 挑笔记(Router 从库里选 0–1 条相关经验)→ 解题(Solver 读着经验写代码)→ 判分(Grader 跑测试,对/错)→ 把这次 (题, 用了哪条经验, 对错) 打包成一条战绩记录
  • 记忆带(黄,持久化、只追加):技能库(所有经验,含已下架的,永不物理删除)、模板(一份"好经验长什么样"的写作规范,全局唯一)、战绩账本(所有战绩记录 + 复盘结论)。系统的全部"状态"都在这三处,模型本身不变。
  • 反思带(绿,每轮跑一次):复盘(Critic 给每条失败记录归因:经验是帮了倒忙还是没用上)→ 写新经验(Synthesizer 把反复出现的同类失败照模板写成新技能)→ 清理(Curator 按战绩把长期帮倒忙的技能下架)。另有一个慢周期的重写模板(Meta-Synth,默认关闭)。

六个角色,都是同一个 LLM + 一本库,换个提示词而已

页面后面会反复提到这几个角色。这里给一张"人话对照表",记住左边的动作名就够了,括号里的英文只是方便对回原文:

推理 · 做题
挑笔记 Router
从库里挑 0 或 1 条最相关的经验,塞给解题的人。库大了先粗筛再让 LLM 拍板选哪条。
推理 · 做题
解题 Solver
冻结的 LLM 读着挑出来的经验,把题做了。单次调用,不反思、不调工具。
推理 · 做题
判分 Grader
跑测试看对错。这是"客观战绩"的唯一来源,写进战绩账本。
反思 · 管库
复盘 Critic
对每条失败记录归因:这次用的经验是帮了倒忙、没用上、还是无关。只用固定标签,避免乱编理由。
反思 · 管库
写新经验 Synthesizer
把最近反复出现的同类失败,照"模板"总结成一条新技能入库。
反思 · 管库
清理 Curator
按账本算每条经验的战绩,长期帮倒忙的下架(改状态,不删除)。库满了也淘汰战绩最差的。
记住一个对应关系:"挑笔记 / 解题 / 判分"是这本手册;"复盘 / 写新经验 / 清理"是这本手册。整篇文章的核心发现,全在"管"这一侧。

一轮里发生了什么:做题 → 复盘 → 更新手册

Ratchet 按"轮"运行。每一轮先用当前手册做一遍题(这是上报成绩的地方),再专门制造一批失败案例供复盘,然后复盘、写新经验、清理。本轮"管"出来的手册,就是下一轮"用"的手册——这样一圈圈滚下去。点"下一步"跟着走一遍:

第 1 / 5 步
① 考试
② 练习
③ 复盘
④ 写新经验
⑤ 清理

第 1 步 考试 Eval Pass

拿当前手册在 40 道"考试题"(held-out)上正常做一遍:挑笔记 → 解题 → 判分。这一遍的 pass@1 就是论文上报的成绩

  • 只允许"在用中"(ACTIVE)的经验参与
  • 考试题拿去复盘,纯粹用来量成绩
  • 每题只跑一次(无采样),所以成绩自带约 ±0.07 抖动
一条考试战绩
题目mbpp_hard_017
用了哪条经验滑窗边界保护
结果
第几轮42
用途只计成绩

第 2 步 练习 Train Pass

在另外 60 道"练习题"上跑同一条流程,目的是制造可供分析的失败案例,不上报成绩。

  • 失败的题 → 送去复盘;做对的题 → 计入战绩统计
  • 冷启动阶段允许"候选经验"(刚写、还没验证够)也来试手
  • 考试题和练习题严格分开,避免"对着答案学"
一条练习失败记录
题目mbpp_hard_053
用了哪条经验边界检查 v2
结果
第几轮42
→ 去向送复盘归因

第 3 步 复盘 Critic

对每条失败记录,让 LLM 以"复盘"身份给一个归因结论:这次用的经验到底起了什么作用。

  • 看的是:题目 + 用了哪条经验 + 模型输出 + 判分反馈
  • 输出:帮了倒忙 / 有帮助 / 无关 / 不适用,外加一个失败"花样"标签和置信度
  • 固定标签而非自由发挥,防止 LLM 编造归因;"诊断"和"写经验"分开是关键设计
一份复盘结论
作用判定帮了倒忙
失败花样边界差一位
置信度
原因经验教成闭区间,测试要开区间

第 4 步 写新经验 Synthesizer

翻最近 6 轮的复盘结论,把同一个失败花样反复出现 ≥3 次的,照"模板"写成一条新经验入库。

  • 写的时候带上"模板"一起给提示词,保证风格统一、字段规整
  • 产出 YAML → 校验格式 → 和库里现有经验去重 → 转为"在用"
  • "模板"这一步是后面消融里损失最大的一环(先记住)
新写的一条经验(片段)
id: boundary_off_by_one
适用场景: 用 range / 切片且涉及端点
关键提示: Python 的 range/切片右端是开区间
常见坑:
  - 末位是 n 时误写成 range(0, n+1)
返回前检查: 单独验一下 n 这个边界
状态: 在用 (ACTIVE)

第 5 步 清理 Curator

用战绩账本给每条"在用"经验算一个战绩分,把长期帮倒忙的下架。这是全文最 load-bearing 的动作。

  • 战绩分 = (做对次数 − 做错次数) / 总试用次数
  • 下架条件:试用次数 ≥ 100(证据够多) 战绩分 ≤ −0.10(确实拖后腿)
  • 下架 = 改状态为"已弃用",记录永久保留,可审计、可恢复
  • "在用"经验超过上限 50 条时,自动淘汰战绩分最低的
一次清理决策
经验边界检查 v1
试用次数128
战绩分−0.14 ≤ −0.10
决策→ 弃用
记录保留在账本
还有一道保险(回滚):如果某轮考试成绩比历史最高点低 0.10 以上,且连续 5 轮都这样,系统才回滚到当时最好的那版手册快照。"连续 5 轮"这个门控,是为了不被单轮的随机抖动吓到、误触发回滚。

真正的问题:哪些"管理动作"不能少?

全文围绕的一个问题

上面那套"复盘 / 写新经验 / 清理 / 去重 / 容量上限 / 回滚……"看着都挺合理,但哪些是真不能少,哪些其实是摆设?Ratchet 的做法很直接:每次只砍掉一个动作、其余不变,跑 100 轮 × 3 个随机种子,看学习曲线掉多少。衡量用 rolling gain(最后 10 轮均值 − 最前 10 轮均值),比看峰值更稳。

100 轮学习曲线:Default 与 A1–A8 消融对比
100 轮里各配置的考试 pass@1(3 个种子 ±1 标准差)。看分层就够了:最上面一簇是完整配置和"去掉冗余机制"的几条;中间那条偏低的是"去掉模板";贴着底部那两条(灰、紫)是"完全没技能"和"退役太激进"。▶ 数据解读
  • 完整配置 +0.328:稳定收敛到 0.58 上下,峰值 0.658。
  • A1 完全不给技能 +0.002:贴着底,证明所有提升都来自技能本身。
  • A2 只检索、不让 LLM 拍板挑 +0.077:只剩约 1/4 的提升。
  • A3 去掉"模板" +0.187:明显掉一档,单项损失最大。
  • A4 退役太激进 −0.019:唯一掉到"没技能"以下的配置。
  • A5 / A6 去掉两种去重 ≈+0.37:不降反升,略好于完整配置。
  • A8 定期重写模板 +0.372、峰值 0.725:峰值最高,但耗时多 55%。

先确认前提:提升真的来自技能吗?

A1 把技能注入整个关掉 对照组

强制每道题都"不挑任何经验",整个库形同虚设。结果 +0.002——几乎不涨。这条确立了基线:Ratchet 的全部增益确实来自技能这套机制,而不是别的什么。后面所有对比都以此为锚。

三个"砍了就疼"的动作

A4 退役规则:必须"温和 + 要够多证据" load-bearing

把退役阈值调狠(试用满 20 次、只要不是正贡献就退),结果 rolling gain 掉到 −0.019是唯一比"完全没技能"还差的配置

原因是统计噪声:样本太少时战绩分极不可信——只试 20 次的偏差可能高达 ±0.44,意味着真正有用的好经验,常因为早期几次倒霉失败就被误杀。把门槛提到"满 100 次且战绩分 ≤ −0.10",才挡住了这种误退役。

启示:"自动退役"不是越积极越好。退役决策必须基于足够多的实测战绩,而不是急着清场。

A3 写经验的"模板":单项损失最大 load-bearing

去掉写新经验时附带的那份"模板"(一份 Markdown 写作规范:该写哪些字段、什么风格、禁止空泛建议),rolling gain 从 +0.328 掉到 +0.187,损失 −0.141,是所有单项消融里最大的。

关键洞察:模板不只是"统一格式"。它逼着每条新经验都长得规整、表达同质,顺手就把"同一个坑写出好几条"的重复问题给消化掉了——这一点在下面会再次出现。

A2 挑笔记:让 LLM 拍板,而不只是检索 load-bearing

"挑笔记"分两步:先按相似度粗筛出几条候选,再让 LLM 读着候选拍板"选哪条、还是干脆不选"。只保留检索、砍掉 LLM 拍板这一步后,只剩 +0.077,约等于完整提升的 1/4。"选不选、选哪条"这个判断,贡献了约 76% 的挑选价值

一个"砍了反而更好"的反直觉发现

A5 / A6 显式去重:在当前规模下其实是负担 反直觉

系统本来有两个去重机制:把语义相近的失败花样合并(A5),以及新经验和已有经验太像就跳过(A6)。直觉上这能防止库里堆重复。但把它们分别砍掉,rolling gain 反而略升+0.374 / +0.363

原因正是上面那条:"模板"已经让新经验高度同质,本来就没多少真重复可去。这时候显式去重的误伤(把其实有用的细微差别也当成重复给合并/跳过)反而成了净损失。

启示:这些"卫生机制"不是各自独立、越多越好。模板和显式去重在很大程度上是替代关系——有了前者,后者就成了多余。这是消融实验最有价值的结论之一:加机制要看它和已有机制是叠加还是替代。

两个"现在用不上、但别删"的设置

A7 容量上限:当前不是瓶颈,但放开会变不稳 理论必要

把"在用"经验的上限从 50 翻倍到 100,均值 +0.317 和完整配置差不多——说明当前规模下 50 这个上限并不卡脖子。但方差从 ±0.018 暴涨到 ±0.110:更大的库带来明显的不稳定。而且下一节会看到,这个上限在理论保证里不可或缺——去掉它,"不会越学越差"的保证就崩了。

A8 定期重写模板:一个可选的"加速档" 可选

让"模板"本身也每 10 轮被重写优化一次。峰值确实拉到全场最高 0.725,rolling gain +0.372,但代价是耗时多 55%(10.1 小时 vs 6.5 小时)。所以默认关闭:算力换那点峰值,作者认为不划算。

▸ 完整数据表(Default vs A1–A8,MBPP+ hard-100,100 轮 ×3 种子)
配置起点 (round 0)峰值rolling gain
Default 完整0.258 ± 0.0470.658 ± 0.042+0.328 ± 0.018
A1 不给技能0.283 ± 0.0310.375 ± 0.000+0.002 ± 0.005
A2 只检索不拍板0.242 ± 0.0120.492 ± 0.042+0.077 ± 0.065
A3 去掉模板0.200 ± 0.0350.592 ± 0.047+0.187 ± 0.036
A4 退役太激进0.300 ± 0.0350.433 ± 0.042−0.019 ± 0.010
A5 去掉花样合并0.275 ± 0.0200.708 ± 0.012+0.374 ± 0.023
A6 去掉相似跳过0.217 ± 0.0240.700 ± 0.035+0.363 ± 0.033
A7 上限=1000.292 ± 0.0420.650 ± 0.089+0.317 ± 0.110
A8 定期重写模板0.250 ± 0.0350.725 ± 0.020+0.372 ± 0.017
这一节的总账:真正不能少的是退役(要温和+够证据)、写经验的模板、让 LLM 拍板挑笔记三件;容量上限当下不卡但理论上必需;而显式去重在有模板时反而是负担。一句话——有用的不是"机制堆得多",而是几个互补、彼此不冗余的关键设置

一个"至少不会越学越差"的承诺

自进化系统最让人担心的是失控下滑:库越攒越烂,成绩一路滑到谷底。Ratchet 给了一个简单但有用的保证:在几条温和假设下,期望成绩不会比"完全没技能"低过一个有限的差距,不会无限下滑。注意——这叫"非发散",是"不会变得太差"的下限保证,不是"一定会变好"。

命题 1 · 期望意义下的非发散

大意:只要 (1) 挑笔记这步本身不乱来、(2) 战绩分是对真实贡献的无偏估计、(3) 退役所需的试用次数足够多(用 Hoeffding 不等式把估计误差压到 ε 以内),那么——

E[pass@1] ≥ E[没技能成绩] − (τ + ε) − C · δ

也就是:期望成绩相对"没技能基线"的下滑,被三个量牢牢锁住——退役阈值 τ、估计误差 ε、以及库容量上限 C(δ 是小概率项)。每一项都是有限的,所以总下滑有限。

📐 代入默认参数

τ=0.10、试用满 100 次、C=50、δ=10⁻³:

  • 估计误差 ε ≈ 0.20
  • C · δ = 0.05
  • 下限 = 没技能成绩 − 0.35

实际系统从没接近过这个下限(真跑出来是 +0.328),但它保证了最坏情况也只是有限损失。

💥 为什么前作没有这个保证

Voyager / ExpeL / AutoManual 这类工作,库可以无限膨胀(C→∞)、也没有像样的退役阈值(τ 无下界)。

代进上面的式子:C→∞ 时下限直接崩到 −∞——也就是理论上允许无限下滑

所以"容量上限 + 退役阈值"不只是工程调参,而是让系统在数学上不发散的最小必要结构。这也解释了 A7 里"容量上限当下不卡、但不能删"。

三个带走的结论 + 四个要当心的地方

最后把全文收成一条线:自进化的瓶颈在"管库"不在"写技能";管库真正关键的是少数几个互补动作;它们还能给出一个不发散的下限保证。

💡 关键结论

"+0.0pp"的真凶是缺管理,不是缺能力。LLM 单条技能写得不差,是缺了"按战绩退役"和"写作模板"这两件事,库才退化的。补上它们,自动管理就追上了人工策划。
以实测战绩为准,不信 LLM 自评。新经验从"外部判分出的失败"里长出来,退役也只看实测战绩分,不依赖模型自己说"我需要什么技能"。这让账本可信、退役可审计。
机制之间会替代,不要无脑叠加。有了"模板",显式去重反而成负担。判断一个机制该不该留,要看它和已有机制是互补还是冗余。

⚠️ 局限与开放问题

是放大器,不是发现者:权重冻结,只能重新调度模型已有的能力,没法教会它真正全新的知识。
SWE-bench 上的结果还很初步:换 agentic Claude Code 当解题器迁移到 SWE-bench Verified,20 轮拿到峰值 +0.22(0.65→0.87,最好种子 0.92),但只跑了 20 轮、单模型单供应商,跨模型稳定性未验证。
复盘可能归错因:错误的失败归因会把伪规律固化成"经验"。固定标签能缓解,但缺少原则性的校准。
战绩分是相关不是因果:战绩分只是观测统计,无法区分某条经验到底是真有用,还是恰好和别的因素一起出现。
Ratchet figure