返回博客
2026-07-08· 约 6 分钟我的论文
Edit

AtomicCommitBench:代码智能体不该只交一坨 diff

我们的 AtomicCommitBench 论文已上线 arXiv。它研究 coding agents 能否把一个最终 squashed patch 重新组织成可 replay、可 review、可 selective revert 的原子提交历史。

AtomicCommitBench:代码智能体不该只交一坨 diff

📄 arXiv 预印本 | arXiv:2607.03332

作者:Zhihao Lin, Mingyi Zhou, Li Li

Li Li 为通讯作者


一句话概括

现在的 coding agent 很会在一个 session 结束时交出最终 patch,但真实维护不只需要“最后代码对了”。AtomicCommitBench 问的是:agent 能不能把一坨 squashed diff 重新拆成清晰、可回放、可审查、可选择性回滚的原子提交历史。


这个故事为什么有意思?

很多 coding agent 的交付物,本质上是一坨最终 diff。

它可能确实能通过测试,也可能确实实现了功能。但这坨 diff 里常常混着很多东西:功能实现、bug fix、测试补充、重构、配置改动、版本 bump、文档调整。对于 demo 来说,这没什么;对于真实仓库来说,这会很快变成维护负担。

因为软件工程里,commit history 不是装饰品。它至少承担三件事:

  • reviewer 要靠 commit 边界理解“这一步到底在改什么”;
  • 维护者要靠 commit 边界做 selective revert,而不是把无关修改一起回滚;
  • 后来的开发者和 agent 会把历史当成检索线索,从过去的 commit 里学习类似修改应该怎么做。

所以同一个最终代码状态,可以有两种完全不同的维护质量:一种是一个大 patch,所有意图缠在一起;另一种是干净的 atomic commits,每个 commit 都像一个可复用的变更单元。

AtomicCommitBench 的核心问题就在这里:如果 agent 已经生成了一坨最终 patch,它能不能事后把这段变更历史整理干净?

为什么不能只看最终 patch?

今天很多 benchmark 默认只看 final patch correctness。只要测试过了,任务就算完成。

但真实维护里,“最终结果对”不代表“过程留下的历史可维护”。一个 bug fix 如果和重构绑在一起,reviewer 很难判断到底哪一部分修了 bug;一个配置改动如果和行为变化放在同一 commit 里,后续 rollback 时就很危险;一个分布在多个文件里的功能如果被拆散,下一次 agent 读历史时可能只学到半个模式。

这也是我觉得 atomic commit 值得被单独评估的原因:它不是代码风格问题,而是软件演化里的结构问题。

好的 commit history 会保留 intent、dependency 和 review unit;差的 commit history 会把这些信息压扁成一坨 diff。

AtomicCommitBench 的任务是什么?

AtomicCommitBench 研究的是 retrospective commit-history reconstruction

输入不是一个开放式 issue,也不是让 agent 再去写代码,而是:

  1. 一个 base repository snapshot;
  2. 一个最终 squashed diff;
  3. 没有原始 commit message、commit hash、中间状态或 commit boundary。

agent 要做的事情也很明确:把这个 squashed diff 里的 hunks 分配到多个 predicted commits 里,并给出一个有序、可 replay 的提交序列。

这里最关键的设计,是把代码生成和历史组织解耦。最终代码已经固定了,agent 不需要再“修 bug”。它只需要回答:这些 hunks 应该怎样组织,才更像一个可维护的开发历史?

这个任务没有唯一标准答案。两个优秀开发者也可能对某个小 cleanup 是否应该和 bug fix 放在一起有不同判断。所以论文没有把“完全复原人类提交”当成唯一目标,而是设计了互补指标。

三个指标:能回放、像人类、能隔离失败

PPAR:Prefix Patch Apply Rate。 它看预测出的 commit 序列能不能一步步应用。一个历史如果连 replay 都做不到,当然谈不上维护价值。

ARI:Adjusted Rand Index。 它比较 agent 的 hunk grouping 和观察到的人类提交组织有多接近。ARI 不关心 commit 名字,也不关心顺序标签,只看哪些 hunks 被分到一起。

TCR:Test-failure Containment Rate。 这是我很喜欢的一个指标。对于那些修改了可执行测试的 episode,论文会做 partial reverse patch,看某个 predicted commit 被单独撤掉后,测试失败是否能局限在这个 commit 单元里。直觉上,如果一个 commit 真的是清晰的维护单元,那么 selective revert 时失败也应该更局部。

这三个指标各看一面:PPAR 看结构合法性,ARI 看历史组织相似度,TCR 看选择性回滚时的行为隔离。

数据集怎么来?

AtomicCommitBench 从 10 个成熟 Python 项目的真实历史里构造了 800 个连续多提交 episode,覆盖 pytest、pydantic、requests、httpx、black、rich、fastapi、flask、click、typer 等项目。

每个 episode 是同一作者在较短时间窗口内的一段连续 commit history。我们把这些 commits squash 成一个最终 diff,然后让 agent 只看 base snapshot 和 squashed diff,尝试恢复一个 replayable commit sequence。

