Skip to content

HGM 如何实现自我进化:从 Gödel Machine 到可运行的“树搜索 + 自改写”框架

1. 这篇文章写什么

HGM(Huxley–Gödel Machine)是一套“让 coding agent 迭代改写自己”的工程化框架,它把理论上的 Gödel Machine(用证明来决定是否自我改进)替换成一种可运行的近似:把“自我改写”当成一棵不断生长的树,然后用 clade(谱系/支系)层面的统计量来判断哪条谱系更值得继续投入计算预算。

本文目标是回答一个非常具体的问题:HGM 的“自我进化”在代码层面到底是怎么跑起来的?我会把论文(arXiv: https://arxiv.org/abs/2510.21614)里的三段策略(selection / expansion / evaluation)与仓库实现逐一对齐,并解释为什么它比“只看当前分数”的方法更像在优化长期可进化性。

相关链接:

2. HGM 的自我进化闭环:一句话版本

把 HGM 想成一个持续运行的外循环(outer loop):

  1. 评估(evaluate):拿某个 agent 版本去跑一个下游任务(SWE-bench / Polyglot 的单个 task),记录成功或失败。
  2. 扩展(expand):选择一个“谱系很有前途”的 agent 版本,让它在隔离环境里改写自己的代码仓库,生成一个新的 diff(也就是新的 agent 版本)。
  3. 调度(selection):在“继续扩展新版本”与“继续评估旧版本”之间自适应切换,以更细粒度的方式花预算。

这三件事重复到预算耗尽,最后挑出一个“best-belief” 的 agent 作为最终产物。

下面用流程图把闭环画出来(这张图对应仓库 hgm.py 的主循环结构,和论文里对 selection/expansion/evaluation 的拆分一致)。

3. “自我进化”在 HGM 里到底进化了什么

HGM 的“进化对象”不是模型权重,而是 coding agent 的代码仓库(包括 agent 的 prompt、工具定义、工作流、错误恢复策略等)。

仓库实现里,扩展一次(expand)会:

  • 复制当前 agent 版本(通过累积 model_patch.diff 把祖先改动按顺序打上去);
  • 在容器里 git init 并提交一个 base commit;
  • coding_agent.py 在这个仓库上执行“自我改进任务”;
  • 把它对仓库做出的改动保存成 model_patch.diff
  • 这个 diff 就是“新后代 agent”。

关键点:HGM 的每个节点并不是一个“模型 checkpoint”,而是一个“仓库补丁(patch) + 元数据”的组合。

4. 论文里的核心概念:MPM、CMP 与为什么要看 clade

4.1 MPM:短期分数 ≠ 长期可进化性

论文提出一个现象:Metaproductivity–Performance Mismatch(MPM)。直觉是:

  • 有的 agent 当前分数高,但后代质量差(“这条谱系不产出更强后代”)。
  • 有的 agent 当前分数一般,但它的后代里能涌现出非常强的版本(“祖先像是一个好跳板”)。

如果只按当前分数挑 parent 去改写,可能会把预算压在“短期强、长期不产出”的谱系上。

4.2 CMP:把“长期潜力”定义为 clade 的产出

论文把 clade(以某个节点为根的整棵子树)当作一个“谱系”,定义 Clade-Metaproductivity(CMP)来刻画这个谱系未来能产出多强的后代。

在实现层面,HGM 采用的核心统计是“成功次数 / 总次数”的 Beta-Bernoulli 视角。你可以把每次评估看作一次 Bernoulli 试验:成功记为 1,失败记为 0

论文里给出一个直观的 CMP 估计(这里用它的简化表达来解释):

CMP^(a)=nC(a)successnC(a)success+nC(a)failure\widehat{\mathrm{CMP}}(a) = \frac{n^{\mathrm{success}}_{C(a)}}{n^{\mathrm{success}}_{C(a)} + n^{\mathrm{failure}}_{C(a)}}

其中 C(a)C(a) 表示以 aa 为根的 clade。

HGM 的关键不是“把 aa 自己的分数当作潜力”,而是“把整个 clade 的证据聚合起来”,这能更稳定地估计一条谱系值不值得继续扩展。

5. 代码对齐:HGM 的三段策略怎么落在仓库里

下面进入最关键的部分:论文的算法是概念性的,仓库是工程化的。我们只看最小闭环路径:

  • 主循环与调度:hgm.py
  • 树结构与 clade 聚合:tree.py
  • 扩展(自我改写)与评估(跑任务):hgm_utils.py
  • 真正写出 patch 的 agent:coding_agent.py
  • 自我改写任务如何被构造(诊断失败 -> 生成“改进方案”):prompts/self_improvement_prompt.py + self_improve_step.py

5.1 Tree:clade 的定义与“把后代评估拉进来”

tree.py 里的 Node 是最核心的数据结构。每个节点维护:

  • children:后代节点
  • utility_measures:评估结果序列(10

它还提供两个关键函数:

  • get_sub_tree():拿到整棵子树
  • get_decendant_evals():把当前节点与整个子树的 utility_measures 拼成一个列表

这正对应“clade 聚合”的工程化版本:HGM 在选择要扩展哪个节点时,不只看该节点自己的均值,而是看 get_decendant_evals() 汇总后的结果。

5.2 Expansion:如何“生成一个新后代 agent”

扩展发生在 hgm.pyexpand()

  1. 过滤可扩展节点(均值可用且大于 0)。
  2. 对每个节点计算 descendant_evals(clade 聚合)。
  3. 用 Thompson Sampling 选一个 parent。
  4. 调用 hgm_utils.sample_child(parent_commit) 生成 child。

hgm_utils.sample_child() 才是“自我改写”的执行器。它会:

  • 在 Docker 容器里把 parent 的 patch 链按顺序 apply(get_model_patch_paths());
  • 在容器内初始化 git,并创建 base commit;
  • 从 parent 的评估日志里选择一个“要改进的切入点”(choose_entry());
  • 通过 diagnose_problem() 把日志 + 失败信息 + 现有代码总结成一个新的 problem_statement
  • 运行 coding_agent.py --self_improve,让 agent 改写自己的仓库;
  • 最后把 git diff 存成 model_patch.diff,复制回宿主机。

为了让你能“按行走读”理解一次扩展到底做了什么,下面把 sample_child() 的关键步骤按真实执行顺序拆开(代码在 hgm_utils.py)。你可以把它理解为:把 parent 节点代表的 patch 链回放成一个可运行的仓库,再让 agent 在这个仓库里产出新的 diff

  1. 生成一个新的 run_id,并创建输出目录(例如 output_hgm/<run_id>/)。

  2. 启动 Docker 容器(隔离执行),并做少量“基准文件切换”(例如 Polyglot 与 SWE-bench 的差异文件)。

  3. 回放 parent 的 patch 链:

    1. 通过 get_model_patch_paths(root_dir, output_dir, parent_commit) 向上追溯 parent_commit 的祖先链。
    2. 将每个祖先的 model_patch.diff 依次 patch -p1 到容器内仓库 /hgm/
  4. 在容器内初始化 git,并提交一个 base commit(作为后续 git diff 的对比基线)。

  5. 安装依赖(例如 python -m pip install -r /hgm/requirements.txt),确保后续能运行测试/评估。

  6. 选择一个“自我改进切入点” entry:调用 choose_entry(parent_commit)

  7. 生成面向 agent 的 problem_statement:调用 diagnose_problem(entry, ...)

  8. 执行自我改写:运行 coding_agent.py --self_improve,让 agent 在仓库里做修改。

  9. 抽取并落盘产物:

    • 从容器中拷贝 self_evo.md(对话与过程日志)到宿主机 output_dir/run_id/self_evo.md
    • 从容器中拷贝 model_patch.diff 到宿主机 output_dir/run_id/model_patch.diff
    • 写入/更新 metadata.json(记录 parent_commitentryproblem_statement 以及后续评估汇总字段)。

如果你想快速把这个过程“抽象成一段伪代码”,大概是这样:

text
child = sample_child(parent_commit):
  patch_chain = get_model_patch_paths(parent_commit)
  container = start_isolated_container()
  apply(patch_chain) into /hgm
  git init && commit base
  entry = choose_entry(parent_commit)
  problem = diagnose_problem(entry, parent_commit, logs, patch_chain)
  run coding_agent.py --self_improve --problem_statement problem
  copy out: self_evo.md, model_patch.diff
  save metadata.json
  return run_id

这里有两个工程细节非常关键,决定了 HGM 的“自我进化”为什么不是瞎改。

第一,choose_entry() 的策略不是固定选一个失败样例,而是带有“失败类型优先级”的启发式(这能显著提高自改写信号质量):

  1. 当空 patch(total_emptypatch_ids)占比明显时,有概率直接进入 solve_empty_patches(优先修复“没有真正改动”的失败)。
  2. 有概率进入 solve_stochasticity(优先缓解不稳定性导致的假失败)。
  3. 若检测到 context length 超限(any_exceeding_context_length(...)),有概率进入 solve_contextlength
  4. 否则优先从 total_unresolved_ids 里随机抽一个 instance 作为改进目标。
  5. 再不行才回退到“从所有 id 中随机抽”。

第二,coding_agent.py 内部还做了一层“局部选择”(micro-selection):它不是只生成一次 diff,而是可以多次尝试(--max_attempts),并用本地 pytest 的得分函数选出最好的那次 diff;如果分数打平,会调用一个 LLM tie-break(score_tie_breaker)来在同分候选里择优。也就是说:

  • HGM 外循环(hgm.py)负责在不同节点/谱系之间做预算分配(macro-selection)。
  • coding_agent.py 负责在单次自改写内部,从多个候选 patch 里选一个“更像能通过测试”的版本(micro-selection)。

这就是一次“谱系扩展”。child 节点并不直接等于“更强”,它只是一份新的改写方案,后续要靠 evaluation 去验证。

5.3 Evaluation:为什么 HGM 把评估拆成“单任务粒度”

论文强调 HGM 把 evaluation 从 expansion 中解耦,并且评估粒度是“单个 agent–task”。仓库里对应的是 hgm.pysample() 分支:

  • 先用一个条件判断当前应不应该 expand:

NαT+pending_expandsN^{\alpha} \ge |\mathcal{T}| + \text{pending\_expands}

其中 NN 对应 n_task_evalsT|\mathcal{T}| 是树节点数量。

  • 如果不 expand,则选择一个已有节点进行 evaluation。
  • 从该节点未评估过的 task 里挑一个,调用 hgm_utils.eval_agent(commit_id, tasks=[one_task])
  • 返回值是 [0][1],追加到 utility_measures

eval_agent()(在 hgm_utils.py)再拆细一点,你会看到它同样是一个“工程化”的流水线:

  1. 任务选择与去重

    • 若是 initial 节点,会维护 init_evaluated_tasks,避免重复评估。
    • 对非 initial 节点,用一个共享的 pending_tasks_lock 保护 pending_tasks,尽量避免多个 worker 同时挑中同一个 task。
    • random_level 控制 task 选择中“随机/固定”的混合:有概率随机抽 task,否则按固定顺序拿第一个。
  2. 把 agent 的 patch 链带入评估 harness

    • 通过 get_model_patch_paths(..., commit_id) 传入当前节点的祖先 patch 链。
    • 评估时 harness 会在一个“初始代码基线”上依次 apply 这些 patch,然后运行任务。
  3. 落盘与汇总

    • 对 SWE-bench 路径,会先跑 swe_harness(...),再 make_report(...),最后 get_all_performance(...) 汇总为 overall_performance
    • overall_performance 会回写到该节点目录下的 metadata.json
  4. 返回 0/1 的单任务结果

    • get_acc_on_tasks(tasks, run_dir) 读取 metadata.json 里的 total_resolved_ids,对每个 task 输出 10

这解释了一个容易忽略但很重要的点:HGM 树上的 utility_measures 并不是“浮点分数”,而是严格的 0/1 序列,它天然适配 Beta-Bernoulli 与 Thompson Sampling。

这会带来两个效果:

  1. 更快淘汰不行的后代:如果某个 child 连续失败,HGM 不会强制“新建就必须评一大串任务”。
  2. 更快积累 clade 证据:因为评估分散在树上,clade 聚合会更早变得稳定。

5.4 Thompson Sampling:HGM 的“决策随机性”不是拍脑袋

hgm.py 里有一个 TS_sample(evals)

  • 对每个候选节点,把 evals(一串 0/1)转换成 Beta 分布参数:
    • alpha = 1 + successes
    • beta = 1 + failures
  • 采样 theta ~ Beta(alpha, beta)
  • theta 最大的节点

这恰好是“Bernoulli 成功率后验采样”的标准做法:

  • 样本少但全成功的节点也有机会被选中(探索)。
  • 样本多且成功率高的节点最终更稳定地被选中(利用)。

在论文里这对应 “selection policy / expansion policy / evaluation policy” 都可以用 Thompson Sampling 来实现“软最大化”。

6. 自我进化的“自我指向”是怎么产生的:诊断 prompt 的设计

如果只让 agent 改写自己,很容易变成“乱改”。HGM 把自我改写变成一个比较可控的工程问题:

  1. 先让当前 agent 跑下游任务,收集失败日志(SWE-bench / Polyglot)。
  2. 从失败类型中挑一个 entry(比如空 patch、随机性导致失败、context length 超限、或具体某个 unresolved task)。
  3. 把“失败日志 + 预测 patch + 私有测试结果摘要(对 SWE)+ 当前 agent 源码摘要”拼成一个诊断输入。
  4. 让诊断模型输出结构化 JSON,里面包括:
    • improvement_proposal
    • implementation_suggestion
    • problem_description
  5. 最终把这些拼成新的 problem_statement,交给 coding_agent.py 去实现。

这套逻辑主要在:

  • self_improve_step.pydiagnose_problem()
  • prompts/self_improvement_prompt.py:诊断 prompt 模板与日志抽取

这里的关键是:HGM 并不是把下游任务当作“直接要修的 bug”,而是把下游失败当作“对 agent 系统能力的回归测试信号”,然后生成一个面向 agent 仓库的改进 issue。

换句话说:HGM 的 self-evolution 很像一个自动化的“写 issue -> 实现 issue -> 用基准测试验收”的工程流水线。

7. 为什么说 HGM 是“可运行的 Gödel Machine 近似”

Gödel Machine 的理想形式是:只有当你能证明某个自改写会提升长期期望效用,才执行它。

HGM 的近似来自两个替代:

  1. 用统计估计替代形式证明:用 clade 聚合的 0/1 评估结果来估计“这条谱系的潜力”。
  2. 用树搜索替代 proof search:把“可能的自改写”当成扩展树的动作,用 bandit / tree-search 的方式分配预算。

论文里强调:在特定假设(Assumption 1)下,若有真实 CMP oracle,就足以模拟 Gödel Machine 的行为(Theorem 1)。工程上当然没有 oracle,于是 HGM 用 \widehat{\mathrm{CMP}} 这种可计算的估计去近似。

从仓库实现看,HGM 的“oracle 近似”就在这句:

  • 扩展时用 node.get_decendant_evals()(clade 的证据)去做 Thompson Sampling。

只要你相信“后代的成功/失败能代表这条谱系的潜力”,这就是一个合理的 Gödel 风格替代品。

8. 一个可复现的最小运行理解(不跑也能读懂)

如果你不想立刻跑完整套 SWE-bench,建议按下面思路理解它的运行产物(输出目录):

  1. 每次 expand(一次自我改写)会生成一个 run_id 文件夹,里面有:

    • model_patch.diff:这次自我改写对仓库做了什么
    • self_evo.md:agent 自我改写时的对话与过程
    • metadata.json:扩展的元数据(parent、entry、problem_statement 等)
  2. 每次 evaluate(一次单任务评估)会更新对应节点的 utility_measures,并在 metadata.json 中累积 overall_performance。

  3. hgm_metadata.jsonl 会记录树结构快照(节点、parent、均值等),适合做可视化分析。

你可以把 model_patch.diff 当作“基因突变记录”,把 utility_measures 当作“生存/繁殖信号”,把 Thompson Sampling 当作“自然选择的随机机制”。

如果你希望直接看一个作者已经挑好的“最终产物”,仓库还提供了一个现成的 best_agent/ 目录:它是论文 Section 4.3 中提到的 best-belief agent。你不需要自己跑完整外循环,也能先从这个产物理解“一个强 agent 仓库最终长什么样”。

9. 风险与边界:HGM 的工程假设是什么

论文与仓库都明确提示了安全风险:它会执行 model 生成的代码(而且在 Docker 里)。这意味着:

  • 需要隔离环境(容器、最小权限、只读挂载等)。
  • 需要资源与预算约束(timeout、worker 上限)。

此外,HGM 的理论对齐依赖 Assumption 1(比如可重复评测、效用只看最终 agent 等)。在现实工程里这些假设可能不成立,例如:

  • 评测噪声更大(不稳定测试、外部依赖波动)。
  • “最终 agent” 的定义更复杂(不仅是 accuracy)。

这时 clade 聚合仍可能有用,但它不再是 Gödel Machine 的严格近似,只是一个偏稳健的探索策略。

10. 总结:HGM 的“自我进化”最值得记住的三点

  1. 进化对象是 agent 代码仓库:每个节点是一个 model_patch.diff(以及对应的元数据),不是模型权重。
  2. 选择依据是 clade 证据(CMP 思路):扩展 parent 时聚合整个子树的 0/1 评估序列,用 Thompson Sampling 做“软最大化”。
  3. 扩展与评估解耦 + 单任务评估粒度:更快试错、更快积累谱系证据,也更适合异步并行。

如果你后续还想继续深挖,我建议你下一篇从“best-belief agent 的选择与可解释性”写起:结合 hgm_metadata.jsonl 做树可视化,把某次表现突变的 patch 与对应的诊断 prompt 对齐,会非常有故事性。

11. best-belief agent 与 best_agent/ 目录:仓库给你的“最终答案长相”

仓库的 best_agent/README.md 明确说明:best_agent/ 目录里包含了 HGM 在论文 Section 4.3 中发现的 best-belief agent。它更像是一个“最终导出的 agent 仓库快照”,而不是一次运行的中间目录。

你可以用仓库提供的评估脚本直接复现实验口径(以 SWE-bench Lite 为例):

bash
PYTHONPATH=. python evaluate_agent.py --agent_path best_agent

关于“best-belief”这个词:在论文叙述里,它指的是一种偏保守的最终选择准则(倾向于选择“我们对其成功率更有把握”的 agent,而不仅是点估计最高)。在我对主代码路径(hgm.py / hgm_utils.py)的走读中,外循环本身负责扩展与评估,并持续写 hgm_metadata.jsonl,但“如何从整棵树里最终挑出 best-belief 节点”的脚本并不直接出现在这条最小闭环路径里。

这并不影响你理解 HGM 的自我进化机制:

  1. 外循环负责产生足够多的 0/1 证据(每个节点的 utility_measures 以及 clade 聚合)。
  2. best-belief 选择可以被视为一个后处理:基于这些证据,对每个候选 agent 的成功率后验做一个更保守的比较。

如果你把 best-belief 当作“最终导出策略”,而把 HGM 的核心当作“生成树 + 在线采样评估 + clade 聚合”,阅读体验会更连贯。

12. 从一次 expand 到一次 evaluate:产物与数据流对照表

下面这张表帮助你把“代码调用”与“磁盘上会出现什么文件”对齐:

阶段主要函数关键输入关键输出(文件)
expand(生成后代)hgm_utils.sample_child()parent_commit、祖先 patch 链、诊断日志output_dir/<run_id>/model_patch.diffoutput_dir/<run_id>/self_evo.mdoutput_dir/<run_id>/metadata.json
evaluate(单任务评估)hgm_utils.eval_agent()commit_id、单个 task、祖先 patch 链output_dir/<commit_id>/metadata.json(更新 overall_performance
outer loop 记录树快照update_metadata()全部节点结构与评估计数output_dir/hgm_metadata.jsonloutput_dir/init_evaluated_tasks.json

用这个表来回看第 5 节,你会发现 HGM 的“自我进化”并不神秘:它就是把 diff 当作遗传信息,把 0/1 当作适应度证据,然后用 Thompson Sampling 在树上分配预算。