摘要

Kimi K3 在一个长会话中为 LLVM 提交了 PR #220979。收到审稿意见后,K3 在原会话中给出了三个修订方案,均未采纳审稿意见;然而,GPT-5.6 Sol 在新会话中处理同一任务,一次即给出了与最终合并版本逻辑等价的修订。这一反差既可能源于模型能力的差距,也可能源于原会话的上下文。为区分二者,我在 K3 收到审稿意见的决策点截断原会话,对会话记录施加单变量编辑(删除自动压缩、补入函数代码、替换压缩摘要中的一句、更换任务说明、在续跑消息末尾追加审稿意图),并从同一决策点重复采样,K3 共 10 个条件、36 次有效运行;另以 GPT-5.6 Sol 补做两个条件,各 5 次;各条件的预测均在开跑前写定。不带历史时,K3 有 5/5 将检查并入已有循环,而原样续跑仅为 1/5;删除自动压缩后为 3/5;补入函数代码(0/3)、替换摘要中的问题框架句(1/3)和更换任务说明(0/3)均无改善;追加一句完整的审稿意图后为 3/3,其前半句或后半句单独给出时也均为 3/3;相比之下,只提醒重读审稿意见为 0/3。Sol 在 fresh 条件下为 5/5;接续同一份压缩后的 K3 历史(去掉 K3 的系统提示与思考)时同样为 5/5。结果表明,原会话中的失败不能简单归因于能力不足或信息缺失,一句无歧义的意图陈述即可改变 K3 的选择;推理记录提示,失败主要发生在带历史时对审稿意见的理解与取舍上。

1. 引言

LLVM 寄存器合并器(RegisterCoalescer)中的一个断言失败,由 Kimi K3 在一个长会话中完成复现、定位与修复,补丁提交为 PR #220979。审稿人要求不新增独立的扫描,而是扩展已有的检查(原文见 2.2 节)。然而,K3 在原会话中给出的三个修订方案均仍为独立扫描,未被采用;而当我在新会话中将同一任务交由 GPT-5.6 Sol 处理时,Sol 一次给出了与最终合并版本逻辑等价的修订。

对此最直接的解释是两个模型存在能力差距。本文检验另一种解释:K3 具备落实该意见的能力,但原会话的上下文阻碍了这一点;若该解释成立,则进一步确定上下文中起作用的部分。方法参照 Anthropic 对网络安全事件的对齐评估(An alignment assessment of recent cybersecurity incidents):在事件记录中定位关键决策点,将每个假设落实为一处具体的记录编辑,从决策点重新采样并比较行为比率,且不采信模型对自身行为的解释。

分工如下:补丁由 K3 编写,合并版本的修订来自 Sol。研究问题、实验条件与每组预测由我确定,实验中的三处转向也由我提出。其一,H-a 的前提原是“K3 决策时看不到预检循环”;我回查原会话后发现,K3 在压缩前不仅读过这段循环,还以它为先例另写了扫描,信息缺口假设因此不成立(4.3 节)。其二,弱线索无效后,我没有继续拆分上下文,而是直接改用明确的意图陈述,带历史的 K3 由此从 1/5 恢复至 3/3(4.5 节)。其三,我质疑分析所依据的关键词片段,发现其仅覆盖约 6% 的内容;补读全文后,“14 次失败中 10 次想到正确做法”的结论被更正为三类(5.1 节)。Claude 协助编写运行脚本、执行实验和整理分析,它在分析中的疏漏列在附录 B。

本文的主要贡献如下:

  • 在一个真实的 coding agent 失败案例中分离了能力与上下文的影响。方法参照参考研究,在决策点截断原会话,做单变量的记录编辑并重采样(第 3 节):同一个 K3 不带历史时为 5/5,带历史原样续跑为 1/5;一句明确的审稿意图即可恢复并入(合计 9/9),只提醒重想则为 0/3(第 4 节)。
  • 对失败运行推理记录的逐次通读:失败并非源于误读代码,而是分为决策失败、没找到可行写法与误读审稿意图三类;不带历史的运行遇到相同的障碍,但将审稿意见第一句视为硬约束(第 5 节)。
  • 两条可检验的 harness 设计建议,以及由该决策点整理成的可复用评测用例(第 7 节)。

2. 背景

概括而言,第一版补丁为修复缺陷新增了一个循环,审稿人认为扩展函数中已有的循环即可覆盖同一情形。本节其余部分为面向编译器读者的技术细节。

2.1 缺陷

自 f44079d 起,x86 指令选择会生成 %27.sub_8bit = MOV8rm ... 形式的子寄存器写。该写操作不带 undef 标记,因此会隐式读取整个寄存器的旧值,以保留其余位。

RegisterCoalescer::removeCopyByCommutingDef 为删除一条 COPY,会将其前的 OR 换向(commute),改写显式操作数,再从活跃区间中删除旧定义。然而 partial def 仍在隐式读取该旧值,重试合并时 JoinVals::analyzeValue 随即断言失败:Instruction is reading nonexistent value。

