Evolver:设计与实现¶
首次使用请先读使用与实验,了解评估范围、配置、成本和 run/resume/finalize 操作流程。本页负责实现映射。
配套文档: 权威的方法论规格是 docs/specs/self-evolution-loop-sop.md,
与代码一同保留在仓库内,未在本站发布。本文将 SOP 条款映射到 Raven 实现,
说明评估方案与模块接入方式。
使用方式: 在改动 evolver/** 或 benchmarks/appworld/evolve/** 之前,
先在此处定位对应的 SOP 条款;若你的改动改变了某条对应关系,
请在同一个 PR 中更新本文。
0. 根本性的架构差异(请先读)¶
SOP 的循环(§3 / §8.3)是 Claude 驱动的:由人打开 Claude,手工走完七步漏斗。
Raven 以程序驱动的循环实现同一套方法论:
evolver/orchestrator/loop.py::EvolutionOrchestrator 驱动漏斗,
SOP 中的 CLI 组件则变成了循环内的函数。
SOP §8.0 的分工(语义交给模型、确定性算术交给代码)原样保留:
诊断 / 设计 / 判定仍然是 LLM 调用,封装在
orchestrator/nodes/semantic.py::SemanticNode 中(将解析错误反馈给模型,并在限定次数内重试修复)。
改变的只是「由谁按下一步的按钮」。
启动器会自动执行漏斗,并保存已完成轮次的检查点,操作者恢复实验时无需手工重建进度。
1. SOP §0 通用规则 → 实现¶
| SOP 条款 | Raven 实现 | 证据(文件 :: 符号) |
|---|---|---|
| Gate0 运行前环境健康检查 | 每轮注入 precheck,冷启动前强制执行一次 | orchestrator/production.py::build_evolution_orchestrator(run_gate0);benchmarks/appworld/evolve/precheck.py::make_appworld_precheck |
| 基础设施失败阶梯:检测 → 重跑 ≤2 次 → 仍失败则计 0 分 | eval_with_infra_rerun,max_reruns=2,生成 _infra_rerun{1,2} 目录阶梯;KEPT 规则取 infra trial 次数最少的那次测量 |
orchestrator/scoring.py::eval_with_infra_rerun;benchmarks/appworld/evolve/adapter.py::ladder_out_dirs / read_kept_out_dir |
| ★ 分母 = 任务总数;infra 任务绝不剔除,就地计 0 分 | Gate-f 只报告污染清单,绝不缩小分母 | orchestrator/gates/pipeline.py::run_gates(注释直接引用 SOP §0) |
| 分工:语义归模型,确定性归代码 | 见上文 §0 | orchestrator/nodes/semantic.py::SemanticNode |
| 两种判定:导航用 = K3 均值 > vanilla(入库);计入战绩 = 配对 2σ(论文口径) | PairedResult 将二者保留为两个独立字段:promoted(导航)与 credited_2sigma(标签);晋级只读取前者 |
orchestrator/gates/paired.py::PairedResult / paired_lift |
| 配对 σ:逐任务配对差值的标准差,消除任务间难度差异 | d_i = rate_c,i − rate_v,i,se = stdev(d)/√n,z = lift/se |
orchestrator/gates/paired.py::paired_lift |
| 封存测试:测试结果不参与选择,结束后评估提升保留率 | AppWorld 启动器在收尾阶段通过 unseal_retention 评估基线和已记录的各轮交付版本。SealedTestRunner.score 返回 None,结果单独保存,但并未阻止任意代码访问这些文件。 |
orchestrator/sealed/runner.py::SealedTestRunner / unseal_retention |
| test 集绝不进入 anchor/train | 比 SOP 的纪律更强:一条机械化断言——发生泄漏时在启动阶段直接抛出 | orchestrator/sealed/runner.py::assert_no_test_leak,在 loop.py 构造时接入 |
| 纪律:只从 train 轨迹做诊断 | 诊断语料来源只挂在 train 上;test 轨迹没有读取路径(密封 runner 只保存分数) | orchestrator/scoring.py::EvalBackend.trajectories |
| 纪律:全程配置一致 | run_meta.json 记录配置指纹;启动器通常拒绝配置变更或已结束的实验继续运行。--force 可绕过这些检查,但此后不能再视为有效的封存实验。 |
evolver/launch/state.py::RunMeta.check_config;evolver/launch/runner.py::_meta_guard |
2. SOP §1 冷启动 → 实现¶
| SOP 条款 | Raven 实现 | 证据 |
|---|---|---|
| vanilla 在 train 全集上跑 K=3,形成厚账本 | backend.cold_start() 必须返回非空的稳定性账本;初始基线根据 vanilla 输出目录中的评估结果构建并冻结。 |
orchestrator/loop.py(构造处);benchmarks/appworld/evolve/run.py(seed_label="van0"、cold_start_k) |
| 失败图谱覆盖 ≥ 7 个 WHY 类别 | diagnose_round(min_why_classes=7) |
orchestrator/nodes/diagnose.py::diagnose_round |
| WHY × WHERE 分类法;可为新基准归纳生成 | TaxonomySpec 加两阶段的 induce_taxonomy;归纳失败会显式报错,绝不静默借用其他基准的表 |
orchestrator/nodes/taxonomy.py |
3. SOP §2 七步漏斗 → 实现¶
| SOP 步骤 | Raven 实现 | 证据 |
|---|---|---|
| ① 第 1 轮使用冷启动图谱;第 2 轮起对子节点重新诊断(已触发 / 翻转 / 仍失败),追加到持续更新的失败图谱中 | _diagnosed_parents 防止重复诊断;merge_failure_maps 跨轮累积并持久化;翻转记录在 _flips 中,附带伤害回放摘录(展示回归任务的实际失败过程) |
orchestrator/loop.py(轮次主体);orchestrator/production.py::outcome_hook |
| ② 1–2 个 WHY × 2–3 个候选,受预算上限约束;由环境变量控制、默认关闭 | WHY 选择默认走 driver 模式(由模型挑选;公式作为回退并留影子日志);Budget(max_why_per_round × candidates_per_why) 在代码中强制;appworld 线路按节点传入 activation_env |
benchmarks/appworld/evolve/editor.py::driver_select_whys / rerank_whys;orchestrator/config.py::Budget;benchmarks/appworld/evolve/adapter.py |
| ③ 免费剪枝:beacon_guard + preflight | beacon:编辑器拒绝没有 activation_beacon 的 python 改动(硬性);preflight:make_zero_hit_preflight 零命中剪枝,默认关闭(见 §6 ①) |
benchmarks/appworld/evolve/editor.py;orchestrator/production.py::make_zero_hit_preflight;benchmarks/appworld/evolve/run.py(zero_hit_preflight=False) |
| ④ 应用改动、创建子节点;path_guard 守护内核;git 持久化 | 先改后提交:改动以父节点的真实 git 子提交落地,工作树不受影响,由程序确定并返回变更文件的路径 | evolver/tree/git_ops.py::commit_files_as_child;evolver/applier/path_guard.py |
| ⑤a K=1 锚点宽松筛选;计算 σ_screen;1.5σ 处淘汰;三档 | 三个分桶 clear_win / within_band / cull,只有 cull 会被拦;σ 公式与 SOP 一致,由 select_anchor 从账本中给出;AppWorld 线路使用聚焦子集的 Fisher 探测变体(同样是宽松通过:只淘汰显著更差者) |
orchestrator/nodes/screen.py::screen_candidate;evolver/scheduler/anchor_selection.py::select_anchor / simple_anchor;orchestrator/gates/strategies.py::FocusedFisherGate |
| ⑤a 锚点构成:亲和度占多数 + 破冰任务 + 哨兵任务,且 ⊂ train | 三种角色均已实现;哨兵守卫引入了 SOP 没有的分层(稳定任务用均值守卫,脆弱任务用 Fisher,避免被噪声误杀);亲和度的数据来源尚未接线(§6 ③) | evolver/scheduler/anchor_selection.py;orchestrator/gates/strategies.py(哨兵守卫) |
| ⑤ 借用机制(SOP 标注为 [defer]) | 模块已存在(与上游逐字节一致),尚未接线(§6 ②) | evolver/scheduler/tree_aware_bandit.py |
| ⑤b 幸存者:全集 × K=3 确认 | k_confirm=3,确认阶段跑完整 train 集 |
orchestrator/gates/strategies.py |
| ⑥ 三道护盾,顺序为 Gate-f → Gate-b → Gate2 | run_gates 正是这个顺序;缺少埋点数据时 Gate-b 放行(绝不冤枉一个没埋点但诚实的候选);上报分数始终使用全集的固定分母,因此 Gate-b 的子集均值绝无可能冒充成绩 |
orchestrator/gates/pipeline.py::run_gates;orchestrator/production.py(感知 beacon 的 fired_source) |
| Gate-b 数据链 | 写入侧:编辑器强制内联 beacon → 每次尝试生成 beacon 目录;读取侧:对确认目录与 infra 阶梯的兄弟目录取并集。诚实说明:归因只到「存在」层面——Gate-b 证明的是候选代码中某个 beacon 在某任务上执行过,而非该 beacon 位于机制的触发条件之内;无条件放置的 beacon 会把 Gate-b 降级为空操作(晋级仍需在全 train 集上取胜) | evolver/activation/ledger.py::beacon_workspace / mark_beacons_enabled(由 benchmarks/appworld/batch.py 调用);evolver/activation/ledger.py::read_fired_tasks |
| ⑦ 库中最优者成为新父节点;若所有候选均未通过,则保留原父节点并重新诊断 | beat_vanilla 耐心信号加贪心的父节点选择 |
orchestrator/loop.py |
| 终止条件:连续 10 轮无人超过 vanilla(比较对象是 vanilla,不是上一个父节点)或 20 轮上限;绝不查看 test | TerminationTracker(patience=10, max_rounds=20),信号定义为战胜固定的 vanilla;外加一项 SOP 没有的保护:出错的轮次不消耗耐心(max_consecutive_errors 是独立的兜底) |
orchestrator/termination.py |
| 四种节点状态 | 实为超集:pruned_inert / pruned_at_screen / pruned_at_confirm / promoted_to_baseline / errored / blocked_l1 / archived-methodology-failure;惰性死亡还会写入按 WHY 归档的历史,供设计器从中学习 |
evolver/tree/node.py::NodeStatus;orchestrator/production.py::inert_hook |
| WHERE 由产物机械绑定,绝不采信自我声明 | bind_where 从实际改动的文件推导杠杆;自我声明的 patch_where 仅留在账本中供审计,绝不决定归档坐标(4 级粒度,见 §6 ④) |
orchestrator/archive.py::bind_where / cell_of |
4. SOP §3 持久化 → 实现¶
| SOP 层 | Raven 对应物 |
|---|---|
| findings 工作日志 | <work_dir>/findings.md(每轮一节,记录驱动器的判定) |
| 跨会话状态 | journal(orchestrator/state/journal.py,崩溃续跑时回放已完成的轮次)加 history.json(按 WHY 归档的尝试历史) |
| Box 持久产物 | failure_map.json(跨轮持续更新的失败图谱)/ nodes/<id>.json(节点账本:身份 + git 锚点 + 最终状态 + 闸门统计)/ 每轮的输出目录(逐任务结果) |
沿用 SOP §3.1 的记录方式:Git 提交保存代码版本(commit_files_as_child 生成提交 SHA),
nodes/*.json 保存演化树结构,两者通过 git_commit_sha 关联。
5. 超出 SOP 的部分¶
- 封存测试评估已自动化。
assert_no_test_leak检查任务集是否重叠。 收尾时,unseal_retention评估已记录的提交,并按训练分数选择交付版本。 这些检查将测试评估与候选选择分开,但不隔离候选代码,也不阻止操作者读取文件。 - 单个候选崩溃不会拖垮一整轮:
errored状态,加上出错轮次不消耗耐心 (max_consecutive_errors作为独立兜底);SOP 未覆盖此项。 - QD 归档与重组: 按(WHERE × WHY)分格的精英库,加上跨格的重组候选
(
orchestrator/archive.py)——一种超出 SOP 的探索机制;闸门与评估口径不变。 - 惰性死亡反馈回路(2026-07):一个独立的
pruned_inert状态,惰性死亡写入历史, 设计提示词区分「触发条件从未命中」与「机制被否决」, 并对惰性死亡施加温和的 WHY 衰减(0.55^n_fail × 0.85^n_inert)。 - 伤害回放: 当候选导致某个任务出现回归时,会将该任务在候选版本下的实际执行轨迹摘录 提供给下一次候选设计;SOP 只要求记录翻转次数。
6. 实现选择¶
- 可选的零命中 preflight(默认
zero_hit_preflight=False)。 提供埋点数据时,Gate-b 排除未触发标记的任务;没有埋点数据时会跳过该检查。 Preflight 是独立的节省预算措施,也可能误删有用的候选。 可在提供TRIGGER_REGEX的实验中启用,并通过历史中的pruned_inert条目评估剪枝。 SOP §2 将这一步标为 [now],Raven 将其作为可选项。 - 全训练集确认。 AppWorld 评估完整训练集。编排器不调用
tree_aware_bandit进行借用(SOP §2 ⑤,标注为 [defer])。 - 锚点构成。 AppWorld 使用破冰任务、哨兵任务和边界任务。
select_anchor(affinity)接受亲和度数据,但 AppWorld 适配器不提供触发密度来源。 这一选择对评估结果的影响尚未单独测量。 - 四级 WHERE 绑定。 机械绑定使用 prompt/runtime/mixed/edit 划分 QD 单元, 判定 schema 则有 14 个类别。账本记录自我声明值和机械值供比对,不自动发出对账告警。
pruned_inert在语义上与 SOP 的状态一致,但实现方式不同: SOP 通过 preflight CLI 判定,我们通过嵌入循环的 preflight 判定(默认关闭,见 ①)。
6.5 统一入口(2026-07 加入)¶
SOP §8.3 的「手工编排」已被 python -m evolver run --config <yaml> 取代:
这是一条命令驱动的状态机,依次执行冷启动 → 各轮 → 终止 → 解封,
结束前可从已保存的试验结果和已完成轮次的检查点恢复,并非从中断调用中的任意指令继续。
配置漂移与结束状态通过 run_meta.json 检查;--force 可绕过这些检查。
基准通过 docs/specs/evolve-bench-contract.md 中的契约接入(该文档与代码一同
保留在仓库内);实现位于 evolver/launch/ 与 evolver/cli.py。
7. SOP 组件 ↔ Raven 组件速查¶
| SOP §8.1 中引用的组件 | Raven 对应物 | 形态差异 |
|---|---|---|
analysis/proxy_features.py |
evolver/analysis/proxy_features.py |
逐字节一致 |
analysis/failure_map_builder.py |
evolver/analysis/failure_map_builder.py |
逐字节一致 |
activation/preflight.py(CLI) |
orchestrator/production.py::make_zero_hit_preflight |
CLI → 嵌入式;正则变体,默认关闭 |
analysis/stability_bucket.py |
evolver/analysis/stability_bucket.py |
逐字节一致 |
scheduler/bandit_tasks.py |
evolver/scheduler/bandit_tasks.py |
逐字节一致 |
scheduler/anchor_selection.py |
evolver/scheduler/anchor_selection.py |
同源,另加我们的 simple_anchor |
scheduler/affinity_picker.py |
无对应物(§6 ③) | — |
scheduler/tree_aware_bandit.py |
evolver/scheduler/tree_aware_bandit.py |
同源,未接线(§6 ②) |
activation/gate_audit.py(CLI) |
orchestrator/gates/pipeline.py::run_gates(Gate-b 已嵌入) |
CLI → 嵌入式 |
analysis/paired_significance.py(CLI) |
orchestrator/gates/paired.py::paired_lift;留存评估在 sealed/runner.py::unseal_retention |
CLI → 嵌入式 |
aggregate_keq3 / gate0_ctrf_audit(外部评估引擎) |
benchmarks/appworld/evolve/eval.py / adapter.py(K=3 聚合加 infra 阶梯) |
跨仓库契约 → 同仓库模块 |
tree/* |
evolver/tree/(node/store/git_ops) |
同源,另加我们的 commit_files_as_child / read_file_at |