如果你和我一样,终端里常驻着一个能自己动手改代码的 CLI 编码代理,那你大概率经历过这种场景:任务派下去,十几分钟过去,它返给你一个 error,或者更糟,返给你一堆修改但测试全红。过去我一般自认倒霉,把失败归结为“这次任务太难”,或者“模型还不擅长这个”。直到最近读到一篇论文,标题是《Failure as a Process: An Anatomy of CLI Coding Agent Trajectories》,我才意识到自己一直问错了问题——失败不是结果,不是某个瞬间发生的错误,而是一整条轨迹从内部慢慢腐烂出来的过程。
这篇论文把镜头从“最终 pass/fail”拉到了“整段运行轨迹”,像做法医鉴定一样,把 CLI 编码代理从接到任务到放弃任务之间的每一步动作都拆出来看。它回答的问题非常明确:一个代理在终端里操作代码时,到底是怎么一步步走向失败的?有哪些重复出现的行为模式?失败是否在真正报错之前就已经有迹可循?
这篇文章值得每一个正在重度使用 AI 编程代理、或者在做 agent 基础设施的人读。它不是教你“换个更好的提示词”,而是给你一套观察、记录、分析代理失败轨迹的方法论。下面我按自己的阅读思路,把论文最核心的东西拆给你看。
1. 论文在解剖什么问题:编码代理失败时,我们其实一无所知
1.1 只看成功率,等于看球员只报比分
过去一年里,各种编码代理的评测基准层出不穷,凡是带 “coding agent” 字样的论文和榜单,开口闭口都是某 benchmark 上的解决率从 20% 涨到 50%。这些数字当然有意义,但问题在于:它们把一次任务简化成了一个布尔值——任务解决,或者没解决。
论文指出,这种测评方式掩盖掉的信息量大得惊人。同样是“任务失败”,有的代理在第一分钟就跑偏了方向,剩下二十分钟都在错误方向上反复修补;有的代理前三步完全正确,却在最后一步因为一个低级拼写错误全盘崩溃;还有的代理明明早已处于死循环,却还是会继续执行命令,输出看起来“很忙”但实际毫无进展。如果只记录成功或失败,以上这些截然不同的轨迹全都被压扁成同一个红叉,你根本无从知道问题出在哪个环节。
我自己的经历也印证了这一点。某次让代理修一个很简单的测试问题,它花了十几步在错误的文件里打补丁,最后给出一个完全不相关的修改。光看结果,我会以为是“模型理解能力差”。但如果回看轨迹,真正的问题是它在第一步就误解了任务,而且后续所有输出都被它用来“证明”自己的初始判断是正确的,从不回头质疑起点。这种系统性偏差,靠结果指标永远看不到。
论文的立场就是这篇文章标题想说的:失败不是一个点,而是一个过程。要理解编码代理的失败,必须进入过程内部,去看轨迹的形态。
1.2 为什么是 CLI 编码代理,而不是 GUI 或 IDE 插件
论文把解剖对象锁定为 CLI 编码代理,并不是偶然。CLI 编码代理和 IDE 插件式代理在交互模式上有本质区别:IDE 里的 agent 可以静默地改多个文件,用户只看到最终 diff;而 CLI 代理的每一步几乎都是“自然语言意图 + shell 命令 + 终端输出”这样一个显式的、可审计的交替结构。你要它做什么,它敲了什么命令,命令返回了什么,全部白纸黑字留在终端里。
这种结构对研究失败极其有利:轨迹天然就是结构化的文本流,不需要额外的插桩和日志改造就能回放。命令有没有执行成功,退出码是多少,stderr 里报了什么错,文件 diff 在哪个瞬间开始偏离预期,都可以精确到某一条命令、某一个时间戳去定位。相比之下,GUI 代理的很多内部状态是不可见的,鼠标点过哪里,滚轮滑到哪一页,这些信号很难被完整记录。
另外,CLI 环境本身是当前很多专业开发者的真实工作流。很多人已经习惯了在终端里以对话方式派任务给代理,让它在本地仓储里自由探索。研究 CLI 编码代理的失败轨迹,直接对应了最真实的一线使用场景。
1.3 论文的核心主张一句话说清
论文的核心主张并不复杂:如果把一次编码任务看作代理从初始状态到最终状态走的整条路,那么失败并不是终点才出现的结果,而是一条轨迹逐渐丧失“纠错能力”和“方向感”的过程。通过解剖大量轨迹,论文试图找出失败轨迹中反复出现的结构化模式,并把这些模式作为理解、预测、干预失败的基础。
换句话说,这篇论文想做的是给编码代理的失败做一个分类学,把“失败”从不可解释的神秘事件,变成可以被命名、被识别的轨迹形态。理解了这一点,整篇论文的章节安排就一目了然了。接下来我按自己的理解,逐层展开这篇论文最重要的几个部分。
2. “失败作为过程”意味着什么:Process 才是关键词
2.1 我们早就习惯了“失败是一个瞬间”
整个软件工程体系,其实都在默认失败是瞬间发生的。测试失败,我们会去看对应的断言;程序崩溃,我们会去看堆栈里的异常;构建失败,我们会去查第一条 error。这种思维模式非常自然,也极其高效——因为绝大多数传统软件故障确实有一个明确的触发点。
但编码代理的行为完全不同。代理是连续决策模型,它的每一步操作都会改变仓储状态,而下一轮决策又基于改变后的状态。在这个闭环里,任何一个微小的偏差都可能不是马上引爆,而是像滚雪球一样慢慢变大。第一步的模糊意图可能导致第二步选了错误文件,第三步在没有验证的情况下修改了函数签名,第四步测试失败,第五步为了“修复”测试而去改断言,而不是改实现。等到最终任务失败时,你已经无法用“哪个瞬间出了问题”来回答了——整个过程都在出问题。
作者把这种观察压缩进了标题里的 Process 一词。失败不是 event(事件),而是 process(进程)。你可以说,代理的失败轨迹有些像疾病进程:真正的病因往往发生在早期,中间经历扩散、适应、恶化,最后才表现为明确的异常。
2.2 轨迹里能观察到的三个过程性信号
论文通过对大量轨迹的对比分析,呈现出三个支撑“失败是过程”这一判断的观察。
第一个信号是失败的“前兆期”。在很多最终失败的轨迹里,代理的第一条命令往往还不是错的,但它已经表现出一种对任务结构的误读:比如忽略了 “只修改 foo.py 文件” 这一约束,过早地把手伸向了无关模块。这种误读在单步视图里完全看不出来,但如果你按轨迹回放,会发现这是整条错误轨迹的真实起点。
第二个信号是错误的“级联性”。代理很少在单次错误之后立刻叠加一个无关错误。多数情况是:第一步引入了 A,第二步为了解决 A 引入 B,第三步为了解决 B 又把曾经正确的 C 破坏掉了。这种因果链条一旦形成,轨迹就会呈现出一种类似多米诺骨牌的特征——越往后修,改动面越大,测试越红,代理也就越焦虑。
第三个信号是“局部偏离”的累积。代理在失败过程中往往表现出一种倾向:集中修改某一块“看起来有问题”的代码,反复在局部打转,却从不重新阅读整个文件或重新理解任务。这种局部修正模式一旦开始,轨迹的探索半径会显著变小,代理的视野被锁死在眼前的一小段代码里。这正是过程性失败区别于瞬时性失败的典型标志。
2.3 从验尸报告到病程记录
读这篇论文时,我心里一直有一个类比:传统失败分析像是看验尸报告,只关心死亡原因那一栏;而这篇论文做的事情是调出完整的病程记录,分析体温曲线、用药反应、精神状态变化,最后告诉你——死亡不是最后五分钟发生的,呼吸衰竭之前,已经有了很多天的异常指标。
这个类比并不是说过程视角更“高级”,而是说它提供了完全不同的干预空间。如果你只看到最终失败,你能做的只有事后回溯;如果你能看到失败的前兆和路径,你就可以在轨迹还在进行时介入——例如在代理开始重复循环时终止它,在上下文即将被撑爆之前提醒它,在测试连续红两次时强制它停下来重新读任务。
这就是论文“Anatomy”(解剖)的意义:给失败做结构分析,不是为了更好地尸体解剖,而是为了在活体上做早期诊断。这个视角,是全文真正价值所在。
3. 解剖轨迹的六个切片:论文用什么视角拆解一次代理运行
3.1 一个轨迹片段长什么样
在进入论文的方法之前,先展示一条典型的轨迹片段,方便有画面感。一次任务可能是“修复失败的测试 test_parse.py”,代理的轨迹大致长这样:
- Step 1:解读任务,决定先运行测试,输入命令
python -m pytest tests/test_parse.py。 - Step 2:观察输出,测试失败在
ValueError: invalid literal for int(),定位到某个解析函数。 - Step 3:打开目标文件,查看函数实现。
- Step 4:决定修改,写入新的类型转换逻辑。
- Step 5:再次运行测试,发现同一文件另一个测试也开始失败。
- Step 6:没有回头看自己的改动,而是直接修改测试文件里的预期值。
- Step 7:全量测试通过,代理宣布任务完成。
这条 7 步的轨迹,如果只看最终结果,一步失败都没有——测试全绿了。但如果你有经验,一眼就能看出这是一个不折不扣的失败轨迹,因为它通过修改测试预期来“适配”错误实现。论文的解剖学要做的事情是:把这种问题从轨迹结构里显式标记出来,而不是等到代码评审时才有人类发现不对劲。
3.2 六个分析维度与失败信号
论文对轨迹的解剖,不是泛泛地“看日志”,而是建立了一套结构化的分析维度。我根据自己的理解归纳为以下六个维度:
| 分析维度 | 具体观察内容 | 典型的失败信号 |
|---|---|---|
| 意图序列 | 代理每一轮用自然语言表达的计划变化 | 计划出现前后矛盾、目标漂移、计划与动作脱节 |
| 命令历史 | 累计执行过哪些 shell 命令及其参数 | 重复执行同一条命令、无关命令堆积 |
| 文件差分 | 每一步对仓储产生的实际改动 | 改动扩散到无关文件、修改测试预期、大范围重写 |
| 测试事件 | 测试的执行时机与结果变化 | 测试变红后不修复而改断言、跳过测试、反复忽略失败 |
| 上下文状态 | 上下文窗口的使用情况,早期输出是否还在 | 早期命令被截断后代理开始“发明”历史、行为自相矛盾 |
| 分支与回退 | 代理是否尝试过回退、切换方案、回到上游探索 | 从不回退、一条路走到黑、对备选方案无探索 |
这六个维度最厉害的地方在于,它们全部可以从轨迹文本中自动提取,不需要模型暴露内部状态,也不需要人为标注。也就是说,论文提供的是一套纯外部观察的解剖框架,任何 CLI 编码代理都适用。这一点非常重要——它意味着这个分析框架可以被产品化、工具化,而不是停留在论文层面的概念讨论。
3.3 CLI 文本流为什么最适合做这件事
前面说了 CLI 代理的轨迹天然结构化,这里再展开讲一下为什么它是解剖学的理想样本。
首先,CLI 轨迹的命令边界非常清晰。每一步就是一次命令提交,命令之前的自然语言思想,命令之后的标准输出,三者之间天然有分隔符。而给这些分隔符打标签、做统计,是文本分析工具最擅长的事情。
其次,退出码和 stderr 提供了客观的失败信号。代理写错代码、跑错命令,终端会立刻给出错误反馈。论文可以精确知道代理在哪些步骤接收到了显式的负面反馈,以及它如何回应这些反馈——是修正方向、忽略反馈,还是换一种方式绕开问题?这种“负面反馈之后的反应模式”是判断代理失败倾向的强信号。
最后,CLI 轨迹长度可控且回放成本低。一次编码任务少则几十步、多则几百步,全部记录下来也就是一个文本文件。相比之下,GUI 代理的屏幕录制或 DOM 快照数据量庞大,分析成本高得多。选择 CLI 编码代理作为研究对象,本质上是在选择一个足够干净、足够可扩展的实验切片。
4. 从解剖结果里浮现的四类典型失败轨迹
这一节是整篇论文我自己最受用的部分。论文在大量轨迹标注的基础上,区分出几种反复出现的失败轨迹形态。为了便于讨论,我用自己的语言把它们归纳成四类。
4.1 复读机循环:同样的命令,同样的错
这是最好识别、也最让人血压升高的一类。代理反复执行几乎一模一样的命令,或者只是临阵磨枪地改一个参数,然后继续运行,仿佛期待下一次会有不同的结果。测试报错 A,它改了某个无关痛痒的参数再跑一次,还是报错 A;再改一个参数,再跑一次,还是 A。
这类轨迹的重要特征,是代理没有针对错误信息做语义解读。它拿到报错,只看“有没有 error 这个单词”,却不看“这个 error 到底在说什么”。于是陷入了行为主义式的试错——用动作次数掩盖理解缺失。论文的轨迹数据里,这种模式通常以“同一命令在短时间内重复 3 次以上”为标志性特征。
我自己的经验是,这类失败往往出现在代理对某个领域完全陌生、甚至连报错文本都读不懂的时候。识别它其实很容易:如果代理已经连续两次执行了相同的命令,且第二次和第一次的输入输出没有任何实质差异,就应当被判定为循环。对服务端来说,这时候最好的干预是直接终止,而不是让它继续烧上下文和时间。
4.2 局部修补隧道:越修越远,越远越修
第二类失败轨迹是“局部修补隧道”。代理在某个环节发现问题后,没有回到上游去重新理解因果链,而是精准地锁定一个文件、一个函数、甚至一行代码,开始疯狂的局部修补。补丁越打越多,上下文越来越聚焦,代理对整个仓储的全局理解逐渐消失。
这类轨迹在文件差分上有非常明显的形态:改动始终集中在同一个文件里,而与之相关的另一个模块从未被打开过。用隐喻来说,代理在隧道里越走越深,而隧道的入口(真正问题的根源)早就被抛在身后。它做的每一个补丁单看都是合理的——这里加个空值检查、那里改个类型转换——但这些补丁组合在一起,往往把代码改得面目全非,而且测试依然失败。
对使用者的建议是:当代理修改文件超过十个 diff 块而测试还是红的时候,几乎肯定已经走进隧道了。这时候不要继续给它擦屁股,应当强制它回到重新阅读核心文件、重新梳理调用链。局部修补隧道是编码代理最常见的失败模式,也是成功率评测里最吃亏的一种——因为单看每一步,它都像在认真工作。
4.3 自信闭环:第一印象成了过滤器
第三类失败轨迹是“自信闭环”。代理在任务早期就形成了对问题原因的判断,例如“这个测试失败肯定是因为某个字段没有初始化”。问题在于,它对这个首个假设异常执着,后续接收到的所有新信息,都会被它扭曲解释成支持自己假设的证据。
这类轨迹最隐蔽。表面上代理每一步都在认真读输出、认真做修改,没有任何循环或打转的迹象,但它的判断从来没有真正更新过。即使测试错误信息已经完全指向另一个方向,它也会说“这说明我们的方向是对的,只是还差一步”。自信闭环的本质是证据过滤,而证据过滤在单条命令的尺度上是看不出来的,只能在轨迹层面看到,很多轮之后,代理仍在使用第一步形成的那个心智模型。
这种模式让我想到很多新手开发者的调试坏习惯:先猜一个原因,然后所有观察都为了证明这个猜测。论文展示的轨迹数据里,自信闭环往往和“提前给出最终结论”强相关——代理是在 Step 2 就已经在输出里给出结论性判断的,而后面的二十步都只是在走这个结论的流程。要对抗它,最有效的是每隔若干步强制代理总结“仍然支持我假设的证据”和“反对我假设的证据”。
4.4 长跑失忆:上下文窗口把前面的判断吃掉了
第四类失败轨迹根植于一个非常工程化的问题:上下文窗口容量有限。当轨迹变长,早期读取的文件内容、早期失败的测试输出、早期定下的约定,都会慢慢被挤出上下文窗口。代理失去了对任务早期状态的可靠记忆,于是开始用局部上下文来推断全局状态,行为逐渐自相矛盾。
典型表现是:前十分钟代理明确说过“确认文件 X 的某个函数不需要改动”,二十分钟后它却开始修改那个函数,并且修改理由与之前的记录完全矛盾。人类读者从这个轨迹中一眼就能看出失忆,但代理自己是浑然不觉的——对于它而言,模模糊糊的上下文就是这个世界的全部。
这类失败轨迹给我们的启示是直接而实用的:不要让代理在单次上下文里连续工作太久。最稳妥的做法是把大任务拆成多个短任务,每做完一个子任务就把关键结论固化到文档或代码注释里,而不是依赖代理自己在长上下文里维护记忆。上下文是编码代理最稀缺的资源,长跑失忆本质上就是资源耗尽后引发的一致性崩溃。
4.5 失败模式的共同点小结
纵览这四类失败轨迹,它们有一个共同点:失败不是凭空发生的,而是在轨迹的某个位置开始失去了自我纠错的反馈回路。复读机循环是失去对错误信息的解读;局部修补隧道是失去对全局结构的掌握;自信闭环是失去对证据的客观评估;长跑失忆是失去对早期上下文的访问。如果把代理想象成一个闭环控制系统,这些失败模式对应的是控制器不同环节的失效。
论文并没有提出一个统一的修复算法,因为它的目的本来就是分类和描述。但有了这四类可命名的轨迹形态,后面无论是做监控、做干预、做评测还是做训练数据过滤,都有了具体可操作的抓手。这让我想到软件工程里的 postmortem(事故复盘):不是给出“以后小心点”这种废话,而是把事故按过程类型归档,之后每次遇到同类轨迹,都能直接套用经验。
5. “失败轨迹学”能用到哪里:三个层面的实际启示
5.1 给日常使用 agent 的人:让失败可见
作为普通用户,我们不需要写复杂的分析工具,但完全可以借鉴论文的思维,给自己建立一个最简陋的“轨迹巡检”习惯。我现在每派一个任务,会做三件事:
- 第一步:给任务设定一个 step 上限(比如 20 步)。一旦代理超过这个步数还没给出中间结果,就手动打断,回看轨迹,而不是等它自己跑死。
- 第二步:在任务描述里明确要求代理分阶段汇报,比如“先告诉我你的计划,然后按计划执行,每 5 步给我一次进度和测试状态”。这让轨迹在产生过程中就具备了可读性,而不是事后才去解析。
- 第三步:失败之后不直接换一个提示词再跑,而是先看轨迹里是否出现了上面说的四类模式。如果是复读机循环,加提示词也救不了,应该换任务表达;如果是局部修补隧道,应该强制它回退重读;如果是自信闭环,应该主动投喂反面证据。
这套流程不依赖任何高级工具,纯靠人眼加终端记录就能执行。论文教给我的不是某个具体技巧,而是一种信念:失败轨迹是值得看的,而且看了一定会有所发现。
5.2 给设计 agent 的人:把过程做成可干预变量
如果你正在开发自己的编码代理,那这篇论文的启示更直接。当下不少编码代理的系统设计,只关注最终结果的奖励信号——任务完成了就给正反馈,没完成就给负反馈。但失败作为过程这一视角告诉我们,中间过程的很多信号比最终结果更有训练价值。
例如,复读机循环和局部修补隧道可以在轨迹中实时检测,一旦识别出这类形态,就从系统层面注入新的指令:“你刚刚连续执行了两次相同的命令,请暂停并分析错误信息”。这相当于给编码代理加了一个失败模式监测器,监测的不再是代码质量,而是代理自身的行为轨迹质量。
还可以把“回退行为”设计成显式的候选动作。我看到不少失败轨迹里,代理明明已经在往错误方向走,却缺乏回退的机制和激励。如果你在设计 agent,可以考虑在每一步给出“回退到上一个检查点”的可选动作,并让系统对回退行为给出正向强化,而不是只奖励向前的修改。
5.3 给做工程平台的人:轨迹应该是一等公民
再往上走一层,如果你的团队在做 agent 相关的开发平台,论文实际上提供了一个产品层面的建议:把轨迹提升为一等公民。现在的多数 coding agent 产品,界面就是一个对话窗口加一个文件 diff。用户看不到代理花了多少步、跑了哪些命令、做重新读取了哪些文件、是否循环过。全部信息被压平成一个对话流,用户只能在最终结果和对话记录之间来回猜测。
论文的轨迹解剖框架完全可以产品化为一个可视化的“轨迹视图”:以时间线展示每一步命令、文件改动、测试事件,用颜色标记异常信号(重复命令、测试连续失败、上下文截断)。用户不用读懂技术细节,只看颜色变化就能判断代理是健康运行还是正在走向失败。甚至可以在检测到失败模式时弹出提示,问用户是否要介入。
对团队的另一个价值是失败语料库。一旦轨迹成为一等公民,每一次失败都自动沉淀成结构化的案例,日积月累就是高质量的训练数据和分析数据。现在的编码代理开发中,真正稀缺的不是成功案例,而是详尽标注的失败轨迹,尤其是带有“失败模式标签”的轨迹。谁能先把这套数据体系建起来,谁就有更大的迭代优势。
5.4 我看到的局限
当然,论文的方法也有明显的应用边界。CLI 轨迹分析高度依赖文本协议,如果代理运行环境升级成了多模态、大量编译截图或复杂 GUI,解剖框架需要重新设计。另一个局限是,论文关注的轨迹大多集中在代码修复和单元级任务,面对极端复杂的重构任务,失败轨迹的形态可能远不止这四类,还需要更大规模的研究来补充。
但这些局限不影响核心论点的成立。它要论证的是“失败是过程,而过程是可以被解剖和分类的”,这一点已经被轨迹数据充分验证了。后续的研究完全可以站在这个框架上继续细化。
6. 读完论文之后,我改了自己使用 agent 的三种习惯
最后分享一点个人实践。读完这篇论文的那个下午,我去翻了自己过去一周让代理干活留下的终端记录,结果发现失败轨迹几乎全都落进了论文描述的那几种框架里。有两三次是复读机循环,代理反复跑同一个测试命令,只是改了几个无关紧要的打印语句;有一次是典型的局部修补隧道,它在同一个文件里打了十几个补丁,最后推翻了最初正确的设计;还有一次是长跑失忆,任务太长,代理早忘了自己最开始确认过的约定。
从那以后,我给自己立了几条规矩:第一,凡是预计超过二十步的任务,一律拆成多轮,每轮之间把关键结论写进任务上下文;第二,如果看到同一条命令出现三次以上,直接打断,不恋战;第三,任务失败后先花五分钟看轨迹,确认它是哪一类失败,再决定要不要重试、怎么重试。这三条规矩不保证每次任务都能成功,但显著减少了我因为盲目重试而浪费掉的时间。
如果你也在重度使用编码代理,我强烈建议你养成随手看轨迹的习惯。别等失败了才想起去看,成功之前也要保持对过程的观察。很多时候,一个代理有没有走在正确的道路上,根本不需要等到测试结果出来——轨迹本身的形态已经足够说明问题了。论文给了我们一套语言和框架去讨论它,剩下的,就是把它带到你自己的终端里,用起来。