该函数原有一段换向前的预检循环(约第 922 行):它遍历 use_nodbg_operands,遇到读取当前值号 AValNo 且绑定(tied)到 def 的 use 时放弃换向。partial def 属于 def 操作数,不在该循环的遍历范围内。

2.2 第一版补丁、审稿意见与合并版本

第一版补丁新增了一个专门扫描 def 的循环:

for (const MachineOperand &DefMO : MRI->def_operands(IntA.reg())) {
  if (!DefMO.getSubReg() || DefMO.isUndef())
    continue;
  SlotIndex ReadIdx =
      LIS->getInstructionIndex(*DefMO.getParent()).getRegSlot();
  if (IntA.Query(ReadIdx).valueIn() == AValNo)
    return {false, false};
}

审稿人 arsenm 针对这段新增的循环留下了以下意见:

I don't think you should need a new scan over defs. Most code should be checking readsReg, not getSubReg / isUndef directly.

MachineOperand::readsReg() 的定义为 !isUndef() && !isInternalRead() && (isUse() || getSubReg()),即不带 undef 的子寄存器 def 本身即计为读取。因此,合并版本不再单独扫描 def,而是将已有的预检循环扩展至全部操作数:

  for (MachineOperand &MO : MRI->use_nodbg_operands(IntA.reg())) {
  // Make sure all reads of AValNo can be rewritten to the new register.
  for (MachineOperand &MO : MRI->reg_nodbg_operands(IntA.reg())) {
    if (!MO.readsReg())
      continue;
    ...
    if (UseMI->isRegTiedToDefOperand(OpNo))
    // Partial defs and tied uses can't be rewritten independently.
    if (MO.isDef() || UseMI->isRegTiedToDefOperand(OpNo))
      return {false, false};

审稿意见的两句分别对应这两处改动:前一句要求不新增扫描,后一句要求以 readsReg() 取代手写的 getSubReg() / isUndef() 判断。

2.3 原会话的配置

原会话运行在 Kimi Work 的 k3-agent 上,上下文窗口为 256k。会话进行至约 21 万 token 时触发自动压缩,数百条消息被压缩为一份交接摘要;第一版补丁写于压缩之后,审稿意见在其后到达。

3. 方法

图 1 概括了实验设计。

实验设计示意图:原会话时间线依次为复现与定位、自动压缩、第一版补丁、取回审稿意见;在取回审稿意见之后的决策点截断,施加单变量编辑后重采样;fresh 对照在新会话中仅给任务说明;所有条件用同一组指标度量。 实验设计示意图:原会话时间线依次为复现与定位、自动压缩、第一版补丁、取回审稿意见;在取回审稿意见之后的决策点截断,施加单变量编辑后重采样;fresh 对照在新会话中仅给任务说明;所有条件用同一组指标度量。 实验设计示意图:原会话时间线依次为复现与定位、自动压缩、第一版补丁、取回审稿意见;在取回审稿意见之后的决策点截断,施加单变量编辑后重采样;fresh 对照在新会话中仅给任务说明;所有条件用同一组指标度量。
图 1 实验设计。原会话在 K3 取回审稿意见之后的决策点截断,施加单变量编辑后重采样;fresh 对照在新会话中仅给出任务说明。各条件以同一组指标度量。

3.1 决策点与重采样

决策点设在原会话中 K3 取回审稿意见之后、开始思考之前,即 K3 首次接触审稿意见且尚未形成方案的位置。原会话在该点截断,按各条件编辑后以 kimi-code/k3 续跑,保留全部思考;续跑消息为一句统一的继续指令,各条件的补充句附在其后。

正式实验前,先验证了记录编辑的有效性:续跑时上下文依据会话记录重建,修改记录即可生效,按原有结构插入的工具调用与结果会被模型视为真实结果。

不带历史的对照条件(fresh)仅提供一份冻结的任务说明,内容包括 issue、根因、第一版补丁与审稿意见原文。两个 Sol 条件由 GPT-5.6 Sol 运行:Sol fresh 与 K3 的 fresh 使用同一份任务说明;Sol 接 K3 历史与原样续跑使用同一份历史记录与续跑消息,二者的差别见 3.2 节。

样本量方面,fresh、原样续跑与不压缩各 5 次,其中后两组先各运行 3 次,再依预先设定的补样规则增至 5 次;其余 K3 条件各 3 次,Sol 两组各 5 次。

3.2 条件与预注册预测

K3 的每个条件只改动一个变量(表 1)。各条件按顺序设计,后续条件在前序结果得出之后确定,而非在实验开始前统一设定。本文所称“预注册”,指每个条件的预测均在该条件开跑前写定,开跑后不再修改。

表 1 实验条件、编辑内容与预注册预测

条件 历史 编辑 预测
fresh 无 只给任务说明 —
原样续跑 压缩后 不做编辑 —
不压缩 未压缩 删除压缩事件,保留压缩前全文 多于 1/5
换任务说明 压缩后 续跑消息换成 fresh 的任务说明 —
H-a 压缩后 决策点插入一次读整个函数的工具调用与结果 ≥2/3(放弃独立扫描)
摘要框架句替换 压缩后 把摘要中描述 bug 框架的一句换成纯事实陈述 ≥2/3
澄清审稿意图 压缩后 续跑消息末尾补一句完整的审稿意图 3/3
只给否定 压缩后 只补审稿意图的前半句 3/3
只提醒重想 压缩后 只提醒重读第一句,不给答案 3/3
只给方向 压缩后 只补审稿意图的后半句 3/3
Sol fresh 无 只给任务说明,由 Sol 运行 5/5
Sol 接 K3 历史 压缩后,去掉 K3 的系统提示与思考 由 Sol 续跑,续跑消息与原样续跑相同 5/5

“—”表示对照条件,开跑前未设预测。“历史”一列指续跑时带入的原会话记录:“压缩后”为自动压缩产生的交接摘要及其后的消息,“未压缩”为删除压缩事件后保留的全部消息。文中“带历史”均指带入原会话记录,包括压缩后与未压缩两种,与 fresh 的“不带历史”相对,并非特指未压缩的全文。

四个续跑补充句的原文分别是:

  • 澄清审稿意图:“补充:arsenm 的意思是不要新增独立的扫描,而是在函数里已有的检查上扩展。”
  • 只给否定:“补充:arsenm 的意思是不要新增独立的扫描。”
  • 只提醒重想:“补充:先想清楚 arsenm 第一句话到底想让我们怎么改。”
  • 只给方向:“补充:arsenm 的意思是在函数里已有的检查上扩展。”

只给否定与只给方向两组分别检验澄清句的前后两半。

H-a 插入的是应用第一版补丁后的 removeCopyByCommutingDef 全文,不单独标注预检循环,以免构成方向提示。摘要框架句替换仅改动压缩摘要中的一句,详见 4.4 节。

Sol fresh 用于检验原始对话中 Sol 的成功是否稳定;Sol 接 K3 历史用于观察同一份压缩后的历史换由另一个模型续跑时的行为。后者与原样续跑之间不只相差一个变量:除模型外,harness、系统提示与工具集均不同,且不含 K3 的思考,因此不属于单变量编辑。

3.3 结果度量

文中“并入”指修订方案放弃独立扫描、改为扩展已有的预检循环,无论该方案已写入代码还是仅在回复中提出。各条件的计数均以此为准,逐次运行的结果见附录 A 的表 5。

代码质量以盲评衡量。评分器为 Claude Sonnet,仅读取匿名化的最终代码,按结构 3 分、落实审稿意见 3 分、正确性 3 分、注释 1 分评定十分制,每次运行评 3 次并取中位数。另以两个 MIR 回归测试作为硬锚点,测试失败时正确性记为 0;第一版补丁同样能通过这两个测试,因此它们只能排除崩溃,无法区分不同写法。

第一版补丁本身已修复该缺陷。本研究衡量的是对审稿意见的采纳,而非一般意义上的功能正确性。盲评 10 分中有 6 分来自结构与落实审稿意见,因此盲评分数与“并入”高度相关,不能作为独立的质量证据。盲评分数主要由写法决定:并入的代码为 9–10 分,保留或另起独立扫描为 7–8 分;仅提出方案而未修改代码的运行按第一版补丁评分,为 3–4 分。唯一的例外是一次在行为测试中崩溃的运行。分数分布见图 3。

为评估小样本下的差异,本文对部分比较报告双侧 Fisher 精确检验的 p 值。这些比较为事后选定,未做多重比较校正,仅作描述性参考。图 2 中的比例另附 Wilson 95% 置信区间。

3.4 阅读推理记录

推理记录由 Claude 抽取并通读,范围为各次运行自续跑开始至形成方案的设计阶段。未并入的 17 次运行中有 14 次、不带历史的 5 次中有 4 次经全文通读,其余仅做了关键词检索。思考记录未必反映行为的真实原因,因此本文未向模型追问其想法,结论仅依据行为与代码。Sol 的推理内容加密保存,无法阅读,因此对 Sol 只分析其最终方案。

3.5 对参考研究做法的借鉴

表 2 列出参考研究的做法及其在本文中的对应。

表 2 参考研究的做法与本文的对应

参考研究的做法 本文的对应
弱线索无效时,升级为无歧义的明确陈述 先做最强的意图陈述(澄清组),再沿同一方向逐档减弱
编辑插入的位置本身是一个变量,越近越有效 所有补充句都放在最近一轮,决策点固定不变
原轨迹中的一次好行为可能只是运气,需要重采样看比率 原始对话中 Sol 的那次成功只记为动机,不作为证据;Sol 在 fresh 条件下另行重采样 5 次,所有条件都重采样
单独看证据和在原上下文中看证据,判断可能截然不同 用 fresh 对照与带历史续跑做比较
不采信模型的自我解释 观测指标只用行为与代码,不追问模型

4. 结果

横向条形图:各条件下将检查并入已有循环的比例。fresh 5/5,原样续跑 1/5,不压缩 3/5,换任务说明 0/3,H-a 0/3,摘要框架句替换 1/3,澄清审稿意图 3/3,只给否定 3/3,只提醒重想 0/3,只给方向 3/3;Sol fresh 5/5,Sol 接 K3 历史 5/5。 横向条形图:各条件下将检查并入已有循环的比例。fresh 5/5,原样续跑 1/5,不压缩 3/5,换任务说明 0/3,H-a 0/3,摘要框架句替换 1/3,澄清审稿意图 3/3,只给否定 3/3,只提醒重想 0/3,只给方向 3/3;Sol fresh 5/5,Sol 接 K3 历史 5/5。 横向条形图:各条件下将检查并入已有循环的比例。fresh 5/5,原样续跑 1/5,不压缩 3/5,换任务说明 0/3,H-a 0/3,摘要框架句替换 1/3,澄清审稿意图 3/3,只给否定 3/3,只提醒重想 0/3,只给方向 3/3;Sol fresh 5/5,Sol 接 K3 历史 5/5。
图 2 各条件下将检查并入已有循环的比例。误差线为 Wilson 95% 置信区间,右侧数字为并入次数与运行次数。最下方两组为 GPT-5.6 Sol。

表 3 列出各条件的计数及预测的检验结果。

表 3 各条件的预测与结果

条件 N 预测 结果
fresh 5 — 5/5
原样续跑 5 — 1/5
不压缩 5 多于 1/5 3/5
换任务说明 3 — 0/3
H-a 3 ≥2/3(放弃独立扫描) 0/3
摘要框架句替换 3 ≥2/3 1/3
澄清审稿意图 3 3/3 3/3
只给否定 3 3/3 3/3
只提醒重想 3 3/3 0/3
只给方向 3 3/3 3/3
Sol fresh 5 5/5 5/5
Sol 接 K3 历史 5 5/5 5/5

两个 Sol 条件的结果见 4.6 节。

图 3 给出各次运行的盲评分数。

点图:各条件下每次运行的盲评分数。并入代码的运行集中在 9 至 10 分,保留或另起扫描的集中在 7 至 8 分,仅提出方案、未修改代码的运行集中在 3 至 4 分。 点图:各条件下每次运行的盲评分数。并入代码的运行集中在 9 至 10 分,保留或另起扫描的集中在 7 至 8 分,仅提出方案、未修改代码的运行集中在 3 至 4 分。 点图:各条件下每次运行的盲评分数。并入代码的运行集中在 9 至 10 分,保留或另起扫描的集中在 7 至 8 分,仅提出方案、未修改代码的运行集中在 3 至 4 分。
图 3 各次运行的盲评分数(三次评分的中位数)。实心点为并入的运行,空心点为未并入的运行;同一分数的点横向错开排列。仅提出方案而未修改代码的运行按第一版补丁评分,位于 3–4 分。H-a 组 3 分的点对应一次在行为测试中崩溃的运行。最下方两组为 GPT-5.6 Sol。

4.1 不带历史时,K3 能落实这条审稿意见

fresh 组 5 次均并入已有循环。写法上,一次与合并版本逻辑等价,三次仅对 def 使用了 readsReg()。与原样续跑相比,5/5 对 1/5,p ≈ 0.048;该对照还混有模型配置的差异(见第 8 节)。

4.2 删除自动压缩使并入次数增加,但没有达到不带历史的水平

主假设为:压缩摘要中“补丁已写好、已验证、已获认可”一类的表述,使 K3 过度相信第一版设计。

结果方向与预测一致:不压缩组为 3/5,原样续跑为 1/5,差异不显著(p ≈ 0.52)。我此前进一步预期不压缩组会接近 fresh,乃至写出合并版本,该预期未实现:三次并入均仅对 def 使用了 readsReg()。

换任务说明组为 0/3:两次保留扫描,一次换成另一个扫描。

不压缩组中有一次运行读到了我本地仓库中的最终修法,但其代码修改均发生在此之前;该结果予以保留,剔除后为 2/4。

4.3 决策时补入函数代码没有带来提升

H-a 检验信息缺口假设:预检循环仅在压缩前出现,K3 决策时无法看到。

H-a 组为 0/3。其中两次删除了第一版扫描,但均换成另一个独立扫描,一次遍历活跃区间的段,一次遍历值号;另一次保留扫描。预测是否成立取决于标准:按预测所用的“放弃独立扫描”为 2/3,按并入为 0/3。

然而,事后检索原会话发现,该假设的前提并不成立。压缩前,K3 已读过整个函数,并在思考中将这段循环视为与自身方案最相似的先例,有意使新的 def 扫描与之对称。它也考虑过 readsReg(),但得出了错误结论:

defs don't 'read' unless... a subreg def without undef IMPLIES reading the other lanes — there's no readsReg flag on it

续跑时同样如此:原样续跑组 5 次均在续跑开头自行读到了预检循环,并查阅了 readsReg() 的定义。因此在续跑条件下,H-a 注入的是 K3 本就会读到的信息。审稿意见的两句恰好分别对应 K3 当时的两个判断:一是必须另写扫描,二是 readsReg() 不覆盖 def。

4.4 替换摘要中的问题框架句没有带来提升

原样续跑组的 4 次失败运行均基于同一理由作出决定:use 列表无法反映这笔隐式读取,而这正是缺陷的根源,因此必须另行扫描 def。例如其中一次起草的回复:

The existing pre-check scans use operands only; the hazard comes from a def operand (subreg non-undef implicit read), which is invisible in use lists — that's the root cause of the bug. So the def scan is needed, but I switched it to readsReg().

压缩摘要描述该缺陷时使用的正是同一框架:

但它的 use 改写循环只遍历显式操作数(约 951-1000 行),看不见这笔隐式读;

相反,摘要中“已验证”“已获认可”一类的定稿表述未被任何一次运行引用。因此我假设,起作用的是摘要给出的问题框架,而非其传递的确定性。

编辑方法是将该句替换为“而那条 partial def 仍在隐式读取 %27 换向前的旧值(值 4);”。替换句保留缺陷机制,不提及预检循环,不提示方向,长度相近。

结果为 1/3,预测(≥2/3)不成立,与原样续跑的 1/5 相近。该组一次未并入的运行在思考中 47 次使用“use 列表看不见”的表述,该表述系其由“缺陷存在”自行推得。

4.5 明确陈述审稿意图足以恢复并入,只提醒重想则不能

澄清组仅在续跑消息末尾补充一句审稿意图,不提供实现细节,结果为 3/3,预测成立。其中一次与合并版本同型。

随后,沿同一方向逐档降低陈述强度:

  • 只给否定组为 3/3,预测成立。
  • 只提醒重想组为 0/3,预测(3/3)不成立。
  • 只给方向组为 3/3,预测成立。三次均仅对 def 使用了 readsReg()。

澄清句的两半单独给出时,结果均为 3/3。这一结果仅表明任一半句单独即足以产生效果,不能说明其作用机制。将澄清、只给否定、只给方向三组合计,9/9 对原样续跑的 1/5,p ≈ 0.005;对只提醒重想的 0/3,p ≈ 0.005。

只提醒重想组三次均未并入:两次改用其他做法,一次遍历值号,一次检查区间段终点的指令;另一次保留扫描。三次均将审稿意见第一句重新解读为“信息已在区间或现有数据结构中,无需扫描”。

其中一次曾写出正确方向:

So "no new scan over defs" could mean: scan reg_operands (or extend the existing use-loops to reg loops) with MO.readsReg() && MO.getParent() != ....

但随后的推理转向了其他方向:

Hmm wait — actually! Maybe his point is architectural in a different direction

此后它未再评估该方案,最终建议保留 def 扫描,仅在其前增加 hasSubRanges() 的提前返回判断。

4.6 Sol 在 fresh 与接续 K3 历史时均能并入

Sol fresh 组的预测为 5/5,结果为 5/5,五次的写法均与合并版本逻辑等价。K3 的 fresh 组同为 5/5,差别在写法:K3 多仅对 def 使用了 readsReg()。

Sol 接 K3 历史组的预测同为 5/5,结果为 5/5,预测成立。该组与原样续跑并非单变量对照:Sol 看到的是另一个模型的历史,不含 K3 的思考,系统提示与工具集也不同(3.2 节)。因此,它与原样续跑 1/5 之间的差别不能单独归因于模型(见 6.3 节)。

5. 推理记录分析

本节为对同一批数据的观察性分析,用于解释第 4 节的结果,不构成独立证据。

5.1 失败分为三类

判定标准为:是否想到将预检循环改为遍历全部操作数,并在循环内区分 def 与 use。据此,经通读的 14 次失败运行分为三类(表 4):

表 4 通读的 14 次失败运行的分类

类别 次数 典型表现
决策失败:想到正确结构却不用 5 以清晰优先、改动太大、“I don't think”只是保留语气、留给审稿人选、下一轮再说等理由放弃;其中 2 次写出了完整代码
没找到可行写法 6 以为并入就要对所有操作数用同一个条件,发现会拦下普通 use,于是退回只扫 def
误读审稿意图 3 把第一句理解为“别扫 def,改用活跃区间里的信息”

逐句核对表明,失败运行对预检循环与 readsReg() 本身的描述均正确,失败并非源于对代码的误读。三类失败的性质不同。第一类为决策失败:正确结构已写出,却因审稿策略或先前结论被放弃。第二类中多数运行已理解审稿意见要求并入已有循环,障碍在于实现方式,所缺的是在循环内按 def 与 use 分支这一步(见 5.2 节);其中仅个别运行误解了意图,例如一次 H-a 运行认为遍历全部操作数的集合更大,审稿人不可能是此意:“That's a superset — but my def_operands scan is smaller. So "no new scan" can't mean that.”第三类为误读审稿意图,只提醒重想组的 3 次亦属此类(见 4.5 节)。决策失败确实存在,但仅占通读样本的 5/14。

5.2 不带历史的运行遇到同样的障碍,但把第一句当作硬约束

fresh 组的 5 次运行都曾遇到“922 行的循环只遍历 use”这一障碍。其中一次也遇到了“对所有操作数使用 readsReg() 会拦下普通 use”的问题,另一次同样出现过审稿策略类的考虑:

The "I don't think you should need a new scan over defs" is a "I don't think" — a suggestion/question.

区别在于后续处理:5 次均将第一句视为必须满足的约束,明确否定了“保留扫描、仅改用 readsReg()”的方案。例如:

But that still has a "new scan over defs", which the reviewer questions.

它们的解法是重新界定已有检查所拦截的对象。同一次运行写道:

So the existing check is a specific case of "a read of AValNo that cannot be rewritten to NewReg". A partial def reading AValNo also cannot be rewritten.

未找到可行写法的 6 次所缺的,正是在循环内按 def 与 use 分支这一步。

5.3 带历史的失败运行沿用先前结论,或援引审稿策略

第一类理由是沿用先前结论。“看不见,所以必须扫描”是 K3 在压缩前自行得出的结论,经由摘要带入续跑;不压缩组的原始历史中同样包含这一推理。fresh 的输入仅有补丁与审稿意见,“看不见”只是阅读代码时注意到的一个事实。一次摘要框架句替换组的运行进一步推断并入与只扫 def 等价:

Scanning reg_operands with (MO.isDef() && MO.readsReg()) == scanning def_operands with readsReg (same set, since def_operands = reg_operands filtered by isDef). So either formulation is equivalent; def_operands is cleaner.

第二类理由是审稿策略。一次不压缩组的运行写出了完整的合并代码,随后写道:

This is arguably the "no new scan" ideal. But it rewrites arsenm's existing loop — bigger diff, higher review friction. My recommendation: minimal variant (readsReg filter, keep separate loop), mention in reply that the check complements the tied-use guard right below and can be merged into one reg_operands walk if he prefers. Give him the choice in the reply — reviewers love that.

另一次摘要框架句替换组的运行同样写出了正确代码,最终选择暂缓改动:

If he then says "fold it into the existing loop" or "use interval valnos", we do that next round. That's a normal review dialogue — you don't have to preempt everything.

不带历史的运行中均未出现此类理由。

6. 讨论

6.1 编辑实验支持什么

按证据强度,第 4 节的结果支持以下判断:

  • 较强:失败不能简单归因于能力不足。不带历史的 K3 为 5/5,原样续跑为 1/5(p ≈ 0.048)。
  • 较强:一句明确的意图陈述足以让带历史的 K3 并入。完整的意图句及其前后两半单独给出均为 3/3,合计 9/9;只提醒重想为 0/3。
  • 较弱:自动压缩可能起了部分作用。删除压缩后为 3/5,与原样续跑的差异不显著(p ≈ 0.52)。
  • 否定性结果:信息缺口与摘要框架句都不是主因。补入函数代码(0/3)和替换框架句(1/3)均无提升,且续跑中的 K3 本来就会自己读到那段代码。

第二点与参考研究的发现方向一致:弱线索无效时,须改用无歧义的陈述才能改变行为。

两项不成立的预测同样具有信息量。框架句替换未见效,说明该句并非框架的唯一来源,K3 会自行推出同一框架。只提醒重想未见效,说明问题不在于是否重新审视审稿意见,而在于重新审视后得出的解读。

6.2 一个尚未检验的解释

综合第 4 节与第 5 节:带历史的 K3 面对存在歧义的意见时,倾向于将歧义朝“保留现有设计、回复解释”的方向消解;不带历史的 K3 面对同一意见,则将其视为硬约束。

我最初认为这是能力问题,或 K3 不擅长处理不确定的任务。fresh 组面对同样存在歧义的意见却为 5/5,表明 K3 至少在部分条件下能给出正确修订,失败不能简单归因于能力不足。但 fresh 与带历史的续跑在上下文长度、信息组织与任务描述上均不同,这些差异尚未完全分离。结合 fresh 组的推理,我目前的解释是:这更可能源于训练中习得的偏好、模式匹配与策略,在长历史中表现为坚持自身先前的结论,或套用“最小改动”的审稿策略。

6.3 关于 Sol

我认同 Sol 的写法,源于一项当时未告知任何模型的先验。此前我在 #202599 里尝试修复一个相关的 RegisterCoalescer 问题,该尝试最终被 bababuck 的 #204039 取代,后者仅在已有逻辑上修改了数行;#202599 最终仅以回归测试的形式合并。我当时仔细研读过 #204039 的写法,由此形成了“保持现有结构、在已有检查上扩展”的先验。

原始对话中 Sol 的成功仅有一次。fresh 条件下的重采样表明,Sol 在该任务上能稳定给出正确修订:Sol 与 K3 在 fresh 下同为 5/5,差别只在写法(4.6 节)。接续 K3 历史时,Sol 仍为 5/5,说明去掉 K3 思考后的这份历史不足以使 Sol 放弃并入;但与原样续跑的 1/5 相比,差别可能来自模型,也可能来自被去掉的 K3 思考,本文无法区分。

7. 对 Coding Agent 设计的启示

以下是由本文结果引出的几点推测,均未经检验,能否起效仍需在同一决策点上重采样验证。

把审稿意见改写成约束。一句明确的意图陈述即可使带历史的 K3 由 1/5 恢复至 3/3,这提示它缺少的或许不是方案,而是对“哪些要求必须满足”的确认。让 agent 在设计前先列出意见中的硬约束,也许能起到类似的作用;但只提醒重想的 0/3 表明,agent 自己的复述未必正确。

用不带历史的子 agent 提出修订。不带历史时 K3 为 5/5,因此可以设想由一个不带会话历史的子 agent 提出修订、主 agent 负责整合。不过主 agent 仍可能以先前的结论否定子 agent 的方案(5.3 节),实际效果尚不确定。

把决策点作为评测用例。本文的决策点可以作为一个源自真实 trace 的评测用例:固定历史前缀与续跑消息,测量并入率;三类失败也可能对应若干可自动检查的信号。单个用例的代表性有限,能否推广到同类场景尚待验证。

8. 局限

每个条件仅运行 3–5 次,p 值仅作描述性参考,未做多重比较校正;各条件按顺序设计,后续条件参考了前序结果,但预测均在开跑前写定。这主要影响证据较弱的判断(6.1 节):不压缩组 3/5 与原样续跑 1/5 的差异无法与抽样波动区分;fresh 5/5 对原样续跑 1/5、意图陈述各组合计 9/9 对只提醒重想 0/3 的差距较大,受影响较小。

主指标与计分规则均在查看部分结果之后确定。盲评分数与“并入”高度相关,反映的主要是审稿意见是否被采纳,而非独立的代码质量;两个回归测试也无法区分不同写法。

实验以 1M 窗口的 kimi-code/k3 续跑,原会话则是 256k 窗口的 k3-agent,不压缩条件因此只在实验中成立;续跑均为一次性作答,不包含原会话之后的多轮反馈。这些差异在各条件间相同,限制的是结论向原会话推广的程度,而非条件之间的比较。隔离也不完全:联网仅由 prompt 约束,一次运行读到了最终修法(见 4.2 节),隔离克隆的日期、分支名与提交作者也留有人为痕迹。

Sol 接 K3 历史并非单变量编辑,其结果只能作为对照观察;Sol 的推理内容加密,无法像 K3 一样分析。原始对话中的 Sol 输入包含我口述的“K3 的修改还是不对”,与 fresh 条件并不完全对等;实验中的 Sol fresh 组则与 K3 的 fresh 输入完全相同。

9. 后续工作

后续工作首先针对上述局限中影响结论解释的部分。6.3 节无法区分 Sol 接 K3 历史时的差别来自模型还是来自被去掉的 K3 思考;让 K3 以同样去掉思考的历史续跑,可以分离这两个因素。隔离方面,带历史的 K3 能推断出我本地真实仓库的位置,后续实验需使用独立的系统账户或虚拟机。度量方面,现有测试无法区分 fresh 中出现的“readsReg() 仅用于 def”这类不完全落实,需要增加覆盖“undef 且 tied 的 use”的测试。

其次是检验第 7 节的设计建议,优先检验意见复述:该方法无需人工补充说明,若验证有效即可直接纳入 harness。为此,我计划整理并公开续跑、记录编辑与评分工具,以便在更多会话中识别外部反馈与 agent 先前结论发生冲突的决策点,将单个用例扩展为评测集。

附录 A:各次运行结果

表 5 各次运行的结果与盲评分数

运行 条件 方案 盲评
r005 fresh 并入 9
r008 fresh 并入 10
r009 fresh 并入 9
r010 fresh 并入 9
r011 fresh 并入 4
r012 原样续跑 保留扫描 4
r015 原样续跑 保留扫描 4
r018 原样续跑 保留扫描 7
r021 原样续跑 保留扫描 4
r022 原样续跑 并入 4
r013 不压缩 并入 9
r019 不压缩 并入 9
r023 不压缩 并入 9
r016 不压缩 保留扫描 4
r024 不压缩 保留扫描 3
r014 换任务说明 保留扫描 7
r017 换任务说明 换成另一个扫描 7
r020 换任务说明 保留扫描 7
r025 H-a 保留扫描 4
r026 H-a 换成另一个扫描 8
r027 H-a 换成另一个扫描(测试崩溃) 3
r034 摘要框架句替换 保留扫描 7
r035 摘要框架句替换 并入 9
r036 摘要框架句替换 保留扫描 3
r037 澄清审稿意图 并入 9
r038 澄清审稿意图 并入 10
r039 澄清审稿意图 并入 4
r044 只给否定 并入 4
r045 只给否定 并入 9
r046 只给否定 并入 9
r047 只提醒重想 其他做法 4
r048 只提醒重想 保留扫描 4
r049 只提醒重想 其他做法 3
r050 只给方向 并入 9
r051 只给方向 并入 9
r052 只给方向 并入 9
r007 Sol(fresh) 并入 10
r053 Sol(fresh) 并入 10
r054 Sol(fresh) 并入 10
r055 Sol(fresh) 并入 10
r056 Sol(fresh) 并入 10
r057 Sol 接 K3 历史 并入 4
r058 Sol 接 K3 历史 并入 10
r059 Sol 接 K3 历史 并入 4
r060 Sol 接 K3 历史 并入 10
r061 Sol 接 K3 历史 并入 3

盲评按最终代码评分;仅提出方案而未修改代码的运行按第一版补丁评分,故部分并入的运行为 3–4 分。

附录 B:分析过程中的更正

以下三处疏漏曾改变对失败原因的解释。

  • 设计 H-a 前没有检索原会话。Claude 的分析仅确认预检循环的代码在压缩前出现过,未发现 K3 当时将其视为先例并仿照另写扫描。我在不知情的情况下批准了设计并定下预测。此后流程改为:设计任何插入或删除类编辑之前,先检索原会话中的全部相关内容。
  • 计数口径。Claude 曾报告原样续跑组“并入 0/5”,漏计了一次仅在回复中提出并入的运行,经我追问后更正为 1/5。早期“放弃独立扫描”的标签还将“换成另一个扫描”计入其中。此后以逐运行结果表作为唯一来源。
  • 失败分类。Claude 最初仅依据约 6% 的关键词片段得出“14 次失败中 10 次明确想到正确做法”,补读全文后更正为 5 / 6 / 3。

附录 C:实验基础设施

续跑。命令为 kimi -S <session_id> -p ... --output-format stream-json,续跑消息原文为“继续。(注意:只使用本地仓库,不要联网。)”。可在某次工具结果之后截断续跑,但续跑会追加一条新的用户消息并重新注入权限提示,并非无缝接续同一轮。原会话由 Kimi Work 创建,其模型别名 k3-agent 在 CLI 中不可用,改为 kimi-code/k3 后方可续跑。续跑时沿用记录中的系统提示,哈希与原会话一致;13 个核心工具相同,另多出 Kimi Work 的 7 个工具,缺少 2 个。

运行。每次运行先建立独立克隆,应用并提交第一版补丁,校验文件哈希,再作为独立的后台进程启动。启动器自行计时,超时后终止整棵进程树,随后收集 diff、会话记录、token 用量与网络审计。时间上限为 30 分钟,达到上限或中途中断的运行以终止时的状态计;正常结束与超时均计入样本,CLI 报错(如额度用尽)不计入,并补跑同一条件。部分条件为节省额度共用同一克隆路径运行,使历史前缀命中缓存;为防止样本彼此重复,预先规定若后续运行的第一段思考与首次运行逐字相同即终止该组,该规则未被触发。

隔离。Codex 在 Windows 上的沙箱并不禁止联网,K3 一侧也无网络限制,因此联网仅由 prompt 约束,并依赖事后审计;审计显示所有运行均未联网。部分运行在形成方案之后访问过运行目录之外的文件,除一次读到最终修法外(见 4.2 节),均未接触答案。实验中的数次越界访问表明,对带历史的 agent 而言,路径与用户名本身即构成线索,按目录隔离并不充分;已完成的运行目录移出运行区后,仍可被全盘搜索找到。后续实验应将完成的运行移至另一磁盘或打包存档,更稳妥的做法是使用独立的系统账户或虚拟机。

评分。盲评时,同一批运行混合后随机编号;评分器为 Claude Sonnet 子 agent,无法看到条件,且非被测模型。另设复核规则:总分 ≥ 8 但测试失败,或总分 ≤ 4 但测试通过且已放弃独立扫描时,转交人工复核。

Sol 续跑。Sol 接 K3 历史组取原样续跑组 K3 在决策点实际收到的请求,去掉 K3 的系统提示与思考后转为 Codex 会话,再发送与原样续跑组相同的续跑消息。转换后的历史保留了 K3 的工具名。

实验数据截至 2026 年 10 月 13 日。