数据本身也说明了这个问题并不简单:

  • 每个 episode 中位数有 12 个 hunks,涉及 6 个文件;
  • 35.4% 的 episode 至少有一个文件里的 hunks 来自不同原始 commits;
  • 59.5% 的观察到的 commits 横跨至少两个文件。

这意味着按文件切不够,按 hunk 全拆也不对。真实变更经常同时要求“拆开同文件里的不同意图”和“合并跨文件里的同一意图”。

关键发现

1. replay 很容易,组织得像人类很难。

所有非随机方法几乎都能做到 PPAR >= 0.988,也就是预测出的 patch 序列基本都能 apply。但 ARI 从 0.03 到 0.46 差距很大。

这说明难点不是“让 patch 能回放”,而是“把变更意图组织对”。

2. 真实 squashed diffs 比 synthetic tangles 难得多。

很多 commit untangling 工作会把不同 episode 里的 commits 人工拼在一起,构造 synthetic tangled changes。但 AtomicCommitBench 发现,这类 synthetic composite 比真实同一开发上下文里的 squashed diff 容易很多。

在匹配 commit count 的情况下,untangling baseline 在 synthetic 上平均多拿 +0.333 ARI。原因很直观:不同 episode 拼出来的 commits 往往文件、命名、主题差异更大;真实同一 session 里的修改更像,边界更模糊。

这说明如果我们想测 agent 在真实维护中的历史组织能力,不能只依赖人工拼接的 tangled commits。

3. 当前 agent 已经能做出有用的 draft history,但差距明显。

在 800 个真实 episode 上,GPT-5.4 + Codex CLI 达到 ARI=0.459,GLM-5 + Claude Code 达到 0.425,高于最强简单基线 FileSplit 的 0.340。MiniMax 和 Kimi 分别是 0.306 和 0.288,更接近路径局部启发式。

这里的结论不是“某个模型一锤定音”,因为 setup 同时包含模型和 agent harness。但有一点很清楚:强 setup 确实能恢复超过按文件切分的结构信号。

4. TCR 给出了另一个维护视角。

在 151 个可通过 partial modified-test probe 打分的 episode 上,TCR 排序和 ARI 一致:GPT-5.4 是 0.917,GLM-5 是 0.871,MiniMax 是 0.827,Kimi 是 0.780,而观察到的人类历史是 0.922。

这说明 grouping 不只是“和人类提交像不像”的形式指标。更好的 grouping 也更像一个能隔离失败的维护单元。

5. 常见错误集中在 locality。

论文里最典型的两个错误模式是:

  • same-file lumping:agent 看到 hunks 在同一个文件里,就倾向于把它们放在同一个 commit,哪怕它们服务于不同意图;
  • support-hunk drift:测试、helper、类型或配置这类 support hunks 被贴到最近的文本修改上,而不是贴到真正支持的 change intent 上。

这两个问题很真实。因为好的 commit 组织经常和“空间距离”反着来:同文件里的两个 hunk 可能应该拆开,跨文件的 source / type / test / config 反而应该合在一起。

6. DACE 说明结构证据有帮助,但不是万能。

论文还做了 Dependency-Aware Commit Evidence,也就是把轻量依赖边和 hunk role 提示给 agent。DACE 没有直接告诉 agent 答案,只是给它更明确的 dependency cues 和 anti-lumping cues。

结果很有意思:DACE 对低分 setup 更有效。MiniMax 从 0.306 提到 0.381,Kimi 从 0.288 提到 0.337;但对 GPT-5.4 和 GLM-5 基本没有显著提升。

这说明结构证据能帮助 agent 少犯 locality 错误,但对于更需要设计判断的边界,它还不能直接替代 agent 的组织能力。

我自己的理解

AtomicCommitBench 和 RepoRescue 是一组很互补的工作。

RepoRescue 问的是:旧仓库被现代生态弄坏后,agent 能不能把源码救回来,而且不走改测试的捷径。

AtomicCommitBench 问的是:agent 完成一个软件变更后,能不能留下一个对未来维护者有用的历史,而不是只留一个最终 diff。

这两件事背后其实是同一个观点:coding agent 的输出不应该只按 final correctness 评估,还要看它留下的工程证据和维护结构。

未来的 agent 很可能既是历史的消费者,也是历史的生产者。它会读过去的 commit 来定位问题、学习模式;它也会生成新的 commits,供后来的人和 agent 继续使用。如果它留下的历史是一坨乱 diff,那么下一轮维护就会从更差的上下文开始。

所以我越来越觉得,software change engineering 会成为 coding agent 研究里很重要的一条线。不是只问“能不能改对”,而是继续追问:

  • 这个变更能不能被 review?
  • 能不能被 replay?
  • 能不能被 selective revert?
  • 未来 agent 能不能从这段历史里学到完整模式?
  • 这次修改到底留下的是工程资产,还是维护债务?

AtomicCommitBench 想测的,就是 agent 有没有能力把最终 patch 变成真正可维护的软件历史。

参考


代码对了当然重要,但软件工程不是只看最后一棵树。一个好的 agent,也应该学会把自己留下的历史整理得让后来的人能读、能查、能回滚。

我的音乐