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)与仓库实现逐一对齐,并解释为什么它比“只看当前分数”的方法更像在优化长期可进化性。
相关链接:
- 代码仓库:https://github.com/metauto-ai/HGM
- 论文 HTML:https://arxiv.org/html/2510.21614v3
- 论文 PDF:https://arxiv.org/pdf/2510.21614.pdf
2. HGM 的自我进化闭环:一句话版本
把 HGM 想成一个持续运行的外循环(outer loop):
- 评估(evaluate):拿某个 agent 版本去跑一个下游任务(SWE-bench / Polyglot 的单个 task),记录成功或失败。
- 扩展(expand):选择一个“谱系很有前途”的 agent 版本,让它在隔离环境里改写自己的代码仓库,生成一个新的
diff(也就是新的 agent 版本)。 - 调度(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 估计(这里用它的简化表达来解释):
其中 表示以 为根的 clade。
HGM 的关键不是“把 自己的分数当作潜力”,而是“把整个 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:评估结果序列(1或0)
它还提供两个关键函数:
get_sub_tree():拿到整棵子树get_decendant_evals():把当前节点与整个子树的utility_measures拼成一个列表
这正对应“clade 聚合”的工程化版本:HGM 在选择要扩展哪个节点时,不只看该节点自己的均值,而是看 get_decendant_evals() 汇总后的结果。
5.2 Expansion:如何“生成一个新后代 agent”
扩展发生在 hgm.py 的 expand():
- 过滤可扩展节点(均值可用且大于 0)。
- 对每个节点计算
descendant_evals(clade 聚合)。 - 用 Thompson Sampling 选一个 parent。
- 调用
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。
生成一个新的
run_id,并创建输出目录(例如output_hgm/<run_id>/)。启动 Docker 容器(隔离执行),并做少量“基准文件切换”(例如 Polyglot 与 SWE-bench 的差异文件)。
回放 parent 的 patch 链:
- 通过
get_model_patch_paths(root_dir, output_dir, parent_commit)向上追溯parent_commit的祖先链。 - 将每个祖先的
model_patch.diff依次patch -p1到容器内仓库/hgm/。
- 通过
在容器内初始化
git,并提交一个 base commit(作为后续git diff的对比基线)。安装依赖(例如
python -m pip install -r /hgm/requirements.txt),确保后续能运行测试/评估。选择一个“自我改进切入点”
entry:调用choose_entry(parent_commit)。生成面向 agent 的
problem_statement:调用diagnose_problem(entry, ...)。执行自我改写:运行
coding_agent.py --self_improve,让 agent 在仓库里做修改。抽取并落盘产物:
- 从容器中拷贝
self_evo.md(对话与过程日志)到宿主机output_dir/run_id/self_evo.md。 - 从容器中拷贝
model_patch.diff到宿主机output_dir/run_id/model_patch.diff。 - 写入/更新
metadata.json(记录parent_commit、entry、problem_statement以及后续评估汇总字段)。
- 从容器中拷贝
如果你想快速把这个过程“抽象成一段伪代码”,大概是这样:
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() 的策略不是固定选一个失败样例,而是带有“失败类型优先级”的启发式(这能显著提高自改写信号质量):
- 当空 patch(
total_emptypatch_ids)占比明显时,有概率直接进入solve_empty_patches(优先修复“没有真正改动”的失败)。 - 有概率进入
solve_stochasticity(优先缓解不稳定性导致的假失败)。 - 若检测到 context length 超限(
any_exceeding_context_length(...)),有概率进入solve_contextlength。 - 否则优先从
total_unresolved_ids里随机抽一个 instance 作为改进目标。 - 再不行才回退到“从所有 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.py 的 sample() 分支:
- 先用一个条件判断当前应不应该 expand:
其中 对应 n_task_evals, 是树节点数量。
- 如果不 expand,则选择一个已有节点进行 evaluation。
- 从该节点未评估过的 task 里挑一个,调用
hgm_utils.eval_agent(commit_id, tasks=[one_task])。 - 返回值是
[0]或[1],追加到utility_measures。
把 eval_agent()(在 hgm_utils.py)再拆细一点,你会看到它同样是一个“工程化”的流水线:
任务选择与去重:
- 若是
initial节点,会维护init_evaluated_tasks,避免重复评估。 - 对非 initial 节点,用一个共享的
pending_tasks_lock保护pending_tasks,尽量避免多个 worker 同时挑中同一个 task。 random_level控制 task 选择中“随机/固定”的混合:有概率随机抽 task,否则按固定顺序拿第一个。
- 若是
把 agent 的 patch 链带入评估 harness:
- 通过
get_model_patch_paths(..., commit_id)传入当前节点的祖先 patch 链。 - 评估时 harness 会在一个“初始代码基线”上依次 apply 这些 patch,然后运行任务。
- 通过
落盘与汇总:
- 对 SWE-bench 路径,会先跑
swe_harness(...),再make_report(...),最后get_all_performance(...)汇总为overall_performance。 overall_performance会回写到该节点目录下的metadata.json。
- 对 SWE-bench 路径,会先跑
返回 0/1 的单任务结果:
get_acc_on_tasks(tasks, run_dir)读取metadata.json里的total_resolved_ids,对每个 task 输出1或0。
这解释了一个容易忽略但很重要的点:HGM 树上的 utility_measures 并不是“浮点分数”,而是严格的 0/1 序列,它天然适配 Beta-Bernoulli 与 Thompson Sampling。
这会带来两个效果:
- 更快淘汰不行的后代:如果某个 child 连续失败,HGM 不会强制“新建就必须评一大串任务”。
- 更快积累 clade 证据:因为评估分散在树上,clade 聚合会更早变得稳定。
5.4 Thompson Sampling:HGM 的“决策随机性”不是拍脑袋
hgm.py 里有一个 TS_sample(evals):
- 对每个候选节点,把
evals(一串0/1)转换成 Beta 分布参数:alpha = 1 + successesbeta = 1 + failures
- 采样
theta ~ Beta(alpha, beta) - 选
theta最大的节点
这恰好是“Bernoulli 成功率后验采样”的标准做法:
- 样本少但全成功的节点也有机会被选中(探索)。
- 样本多且成功率高的节点最终更稳定地被选中(利用)。
在论文里这对应 “selection policy / expansion policy / evaluation policy” 都可以用 Thompson Sampling 来实现“软最大化”。
6. 自我进化的“自我指向”是怎么产生的:诊断 prompt 的设计
如果只让 agent 改写自己,很容易变成“乱改”。HGM 把自我改写变成一个比较可控的工程问题:
- 先让当前 agent 跑下游任务,收集失败日志(SWE-bench / Polyglot)。
- 从失败类型中挑一个 entry(比如空 patch、随机性导致失败、context length 超限、或具体某个 unresolved task)。
- 把“失败日志 + 预测 patch + 私有测试结果摘要(对 SWE)+ 当前 agent 源码摘要”拼成一个诊断输入。
- 让诊断模型输出结构化 JSON,里面包括:
improvement_proposalimplementation_suggestionproblem_description
- 最终把这些拼成新的
problem_statement,交给coding_agent.py去实现。
这套逻辑主要在:
self_improve_step.py:diagnose_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 的近似来自两个替代:
- 用统计估计替代形式证明:用 clade 聚合的
0/1评估结果来估计“这条谱系的潜力”。 - 用树搜索替代 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,建议按下面思路理解它的运行产物(输出目录):
每次 expand(一次自我改写)会生成一个
run_id文件夹,里面有:model_patch.diff:这次自我改写对仓库做了什么self_evo.md:agent 自我改写时的对话与过程metadata.json:扩展的元数据(parent、entry、problem_statement 等)
每次 evaluate(一次单任务评估)会更新对应节点的
utility_measures,并在metadata.json中累积 overall_performance。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 的“自我进化”最值得记住的三点
- 进化对象是 agent 代码仓库:每个节点是一个
model_patch.diff(以及对应的元数据),不是模型权重。 - 选择依据是 clade 证据(CMP 思路):扩展 parent 时聚合整个子树的
0/1评估序列,用 Thompson Sampling 做“软最大化”。 - 扩展与评估解耦 + 单任务评估粒度:更快试错、更快积累谱系证据,也更适合异步并行。
如果你后续还想继续深挖,我建议你下一篇从“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 为例):
PYTHONPATH=. python evaluate_agent.py --agent_path best_agent关于“best-belief”这个词:在论文叙述里,它指的是一种偏保守的最终选择准则(倾向于选择“我们对其成功率更有把握”的 agent,而不仅是点估计最高)。在我对主代码路径(hgm.py / hgm_utils.py)的走读中,外循环本身负责扩展与评估,并持续写 hgm_metadata.jsonl,但“如何从整棵树里最终挑出 best-belief 节点”的脚本并不直接出现在这条最小闭环路径里。
这并不影响你理解 HGM 的自我进化机制:
- 外循环负责产生足够多的
0/1证据(每个节点的utility_measures以及 clade 聚合)。 - best-belief 选择可以被视为一个后处理:基于这些证据,对每个候选 agent 的成功率后验做一个更保守的比较。
如果你把 best-belief 当作“最终导出策略”,而把 HGM 的核心当作“生成树 + 在线采样评估 + clade 聚合”,阅读体验会更连贯。
12. 从一次 expand 到一次 evaluate:产物与数据流对照表
下面这张表帮助你把“代码调用”与“磁盘上会出现什么文件”对齐:
| 阶段 | 主要函数 | 关键输入 | 关键输出(文件) |
|---|---|---|---|
| expand(生成后代) | hgm_utils.sample_child() | parent_commit、祖先 patch 链、诊断日志 | output_dir/<run_id>/model_patch.diff、output_dir/<run_id>/self_evo.md、output_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.jsonl、output_dir/init_evaluated_tasks.json |
用这个表来回看第 5 节,你会发现 HGM 的“自我进化”并不神秘:它就是把 diff 当作遗传信息,把 0/1 当作适应度证据,然后用 Thompson Sampling 在树上分配预算。