- 两个递归交替:内递归固定模型、搜索更好的 harness(流程代码);外递归固定 harness、用 GRPO 训练模型。选中的流程跨轮继承。
- 最见功力的是证据隔离:私有参考答案、数值打分、Val 轨迹、Test 结果全部不得进入改进器的学习回路。
- 配方公开:Qwen3.5-4B、715/90/90 冻结任务集、3 轮 h→r、每阶段 3 步 × 16 交互 × 3 候选、30 次 RL 更新、学习率 1e-6。
- 门槛不低:Linux + CUDA 13 + Docker + 8 张 A100,模型权重与任务数据需另外授权。
- 五个边界它自己写了:Test 不是独立测试集、反馈是模拟的、产品演示 ≠ 实验、完整源码未开源、默认跑法是 debug 模式。
一、先看它想解决什么问题
自我改进是 Agent 领域最诱人的方向,也是最容易翻车的地方。目前常见的做法有两类,各缺一半:
- 只改提示词与工作流:让模型自己写更好的 prompt、更好的工具编排。流程确实能变好,但模型本身没变,能力天花板就卡在那里。
- 只做 RL 训练:用奖励去调模型权重。但训练用的「经验」来自固定流程,流程本身的缺陷会被固化下来,越训越偏。
ScienceBuddy 的思路是把这两件事交替做,并且互相喂料:
- 模型不动,改 harness——也就是引导 Agent 干活的那个 Python 程序;
- harness 不动,用新流程产出的轨迹去训练模型;
- 把更新后的模型,交给下一轮的流程改进。
项目把这个结构叫 recursive-in-recursive self-improvement(递归中的递归自我改进),简称双递归 RSI。
二、两个「递归」到底是什么
先把两个角色定义清楚:
- 任务模型 M_k:负责生成 Agent 的回复和工具调用决策。k 是模型学习轮次。
- harness H_{k,j}:一个 Python 程序,负责组织提示、调用模型、执行工具、管理上下文、提交答案。j 是同一轮内的流程更新步数。
- 辅助模型:独立于前两者,负责提 harness 修改建议。它的参数在任务模型和 harness 演化期间始终冻结。
两个递归的定义式:
内递归(固定 M_k,搜更好的 harness)
H_{k,j+1} = Select(H_{k,j}, C_{k,j}; M_k, D_val)
外递归(固定选中的 harness,RL 更新模型)
M_{k+1} = GRPO(M_k; H_{k,J}, D_train)
且 H_{k+1,0} = H_{k,J}
C_{k,j} 是从当前交互证据里提出的候选,J 是每阶段的 harness 步数。
最关键的一行是 H_{k+1,0} = H_{k,J}:选中的 harness 会被下一轮继承。这才是「递归」的真正来源——不是每轮从零开始,而是把上一轮最好的流程当作起点,一轮轮往上垒。
默认排期是三轮:
(M0, H0)
→ h0001: 学流程 → r0001: 学模型 → M1
→ h0002: 学流程 → r0002: 学模型 → M2
→ h0003: 学流程 → r0003: 学模型 → M3
跑完 r0003 就结束,不再加第四个流程阶段。
为什么这个交替不是花架子
因为两者互为环境:
- harness 变了 → 模型收集经验的方式变了;
- 模型变了 → 下一轮里「哪些流程改进有效」也变了。
这跟「只调 prompt」或「只做 RL」的根本区别就在这里:单一方向的优化,会把另一侧的缺陷锁死。
三、内递归:流程是怎么被改掉的
这是整套设计里信息量最大的部分。四个关键环节:
3.1 交互怎么变成「可用证据」
反馈协议是验证器辅助的,一共四步:
- 宿主用私有参考答案给提交的答案打分;
- 宿主构造一个「允许的研究员回复集合」,选项区分四类情况:接受、答案格式无效、最近出现过工具报错、泛化的「再想想」;
- 辅助模型从这个集合里选一条回复。如果输出不合法,就回落到允许的模板,并且把这个回落记录下来;
- 一个 PRM 风格的解释请求,把这条回复和公开执行记录关联起来——模型输出、工具观察、提交的答案、这条回复。
之后宿主会校验它的结构、字段地址和反馈分类。
这里有个容易被忽略的细节:PRM 的解释只被当作假设。文档明确写了,一次泛化纠错并不能确立某个具体的科学原因,一次「接受」也不等于验证了中间每一步。
3.2 公开证据与私有答案的隔离
提案证据包含:公开问题、真实的模型输入输出、工具调用、观察、有效的反馈标注、执行诊断。
提案证据排除:私有参考答案、数值打分结果。
而且,无效的 PRM 标注不会连带删掉底层的公开执行记录——记录归记录,解释归解释。
当前步的证据还会与「同阶段内有限选取的早期公开失败/纠正与成功对照」合并。一旦某个候选被采纳,待定证据会被清空,让下一轮搜索反映的是新 harness,而不是旧流程的旧问题。聚合的提案历史则跨模型更新保留。
3.3 提案必须是完整程序,而且得拿证据说话
每一步从同一个父代请求 3 个独立候选假设。所有提案在候选的 Val 结果揭晓之前就全部生成完——避免候选之间互相抄答案。
每个候选可以整体替换 run(task, api) 程序,覆盖范围包括提示、解析、控制流、记忆、工具编排。
改进器拿到的接口只有三个命令:
| 命令 | 用途 |
|---|---|
read | 查看某个允许的公开字段,比如真实的模型输入或工具观察 |
propose | 返回完整 Python 程序、假设、理由和证据 ID |
skip | 说明为什么现有证据不支持做出改动 |
约束很硬:
- 一个提案必须引用至少两个不同 Train 任务的证据;
- 每个候选最多 3 次 read、2 次修复尝试、6 次辅助调用;
- 宿主解析候选 Python,但不执行它;
- 结构校验要求同步的 run(task, api) 接口,并拒绝已知的无效指令模式。
「必须引用两个不同任务的证据」这条特别值得注意——它挡住了「从一个案例里硬推出一条通用规则」这种最常见的过拟合。
3.4 怎么选出更好的那一个
结构合法的候选,必须先在指定的 preflight 任务上跑通。preflight 只检查执行和答案格式是否合法,不要求科学上正确。字节完全相同的程序直接排除。
然后,父代和每个剩余候选都在全部 90 个 Val 任务上评估:
- 每个任务只取第一次提交的答案,不给研究员反馈;
- temperature 0,匹配的任务种子和宿主预算;
- 必须完整覆盖所有任务 ID;
- 候选评估任务共享同一个并发上限。
打分公式:
S(H; M, D_val) = (1/|D_val|) · Σ 1[ Verify_x( a⁽¹⁾(M, H, x) ) = 1 ]
a⁽¹⁾ 就是第一次提交的答案。
替换条件非常严:候选必须完整 Val 评估无运行时错误,且分数严格高于父代。
- 平局 → 保留父代;
- 多个候选都高于父代 → 更早的候选胜出;
- 父代在每一步都重新评估,不使用上一次比较的缓存分数。
还有一个关键隔离:只有聚合的 Val 比较进入后续提案历史,Val 轨迹不会成为改进器的证据。
四、外递归:模型是怎么被训出来的
4.1 用新鲜轨迹,不用回放
选中的 harness 在整个 RL 阶段冻结。SkyRL 每批采样 8 个任务实例、每实例 8 次尝试,默认 64 个 episode。
这些是当前任务模型产出的新鲜 on-policy 尝试。要注意:harness 阶段的那些对话提供的是流程性证据,不是拿来给 RL 重放的策略补全。Val 任务排除在 RL 训练索引之外。
训练用 temperature 1、top-p 1、关闭 thinking。第一次提交答案就结束 episode,RL 期间不提供模拟研究员回复。
4.2 奖励就是「首答对错」
R_i = Verify_{x_i}( a_i⁽¹⁾ ) ∈ {0, 1}
验证器解析要求的答案格式,核对私有参考答案。从未提交答案的尝试没有成功结果。 PRM 的反馈标注不能替代这个奖励。
4.3 信用分配:一个 episode 只给一次奖励
每个模型调用变成一行训练数据,包含它真实的 prompt token IDs、completion token IDs 和 rollout 对数概率。同一个 episode 里的调用共享一个轨迹身份。
episode 奖励只在最后一次 completion 上放一次,更早的调用不单独拿终止奖励。
只有模型生成的 completion token 是训练目标。prompt token、工具输出、模拟研究员文本、生成的 harness Python——统统不作为模型输出去优化。
最后这一条很讲究:harness 的 Python 是辅助模型写的,不是任务模型写的,自然不该算到任务模型的梯度里。
GRPO 的组内相对优势:
μ_x = (1/G) · Σ R_i
A_i = (R_i − μ_x) / ( std(R_1 … R_G) + δ )
- 组内奖励全同 → 相对优势为 0;
- 组内两种结果都有 → 有正有负;
- 零调用失败不提供训练行,整批为空会被直接拒绝。
裁剪目标用学习率 1e-6,每批一个更新 epoch,KL loss 与 KL reward penalty 都关闭,默认 30 次优化更新。
4.4 导出必须过验证才算数
最后更新完成后,worker 导出模型权重、配置和 tokenizer。然后在独立的评估进程里加载这个导出,检查 Test 完整覆盖且模型生成非空,才接受这个阶段。
文档写得很直白:检查点目录存在、或训练进程正常退出,本身都不算数。
五、宿主始终攥在手里的东西
生成的 harness 可以自由编排 Agent 行为,但科学环境和训练契约始终归宿主:
| 组件 | 职责 |
|---|---|
| harness 控制器 | 执行候选的独立 Python 程序 |
api.generate(messages) | 在宿主预算下请求任务模型补全 |
api.execute(code) | 在独立持久化的科学执行容器里跑 Python |
api.submit(answer) | 把文本提交给宿主验证器 |
| 宿主 broker | 强制溯源、限额、评分、episode 终止 |
| 训练适配器 | 保留真实模型上下文、对数概率、completion mask |
两个容器(控制器和执行容器)都没有网络访问。只有科学执行容器拿到公开任务资产和冻结的数据湖;私有参考答案和服务凭据留在宿主上。
消息会标明来源:task / model / tool / feedback / harness。宿主校验这些元数据、套用工具历史预算,并在分词之前移除元数据。任务问题和反馈必须匹配它们真实的来源。
宿主还会在配置的 token 预算内裁剪工具响应和较旧的工具历史。裁剪保留记录下来的溯源和实际缩短后的模型输入——新 harness 不能靠重命名一个工具观察来绕过预算。
让 harness 自由改流程,但不给它改规则和评分的机会。流程可以进化,评判标准不行——这是自我改进系统不跑偏的前提。
六、实验配方:想复现要看的数字
| 组件 | 设置 |
|---|---|
| 任务模型 | Qwen3.5-4B |
| 辅助改进器 | 远程 Chat Completions 模型,中等推理强度 |
| 排期 | h0001 → r0001 → h0002 → r0002 → h0003 → r0003 |
| 每阶段 harness 步数 | 3 |
| 每步训练交互 | 16 |
| 每步候选 | 3(同一父代) |
| 候选选择 | 完整 90 任务 Val 对比 |
| 每阶段 RL 更新 | 30 |
| RL 批 | 8 任务实例 × 8 次尝试 |
| 学习率 | 1e-6 |
| 后端 | SkyRL + FSDP + vLLM |
| KL loss / KL reward penalty | 都关闭 |
| 任务模型采样 | temperature 1、top-p 1、关闭 thinking |
| 评估采样 | temperature 0、每任务一次 |
| rollout 并发 | 64 |
数据集:冻结发布版,715 Train / 90 Val / 90 Test。四个任务家族的分布是 DbQA 411/50/50、GWAS 140/20/20、LitQA2 76/10/10、ProtocolQA 88/10/10。
单 episode 预算:
| 项目 | 预算 |
|---|---|
| 上下文 | 24,576 token |
| 单次生成 | 4,096 token |
| 模型调用 / 工具调用 | 9 / 9 |
| 可见工具响应 | 2,048 token |
| 工具历史总量 | 8,192 token |
| Python 调用超时 | 60 秒 |
| episode 超时 | 900 秒 |
| 研究员回复 | 流程交互期最多 1 次;RL/评估期 0 次 |
Linux + Python 3.12 + CUDA 13 + Docker + 8 张 A100。模型权重和任务数据需要另外授权获取,不随代码一起给。
七、三点真正值得学的东西
7.1 把「流程」当一等公民,和权重同等对待
大多数团队把 Agent 的编排逻辑当工程代码:手写、手改、手工评审。ScienceBuddy 把它变成一个可搜索、可评估、可继承的对象——候选是完整 Python 程序,用固定 Val 集对比,严格更优才替换,而且会跨轮继承。
这意味着「怎么干活」这件事,从工程问题变成了优化问题。这是全文最有迁移价值的一点。
7.2 自我改进能跑通的前提是「证据卫生」
整套设计里最见功力的是隔离:
- 私有参考答案和数值打分永不进入提案证据;
- Val 只提供聚合比较,轨迹不进改进器;
- Test 不产生任何提案痕迹、反馈或 RL 更新;
- PRM 的解释被明确定位为假设,不是事实;
- 模型只在自己生成的 token 上被优化。
自我改进系统最常见的死法,是让「评分器」的信息漏进「被评分者」的学习回路,然后系统学会讨好评分器而不是解决问题。这里的每一条隔离,都在防这件事。
7.3 对「测量」的克制,比结果本身更值得抄
文档里反复强调:
- 训练准确率衡量的是变化的采样批次,不能当成固定的 Test 曲线;
- Val 分数用于选择,不能当成独立 Test 结果;
- 报告要同时给出首答准确率、尝试预算和任务分母;
- pass@k 覆盖率是另一个指标,需要对应的重复尝试。
这种克制在当下相当稀缺。大多数自我改进的发布,都乐于展示一条漂亮的上升曲线。
八、边界:它自己承认的局限
这一节比前面的机制更重要,因为它决定了你能不能用这套东西下结论。
- Test 不是独立测试集。任务 ID 是分开的,但材料组在 Train–Val 之间有 20 组重叠、Train–Test 之间有 18 组重叠。文档原话是:「这不是在声称 Test 材料独立或此前未见。」
- 反馈是模拟的、有界的。实验用的是「验证器辅助的有界模拟反馈」,不是真人研究员。这和面向研究者的产品演示是两回事,不能混着讲。
- 产品演示 ≠ RSI 实验。公开的 web 版是产品工作台,文档描述它有 224 个工具、22 个功能模块,当前工具和数据偏生物医学。而代码库里的 RSI 实验是独立的简化实现,用的是冻结任务集和顺序的 harness/RL 阶段——不包含方法图里的自适应任务环境和在线部署。
- 完整产品源码没开源。代码库里只有简化 Agent 实现(实验 harness、执行 broker、验证器、SkyRL 适配器)。托管工作台前端、账号体系、产品 API 服务都不在里面。
- 默认跑法是 debug 模式。默认触发器会在不管 harness 的 Test 分数有没有提升的情况下跑完固定阶段排期。也就是说,默认配置验证的是「流程能跑通」,而不是「每轮都变好了」。
九、怎么看这件事
如果把 Agent 的进化拆成「改流程」和「改权重」两件事,ScienceBuddy 的贡献并不在于发明了其中任何一件——流程搜索和 RL 训练都是成熟技术——而在于把两者咬合成一个闭环,并且把每一步的证据边界划清楚。
它读起来更像一份工程规范,而不是一份成绩单。文档里那些「必须完整覆盖」「平局保留父代」「检查点存在不算数」「训练准确率不是 Test 曲线」,枯燥,但恰好是这类系统能长期跑下去的地基。
对做 Agent 的人来说,可迁移的经验可以浓缩成三条:把流程变成可搜索的对象;让评分信息远离被评分者;对自己的测量保持克制。
至于这套东西到底能让 Agent 变强多少——它自己没给结论,还明确说了当前配置的测量结果必须以实际记录为准,不能拿早期的案例图代替。这种诚实,反而比一条漂亮曲线更让人愿意往下看。
附:术语速查
| 术语 | 含义 |
|---|---|
| harness | 引导 Agent 干活的 Python 程序,组织提示、调用模型、执行工具、提交答案 |
| 内递归 | 固定模型,搜索更好的 harness |
| 外递归 | 固定 harness,用 RL 更新模型 |
| RSI | Recursive Self-Improvement,自我改进 |
| Val / Test | 验证集(用于选择)/测试集(用于阶段边界测量) |
| GRPO | 一种组内相对优势的 RL 算法,比较同任务多次尝试的奖励 |
| PRM | 过程奖励模型,这里用于把反馈关联到公开执行记录 |
| preflight | 候选程序在正式评估前的执行与格式合法性检查 |
| on-policy | 用当前模型自己产出的轨迹做训练 |