两周前 arXiv 上挂了篇 Chronos,讲怎么让代码 agent 在生成和挑选补丁时用上仓库演化史。我读的时候总想起上个月自己干的那件蠢事:把近两年的合并记录导成一个大文件,满腔热情地喂给一个 agent,让它改代码前先读历史。那晚我坚定地相信这招有用——这种坚定,一般就是出事的前兆。后来的效果怎么说呢,它就像被按进一屋子旧报纸里,然后我问它能不能读懂这座城市。
Chronos 把这件事做得具体多了:它没有把 PR 列表当成可检索的池子,而是把每个合并过的 PR 抽成一张“经验卡片”,再用一张带类型的图把卡片连起来。我盯着这个设计看了很久,最想问的是——为什么非要搞成图?先停一下,问个问题:如果只是让 agent 从历史里找有用的东西,把 PR 描述和 commit message 全向量化、语义搜一遍,缺了什么?
我一开始也以为,语义搜索就够了。毕竟每个 PR 的标题、正文、diff 都在,模型读过还能不知道哪些相关吗。但问题恰恰出在“每个 PR 各说各话”。一个新任务真正用得上的经验,往往不会老老实实待在一个 PR 里;它散在好几个变更里,每个变更的侧重点还不一样。有一个 PR 会告诉你为什么接口签名长这样,另一个会解释某个边角 case 是怎么踩过的,还有一个只留下了一段测试里嘲笑自己的注释。你语义搜出来的,通常是其中“长得最像”的那一个,而不是真正该看的一串。
论文里有个数字我反复看了好几遍。他们做了一百个任务、每个任务拉十张卡片,扁平语义检索平均只能翻出 1.24 张真正有用的;换了图上走,变成 2.87 张。整条工作流在 SWE-Bench Verified 上解决率也从 69.2 涨到 72.9,但这几个点我不太关心。我关心的是那个 2.87——它说的不是货架上多摆了几样东西,而是你终于有办法把一串串起来了:一张卡片会告诉你下一步该看哪张。
为了让图有用,得先有节点。节点是“经验卡片”。我猜它的形状大概是这个意思(不是论文里真实格式,是我自己脑补的):
{
"pr": "#1452",
"summary": "把订单状态机的锁从内存挪到 Redis",
"why": "多实例部署后内存锁会互相打架",
"touched": ["order/state.go", "infra/lock.go"]
}
每张卡片都是从一篇已经合进去的 PR 里提炼出来的,提炼动作本身就是一个设计决定。不是把所有原文塞进去,而是把“当时为什么改、动了哪些地方”压成一小块,这样检索和阅读才负担得起。但这样一来,卡片必然丢掉大量细节。所以真正的问题从来不是“单张卡片讲得全不全”,而是“丢掉的细节还能不能从相邻的卡片里找回来”。这就是边要负责的事。
边的来源并不统一。代码层面的最好办:两个 PR 碰过同一个函数,边就连上。第二种有点虚,靠的是意图——改锁和改缓存,表面上不挨着,但背后都在解决多实例一致问题。第三种最难,是组织层面的:“这几个人那半年负责这个子系统”,这种信息 git log 里根本不在显眼处,得靠额外规则甚至人事变化来推。
再往下,是检索策略。Chronos 走的是先语义定位一张入口卡片,再从这张卡出发做带权重的多跳扩展,每走一步都衰减。这个设计默认了一个前提:有用的经验在图上是聚团的,一张好卡片会牵着另一张好卡片出来。
你可能会反对说:那万一最关键的卡片离入口隔了三跳、四跳,而每一步又被权重压得很低,它不就被图检索漏掉了?对。这正是图的代价。它把“哪些算相关”这件事提前交到了边权的肉上。入口一旦选错,你往后走多远都是错。所以入口语义搜索的质量,决定了下限;边权设置和跳数控制,决定了天花板。这一层,扁平检索没这个烦恼——因为它压根没有“走多远”的概念,它只有“每一篇跟 query 像不像”。
接着,这套图不只喂给“生成补丁”的 agent,还喂给一个“演化管家”。论文里的设计是,两个 agent 各自给出一版补丁——一个变更 agent,一个验证策略 agent——然后管家调阅历史,从两版里挑。
这个设定比单点提升更值得停下来想想。它把演化的历史从“生成材料”升级成了“裁判依据”。生成时看历史,是问“以前怎么改”;挑选时看历史,是问“哪个改法更符合这仓库一直以来的脾气”。同一份记忆,两处用,但用法的深浅完全不同。后者要求的不是“像”,是“判断”。这比单纯在 prompt 里塞一句“这里有一些历史 PR 供你参考”诚实多了。但代价也藏在这里:如果两个候选都烂,翻历史也翻不出花来。这个坑论文未必明说,但我觉得是设计里最容易被忽略的地方。
如果非要再往下剥一层,Chronos 这套设计最大的前提不是一个算法问题,是一个数据库维护问题。经验卡片不是 git log 里现成的;它是一套从 PR 的原始文本、diff、代码关系里提炼出来的结构。谁来做这个提炼?关系边由谁来更新?代码在演进,历史图的节点和边也在腐化——一张卡片里提到的模块可能下个季度就被重命名了,指向它的边还留在图上,像一封永远寄不到的信。更不用说想要组织层面的边,就得在每次人员变动后更新规则,否则图会慢慢变成“以前这些人是一队的,现在这张边还留着”的考古现场。
这么说吧,用图去捕演化史,负担的是个额外的、活着的数据库。它值不值,全看这仓库能不能把提取和更新这件事长期养下来。这个标准比提升几个点冷峻多了。
现在回到我最开始干的蠢事。我把历史 PR 丢给 agent,本质上还在用“历史是一堆可以检索的文档”这个老框架。Chronos 真正改变的,是它把历史看成一张会随着仓库生长的网。这个改变让我有点后悔:那晚我要是有能力先织一张哪怕粗糙的网,而不是扔一屋子报纸,结局可能不一样。
可再往下,就是这张网怎么自己修自己了——边会过时、卡片会失效、权重会漂。这个 Chronos 没回答,我暂时也没想明白,下回再剥。
