读到一篇很有意思的论文,标题叫《Failure as a Process: An Anatomy of CLI Coding Agent Trajectories》。这两年命令行AI编程工具越来越多,从最初的“能写一段函数”到现在能在真实仓库里自己定位问题、改代码、跑测试,甚至提交补丁。但大多数人的使用体验并不完全是宣传里那种顺畅:代理经常卡在某个奇怪的循环里,或者自认为完成了任务,实际却离题万里。我们通常把这归结为“模型不够聪明”,然后草草放弃。这篇论文却提供了一个完全不同的视角:失败不是一次性的坏结果,而是可以被观察、被解剖、被分类的过程。顺着一条轨迹去看,很多失败其实在很早的时候就种下了种子。这篇文章适合三类人:一类是正在开发或维护CLI编码代理的工程师,一类是在日常工作中重度使用AI编程助手的开发者,还有一类是做LLM Agent评估与可观测性研究的人。如果你只想看结论,那我会告诉你论文的核心贡献是把“失败”从评价指标里拎出来,变成一种过程化的事件流,并给出了系统的分析框架。但更值得读的是它的方法——到底如何从一段段轨迹日志里找出失败发生的根源。下面我从问题背景、实验设计、核心发现到实践启发,一条一条说清楚。1. 研究背景:为什么要把“失败”摆上台面1.1 CLI编码代理到底是什么先对齐一下概念。所谓CLI编码代理,可以简单理解成在终端环境中运行的AI助手,它接收一段自然语言描述的任务,然后通过一连串动作去完成代码修改。常见的动作包括读取文件、搜索符号、执行构建与测试命令、运行静态分析、编辑源码文件,最后生成一个差异补丁。和我们熟悉的IDE插件不同,CLI代理不再只是一个“补全括号”的辅助工具,而是承担了相当多的自主判断:先读哪个文件,改完要不要跑测试,失败之后下一步怎么办,这些问题都由模型来决定。从工程角度看,这种代理更像一个“实习生工程师”,只是这个实习生可能看过大量代码,但缺少常识。它常常会执着于某一条路径,忽略明显的线索,甚至在没有验证的情况下声称完成了任务。正因为它拥有很高的自主性,出问题的空间也就极大。这也是论文选择CLI代理作为研究对象的原因——它不是键盘上的自动建议,而是一个完整的行为体,有状态、有决策、有轨迹。1.2 现有评估方式的盲区那么传统的评估方式又是怎样的呢?绝大多数情况下,我们会给代理一个任务,然后在最后看它生成的补丁能否通过固定的测试集。行就成功,不行就失败。这个评价逻辑干净利落,适合横向对比模型,但它有一个非常大的盲区:它把失败当成一个点,而不是一条线。打个比方,一个代理在第一步就把任务方向理解错了,后续每一步都非常努力,改了三十个文件,最后补丁当然不合格。另一个代理前二十步都走得很好,只是在最后一步验证时忘记运行测试,导致改动和预期有偏差。这两个失败点,在传统指标里都只是“失败”两个字。但在真实使用中,前者的病因是理解层,后者的病因是流程层,需要的修复策略完全不同。如果只看结果,我们永远搞不清楚问题出在哪个环节。这篇论文真正有意思的地方就在这里。它主张把失败看作一个过程,像解剖青蛙一样,把一条失败轨迹拆成阶段,观察每一条决策链上到底发生了什么。论文标题里的“Anatomy”一词用得极其贴切,中文是“解剖学”,它暗示的是一种系统性的、结构化的研究方法,而不是笼统地叹息一句“又失败了”。1.3 作者想解决什么问题说得再具体一点,作者真正想解决的问题可以拆成三个层面。第一,失败是否具有可复现的结构?也就是说,大量失败轨迹背后,是不是存在少数几种反复出现的模式。第二,失败发生在整个任务轨迹的哪个阶段?是刚刚开始、中间执行、还是接近收尾时。第三,失败与代理的外部行为之间有没有可识别的信号?比如它频繁执行同一条命令、长时间不读取新文件、或者过早停止运行测试。这三个问题围绕的是同一个核心:把失败作为过程来研究。一旦有了过程级的理解,我们就能在代理运行中去预测失败、诊断失败、甚至自动恢复失败。这比单纯增加模型参数要有意思得多,也务实得多。2. 轨迹数据与实验设计2.1 轨迹数据里到底有什么了解了一篇论文的动机,接下来就该看它的实验数据是怎么设计的。老实说,很多Agent方向的研究文章会在模型和prompt上花大量篇幅,但这篇论文的重心明显放在数据形式上。作者收集的是CLI编码代理在完成真实软件工程任务时的完整轨迹,而不是只留下一个最终补丁。轨迹中的一条记录到底长什么样?如果你已经接入过某种代理的日志系统,应该很容易理解。它大致包含这样几个要素:任务的自然语言描述、代理内部思考链、每一步采取的动作类型、动作对应的具体参数、命令产生的标准输出和错误输出、以及执行前后的文件状态变化。为了把每一步串联在一起,还会记录时间戳和相对步数。我画一个很简化的轨迹片段,方便你直观感受。下面的例子展示了一个代理在第17步运行测试并失败的行为,它的下一步是去读取源文件。{ "task": "修复模块中导致测试失败的空指针异常", "step": 17, "action": "RunCommand", "command": "pytest tests/test_null_pointer.py", "exit_code": 1, "output": "1 failed, 2 passed", "context": { "recent_files": ["src/handler.py", "tests/test_null_pointer.py"], "git_diff": null }, "next_action": "ReadFile" }注意几个细节:action的类型是结构化的,不是把一条命令扔进黑盒就完了;exit_code和output被独立保存,这就允许研究者分析“这是不是一次可恢复的失败”;context字段保留了代理最近的注意力范围,用来判断它的行为是否陷入局部。轨迹的粒度越细,后续的阶段分类和信号提取就越可靠。2.2 失败事件是如何被标注出来的有了轨迹之后,最麻烦的工作是标注失败。作者团队没有简单地把“任务没有达成”当作唯一的失败信号,而是把失败从“评价维度”挪进了“事件层面”。也就是说,一条轨迹可能需要标注出多个失败点:哪一步的命令执行报错了,哪一次文件编辑覆盖了错误的内容,哪一步代理明显偏离了任务描述,哪一处验证环节缺失。标注方式我猜测是规则筛选加上人工校验的混合流程。因为命令的退出码、测试失败的数量这些信息完全可以自动化提取;而“代理是不是误解了任务”这种语义判断,就必须借助有经验的工程师逐一阅读轨迹。这其实是很多Agent研究团队容易低估的投入点。不少论文只对最终结果打一个分,根本不去标注过程事件,导致分析环节只能靠讲故事。这篇论文愿意做细粒度的标注,我认为是它最有价值的部分之一。2.3 过程化指标的设计逻辑标注之后,自然要引入一系列过程化指标。论文并没有抛弃成功/失败这个最终指标,而是在它之上加了好几层信息。我比较关注的是几个直接能落地的指标。第一个是失败阶段定位,所有失败事件出现在轨迹的哪个区间,比如前25%、中间50%、后25%。第二个是失败密度,用失败事件数量除以轨迹总步数,用来衡量一条轨迹到底有多“挣扎”。第三个是恢复率,指的是在一次失败事件之后,代理是否做出了有效的纠正行为,并且后续步骤没有再重复同样的错误。第四个是提前终止率,代理在没有完成验证的情况下主动宣告任务完成的比例。这些指标一起使用,能看出很多有趣的关联。作者在论文里对这几个指标做了统计,结果显示它们之间的相关性很强,也就是说,一条轨迹是“慢性挣扎型”还是“突然崩溃型”,在早期就有迹可循。2.4 任务集与实验环境设定为了不让实验结论过于偏科,作者选取的任务集包含了多种类型的软件工程问题:有的是bug修复,有的是功能增强,还有一些是依赖升级后需要调整代码。这在我的阅读体验里是个加分项,因为很多Agent研究都扎堆在单纯的bug修复上,而真实开发中大量的时间花在了“改动后如何不影响旧逻辑”上。任务集覆盖这种差异,才能让“失败过程”的分析更有普适性。环境方面,作者给每个代理提供了一个接近真实的沙盒环境,里面有可用的编译器、依赖管理工具和测试框架。CLI代理可以在环境中自由执行命令,但不能访问外部网络。这一点很关键:如果代理能够随时搜索网络,那失败轨迹的成因就会多出一个外部变量,很难归因。锁定环境后,大量失败就都能回到“模型决策”和“上下文管理”这两个内部因素上。3. 核心发现:失败的分型与生命周期3.1 失败的五种常见类型接下来进入文章的重头戏。作者通过对大量轨迹的解剖,把失败事件归纳成了几种反复出现的模式。我结合论文中的描述和自己的理解,整理成一张速查表。失败类型典型表现常见阶段诊断信号任务误解代理从第一步就在解决另一个问题早期首次编辑文件与任务无关上下文丢失需要的信息出现过,但代理没有再次利用中期反复读取同一个文件却没形成修改工具使用错误命令参数拼错、找错文件路径任意阶段高频出现非零退出码验证缺失没有运行测试就宣告完成后期生成补丁后未执行验证命令恢复僵局失败后不断用相同方式重试中后期重复动作占比过高任务误解这类失败最致命,因为偏离方向越久,代理积累的错误上下文就越多,后期几乎不可能自行纠正。作者在论文里提到,从失败轨迹的反向追踪看,相当比例的最终失败,根因都能追到轨迹前五步之内。这个结论听起来有点反直觉:我们总认为复杂的失败应该有复杂的原因,但实际上很多灾难性结果都是最早期的一个小偏差被不断放大造成的。上下文丢失也很有意思。CLI代理往往会读取大量文件,然后把相关代码片段塞进上下文里。但当轨迹很长、文件很多时,模型的选择性注意力会出现漂移,它可能把早期读到的关键文件忽略掉,转而去修改那些关系不大的部分。这时轨迹的长度反而成了风险因素。作者给出一个很直觉的统计:失败密度和轨迹长度呈正相关,越长的轨迹越容易积累更多失败事件。这说明长轨迹不仅要考虑性能损耗,还要考虑信息遗忘带来的可靠性问题。工具使用错误在传统评测里往往被看作是琐碎的小问题,但在这篇论文里,它被提升到了一个很关键的位置。因为CLI代理一旦开始执行命令,它就在和环境进行真实交互,一个错误的路径、一个非预期的参数,都可能直接摧毁掉之前的所有有效工作。论文数据中很大一部分失败事件是命令非零退出,而代理面对这种错误时的常见反应是加大输出信息的宽度,反复打印错误日志,而不是停下来检查自己写出的命令。这种“盲人摸象”式的行为,是一个非常典型的工程化失败信号。验证缺失是最让人头疼的一种失败。代理明明没有跑过测试,却非常有信心地输出“已完成”。论文把它归类为失败,我觉得完全正确。代码改动不是写作,没有测试的确认,改动是否成立都是未知数。作者通过轨迹分析发现,验证缺失和任务失败之间存在强相关性,而且验证缺失很难被传统指标直接发现——从最终patch看,代码可能有模有样,但一跑测试就露馅。恢复僵局则是很多人在实际使用中的体感来源:代理卡在同一个坑里反复横跳。它可能先尝试方案A失败,然后不做任何修正地又试一遍方案A,或者把参数稍微改一点,却依然谈不上策略更新。作者用“重复动作占比”这个指标把这类轨迹识别出来,指出恢复僵局的本质不是“尝试不够”,而是“没有形成对失败假设的修正”。这就像一个实验者每次重复同一个实验,却期望得到不同结果。3.2 失败的时间分布与级联效应光有类型还不够,作者进一步分析了失败在时间轴上的分布特征。如果把所有失败事件按任务轨迹的相对位置来切分,会看到一个非常明显的模式:失败事件并不是均匀分布的,而是存在两个高峰。第一个高峰出现在轨迹早期,也就是代理刚接到任务、还没完全理解仓库结构的时候。这个阶段的失败多与任务误解、错误的信息获取策略有关。第二个高峰则出现在轨迹后期,常见于验证阶段,比如测试失败、遗漏测试、补丁冲突。两个高峰之间反而相对平稳,说明如果代理熬过了早期的方向选择,中段通常不会出现特别剧烈的崩溃,真正的问题往往是以一种慢性累积的方式蔓延。这里值得展开说一下级联效应。论文中有一个很扎眼的发现:一次看起来不太重要的失败,比如某条命令运行失败,如果代理没有正确识别失败原因,它会围绕着这个失败事件反复打转,为了“绕过”这个问题产生一连串新动作,而这些新动作又可能引入新的错误。最后整条轨迹就像滚雪球一样,从一个小失败滚成完全失控的状态。用一个生活化的类比:就像你做饭时不小心放多了盐,正常的处理方式是尝一下味道、判断咸度、采取补救;而一个处于失败级联中的代理,可能会把整锅菜倒掉,重新开始,然后在新的锅里又放多了盐,如此往复。这种级联效应的存在,意味着我们不能只关注失败事件本身,还要关注失败之后的“决定链”。论文里用“早期失败信号”来预测最终结果,原理就在这里:一个未能被正确处理的失败,很容易成为后续级联失败的种子。3.3 失败的可预测性与早期预警既然失败存在规律,自然引出一个很实用的问题:我们可以从轨迹早期就预测到它大概率会失败吗?作者的答案是肯定的,而且给出了一些具体的预测信号。最显著的信号包括:初始动作与实际任务无关、首次文件读取集中在错误区域、前几条命令中有非零退出码、以及在给出最终结果前没有触发任何验证命令。当然,单一信号未必可靠,但多个信号叠加时,预测的准确率会显著上升。作者构建了一个基于早期轨迹特征的预测模型,效果远好于随机猜测。这也就意味着,未来的CLI代理完全可以在运行过程中阶段性地问自己一句:“我是不是已经跑偏了?”,而不是等到最后验收才被测试打脸。这项研究最让我欣赏的是,它给“失败”赋予了结构。失败不是一个随机噪声,而是由多个阶段、多种类型、多种信号组成的过程。理解这个过程,就和理解程序运行时的调用栈一样,是调试Agent的第一步。4. 实操启示:如何利用这份解剖改进自己的Agent4.1 给Agent开发者的三项改进读完论文不能白读,总要落实到工程上。如果你正在维护一个CLI编码代理,我认为可以从论文结论中提取出三个优先度极高的改进方向。第一,把轨迹日志做成结构化的第一等公民。现在很多代理框架的日志是给开发者看调试用的,而不是给Agent自己看的数据结构。论文的分析之所以能成立,完全是因为轨迹中的每个动作都有清晰的action type、exit code和context信息。如果你的日志不是结构化的,那就赶紧改造。哪怕暂时不做论文里的过程化指标,结构化的轨迹也能让你在用户报bug时快速复盘。第二,设计“早期跑偏检测”的规则引擎。不需要一开始就上模型预测,你可以先实现一些硬规则,比如:代理的前三次文件读取如果都指向任务描述中没有提到的区域,就触发一次检查;如果五步内出现三次非零退出码,就强制代理暂停并重新概括任务。这种规则简单有效,论文里的信号统计就是它最直接的依据。第三,把验证器放在链路里,而不是放在终点。代理不应该等到最后才通过“运行所有测试”来确认结果。更好的做法是让代理在每次修改关键文件后都运行一个窄范围内的定向测试,用测试结果来指导下一步修改。论文里的验证缺失失败类型已经证明,缺少验证的轨迹风险极高。你的Agent越早拥有验证的习惯,最终的成功率就会越高。4.2 给Agent使用者的任务设计建议如果你只是CLI代理的使用者,可能觉得自己改不了内部逻辑,但任务输入和管理方式也会极大影响失败轨迹。论文里关于任务误解的发现直接指向一个结论:初始prompt是真的值得花时间写的。我的建议是,在给代理分配任务时,不仅要说“修什么”,还要说清楚“怎么算修好”。比如你让它修复一个空指针异常,可以额外提供复现命令和预期行为:“运行pytest tests/test_handler.py,当前在测试test_load时会报TypeError,修复后该测试应通过,且其他测试不受影响。”当任务描述里包含明确的验收标准时,代理早期就不会完全迷失在代码搜索中,失败的集中期能得到显著缓解。另外,限制执行范围也很重要。CLI代理如果拥有无限权限,它会热衷于做一些不相关的实验。你可以通过环境配置约束它只能访问某个目录,或者禁用危险命令。论文数据里工具使用错误如此突出,一部分责任其实在用户侧——我们给了太宽泛的自由,却没有给它足够的护栏。4.3 搭建一个简易的轨迹失败监控脚本理论知识说得再多,不如动手做一个最小例子。我已经开始在自己维护的一个CLI代理工具上加了轨迹日志,并用一个非常简单的Python脚本做了初步监控。这里分享一个最基础的版本,统计每条轨迹中的失败事件类型和分布。import json import sys from collections import Counter def load_trajectory(path): with open(path, "r", encoding="utf-8") as f: return json.load(f)["steps"] def classify_failure(step): if step.get("action") == "RunCommand" and step.get("exit_code", 0) != 0: return "tool_error" if step.get("action") == "EditFile" and step.get("validated") is False: return "missing_verification" if step.get("action") == "RunCommand" and step.get("command") in step.get("recent_commands", []): return "repeated_retry" return None def monitor(path): steps = load_trajectory(path) total = len(steps) if total == 0: print("empty trajectory") return failures = [] recent_commands = [] for i, step in enumerate(steps): if step.get("command"): recent_commands.append(step["command"]) label = classify_failure(step) if label: failures.append((i, label, step)) if len(recent_commands) > 10: recent_commands.pop(0) stats = Counter(label for _, label, _ in failures) print(f"total steps: {total}") print(f"failure events: {len(failures)}") for label, count in stats.most_common(): print(f"{label}: {count}") for i, label, step in failures: print(f" step {i}: {label} -> {step.get('command')}") if __name__ == "__main__": monitor(sys.argv[1])这个脚本的逻辑很简单:把非零退出码标成tool_error,把没有验证标记的编辑标成missing_verification,把近期重复的命令标成repeated_retry。实际使用中你会发现,哪怕不做任何复杂的数据分析,光是扫一眼这些标签的分布,就能判断代理是在中段挣扎,还是在起点就已经跑偏。针对不同标签设计不同的恢复策略,是论文方法论最直接的一种落地方式。4.4 从监控到自动化恢复监控只是第一步,再往前走一步就是自动化恢复。我最近在实践里的一个体会是,不要试图一次性解决所有失败类型,而是先从最高频、最机械的失败入手。比如工具使用错误,完全可以在监测到非零退出码后,让代理读取命令帮助文本,或者要求它重写命令并附带参数来源;比如验证缺失,可以在代理输出完成信号时强制拦截,要求它先运行一次目标测试。论文里那种把失败看成过程的态度,在自动化恢复的设计里也有很强的指导意义:失败之后的第一个动作不是继续重试,而是“定位失败类型”。很多Agent之所以卡死,就是因为把“失败”当成一个整体来应对,没有区分它是命令错误、验证缺失还是方向偏航。一旦你能在失败事件发生后快速贴上标签,你就已经在往正确的路径上逼近了。5. 批判性思考与讨论5.1 论文的局限在哪再好的论文也有边界。首先是任务集和环境的代表性。CLI编码代理的失败模式极易受任务类型影响,如果一个任务本身描述模糊、验收标准不清晰,那任何代理都会大概率失败。论文挑选的任务集虽然涵盖了多种类型,但终究是被限定的范围。在更开放的、接近真实产品迭代的开发任务中,失败轨迹的分布很可能完全不同。其次是失败标注的主观性。把“验证缺失”和“任务误解”分开看似简单,但在具体轨迹边界上经常存在灰色地带。比如代理在某个文件里改了十行代码,其中有几行确实与任务相关,剩余几行则是顺手重构。这种部分偏离算不算任务误解?不同的标注者可能给出不同答案。论文采用了人工校验来减缓这个问题,但它并没有完全消除。5.2 从失败过程研究到鲁棒性评估论文的影响力不应只停留在论文阅读层面。它给Agent评估提供了一个新方向:过程化的基准测试。传统benchmark只给一个pass@k,太粗。未来的评估完全可以记录“恢复率”、“失败类型分布”、“第一处不可逆错误出现的步数”这些指标。甚至可以做失败注入测试:故意在环境里放一个损坏的依赖、一个冲突的分支,看看代理能不能识别出异常并调整策略。这种评估更贴近真实工程中的应变能力,而不只是静态的代码修改能力。还有一个延伸方向是把失败过程用于数据构造。既然知道了失败类型和轨迹特征,就可以设计针对性的数据增强,比如在训练数据中加入更多的早期上下文丢失场景,或者加入需要恢复僵局的轨迹。这对提升CLI代理的鲁棒性,可能比单纯扩大模型参数更有效。5.3 对开发者工具生态的影响最后聊聊对工具生态的影响。过去我们调试IDE插件,靠的是断点和log。而CLI代理是一个决策系统,最简单的断点无法体现它的“思维过程”。这篇论文强调的轨迹结构化,其实正在催生一类新的开发者工具:Agent调试器、轨迹回放器、失败模式分析面板。你可以把完整任务回放成时间线,看到代理每一步在读什么文件、执行什么命令、改了哪些行,然后在失败点打上标签,反向寻找早期病因。我甚至觉得,未来的CLI代理会自带“失败复盘”功能:在一次任务失败后,它能自动输出一份失败轨迹摘要,包含失败类型分布、关键偏离点、恢复建议。这会让使用者从“代理没做好”的模糊抱怨,变成“它在第三步就误读了模块范围”的精准反馈。对于长期依赖AI编码工具的人来说,这种透明度比模型能力的微小提升更有吸引力。5.4 还有哪些值得继续追问的问题论文读完,留在我心里还有几个没有完全解决的问题。一个是失败过程的“可恢复性”到底由什么决定:是模型能力、任务难度、还是上下文管理策略?作者对轨迹做了分组分析,但这个因果链条还没有完全建立起来。另一个是多代理协作场景下的失败传播:如果是一个代理负责重构,另一个代理负责测试,失败会不会呈现完全不同的形态?现在所有分析都基于单代理,这还是被简化过的世界。最后一个问题是关于失败信号的时效性。早期预测模型确实能提前判断风险,但预警之后怎么干预,干预会不会打断正常的探索过程,这里面还有很大的工程空间。把一个高风险轨迹及时叫停,和把一个正在探索有效路径的轨迹误伤,两者可能只差一个很微弱的信号。这恐怕也是未来研究最值得投入的地方。读到这儿,你应该能理解我为什么花这么大力气来推荐这篇论文了。它没有给出一个“让代理永不失败”的万能药,而是提供了一种看待失败的方式,一种把混乱轨迹转译成可读结构的方法。我自己开始实践之后,最大的感受是:代理的失败不可怕,可怕的是失败没有留下任何可回放的痕迹。最后再分享一个小技巧。我在自己的Agent配置里加了一条干预规则:如果连续三分钟没有产生任何有效文件变化,同时执行了三条以上相同的命令,就自动暂停并向用户发送一段当前状态摘要。这个规则完全来自论文里的“恢复僵局”失败模式。实测下来,它显著减少了我对着终端干瞪眼的时间。希望你也能把这篇论文里的解剖方法用起来,在自己和Agent的协作过程中,找到那些早期跑偏的种子。
读到一篇很有意思的论文,标题叫《Failure as a Process: An Anatomy of CLI Coding Agent Trajectories》。这两年命令行AI编程工具越来越多,从最初的“能写一段函数”到现在能在真实仓库里自己定位问题、改代码、跑测试,甚至提交补丁。但大多数人的使用体验并不完全是宣传里那种顺畅:代理经常卡在某个奇怪的循环里,或者自认为完成了任务,实际却离题万里。我们通常把这归结为“模型不够聪明”,然后草草放弃。这篇论文却提供了一个完全不同的视角:失败不是一次性的坏结果,而是可以被观察、被解剖、被分类的过程。顺着一条轨迹去看,很多失败其实在很早的时候就种下了种子。这篇文章适合三类人:一类是正在开发或维护CLI编码代理的工程师,一类是在日常工作中重度使用AI编程助手的开发者,还有一类是做LLM Agent评估与可观测性研究的人。如果你只想看结论,那我会告诉你论文的核心贡献是把“失败”从评价指标里拎出来,变成一种过程化的事件流,并给出了系统的分析框架。但更值得读的是它的方法——到底如何从一段段轨迹日志里找出失败发生的根源。下面我从问题背景、实验设计、核心发现到实践启发,一条一条说清楚。1. 研究背景:为什么要把“失败”摆上台面1.1 CLI编码代理到底是什么先对齐一下概念。所谓CLI编码代理,可以简单理解成在终端环境中运行的AI助手,它接收一段自然语言描述的任务,然后通过一连串动作去完成代码修改。常见的动作包括读取文件、搜索符号、执行构建与测试命令、运行静态分析、编辑源码文件,最后生成一个差异补丁。和我们熟悉的IDE插件不同,CLI代理不再只是一个“补全括号”的辅助工具,而是承担了相当多的自主判断:先读哪个文件,改完要不要跑测试,失败之后下一步怎么办,这些问题都由模型来决定。从工程角度看,这种代理更像一个“实习生工程师”,只是这个实习生可能看过大量代码,但缺少常识。它常常会执着于某一条路径,忽略明显的线索,甚至在没有验证的情况下声称完成了任务。正因为它拥有很高的自主性,出问题的空间也就极大。这也是论文选择CLI代理作为研究对象的原因——它不是键盘上的自动建议,而是一个完整的行为体,有状态、有决策、有轨迹。1.2 现有评估方式的盲区那么传统的评估方式又是怎样的呢?绝大多数情况下,我们会给代理一个任务,然后在最后看它生成的补丁能否通过固定的测试集。行就成功,不行就失败。这个评价逻辑干净利落,适合横向对比模型,但它有一个非常大的盲区:它把失败当成一个点,而不是一条线。打个比方,一个代理在第一步就把任务方向理解错了,后续每一步都非常努力,改了三十个文件,最后补丁当然不合格。另一个代理前二十步都走得很好,只是在最后一步验证时忘记运行测试,导致改动和预期有偏差。这两个失败点,在传统指标里都只是“失败”两个字。但在真实使用中,前者的病因是理解层,后者的病因是流程层,需要的修复策略完全不同。如果只看结果,我们永远搞不清楚问题出在哪个环节。这篇论文真正有意思的地方就在这里。它主张把失败看作一个过程,像解剖青蛙一样,把一条失败轨迹拆成阶段,观察每一条决策链上到底发生了什么。论文标题里的“Anatomy”一词用得极其贴切,中文是“解剖学”,它暗示的是一种系统性的、结构化的研究方法,而不是笼统地叹息一句“又失败了”。1.3 作者想解决什么问题说得再具体一点,作者真正想解决的问题可以拆成三个层面。第一,失败是否具有可复现的结构?也就是说,大量失败轨迹背后,是不是存在少数几种反复出现的模式。第二,失败发生在整个任务轨迹的哪个阶段?是刚刚开始、中间执行、还是接近收尾时。第三,失败与代理的外部行为之间有没有可识别的信号?比如它频繁执行同一条命令、长时间不读取新文件、或者过早停止运行测试。这三个问题围绕的是同一个核心:把失败作为过程来研究。一旦有了过程级的理解,我们就能在代理运行中去预测失败、诊断失败、甚至自动恢复失败。这比单纯增加模型参数要有意思得多,也务实得多。2. 轨迹数据与实验设计2.1 轨迹数据里到底有什么了解了一篇论文的动机,接下来就该看它的实验数据是怎么设计的。老实说,很多Agent方向的研究文章会在模型和prompt上花大量篇幅,但这篇论文的重心明显放在数据形式上。作者收集的是CLI编码代理在完成真实软件工程任务时的完整轨迹,而不是只留下一个最终补丁。轨迹中的一条记录到底长什么样?如果你已经接入过某种代理的日志系统,应该很容易理解。它大致包含这样几个要素:任务的自然语言描述、代理内部思考链、每一步采取的动作类型、动作对应的具体参数、命令产生的标准输出和错误输出、以及执行前后的文件状态变化。为了把每一步串联在一起,还会记录时间戳和相对步数。我画一个很简化的轨迹片段,方便你直观感受。下面的例子展示了一个代理在第17步运行测试并失败的行为,它的下一步是去读取源文件。{ "task": "修复模块中导致测试失败的空指针异常", "step": 17, "action": "RunCommand", "command": "pytest tests/test_null_pointer.py", "exit_code": 1, "output": "1 failed, 2 passed", "context": { "recent_files": ["src/handler.py", "tests/test_null_pointer.py"], "git_diff": null }, "next_action": "ReadFile" }注意几个细节:action的类型是结构化的,不是把一条命令扔进黑盒就完了;exit_code和output被独立保存,这就允许研究者分析“这是不是一次可恢复的失败”;context字段保留了代理最近的注意力范围,用来判断它的行为是否陷入局部。轨迹的粒度越细,后续的阶段分类和信号提取就越可靠。2.2 失败事件是如何被标注出来的有了轨迹之后,最麻烦的工作是标注失败。作者团队没有简单地把“任务没有达成”当作唯一的失败信号,而是把失败从“评价维度”挪进了“事件层面”。也就是说,一条轨迹可能需要标注出多个失败点:哪一步的命令执行报错了,哪一次文件编辑覆盖了错误的内容,哪一步代理明显偏离了任务描述,哪一处验证环节缺失。标注方式我猜测是规则筛选加上人工校验的混合流程。因为命令的退出码、测试失败的数量这些信息完全可以自动化提取;而“代理是不是误解了任务”这种语义判断,就必须借助有经验的工程师逐一阅读轨迹。这其实是很多Agent研究团队容易低估的投入点。不少论文只对最终结果打一个分,根本不去标注过程事件,导致分析环节只能靠讲故事。这篇论文愿意做细粒度的标注,我认为是它最有价值的部分之一。2.3 过程化指标的设计逻辑标注之后,自然要引入一系列过程化指标。论文并没有抛弃成功/失败这个最终指标,而是在它之上加了好几层信息。我比较关注的是几个直接能落地的指标。第一个是失败阶段定位,所有失败事件出现在轨迹的哪个区间,比如前25%、中间50%、后25%。第二个是失败密度,用失败事件数量除以轨迹总步数,用来衡量一条轨迹到底有多“挣扎”。第三个是恢复率,指的是在一次失败事件之后,代理是否做出了有效的纠正行为,并且后续步骤没有再重复同样的错误。第四个是提前终止率,代理在没有完成验证的情况下主动宣告任务完成的比例。这些指标一起使用,能看出很多有趣的关联。作者在论文里对这几个指标做了统计,结果显示它们之间的相关性很强,也就是说,一条轨迹是“慢性挣扎型”还是“突然崩溃型”,在早期就有迹可循。2.4 任务集与实验环境设定为了不让实验结论过于偏科,作者选取的任务集包含了多种类型的软件工程问题:有的是bug修复,有的是功能增强,还有一些是依赖升级后需要调整代码。这在我的阅读体验里是个加分项,因为很多Agent研究都扎堆在单纯的bug修复上,而真实开发中大量的时间花在了“改动后如何不影响旧逻辑”上。任务集覆盖这种差异,才能让“失败过程”的分析更有普适性。环境方面,作者给每个代理提供了一个接近真实的沙盒环境,里面有可用的编译器、依赖管理工具和测试框架。CLI代理可以在环境中自由执行命令,但不能访问外部网络。这一点很关键:如果代理能够随时搜索网络,那失败轨迹的成因就会多出一个外部变量,很难归因。锁定环境后,大量失败就都能回到“模型决策”和“上下文管理”这两个内部因素上。3. 核心发现:失败的分型与生命周期3.1 失败的五种常见类型接下来进入文章的重头戏。作者通过对大量轨迹的解剖,把失败事件归纳成了几种反复出现的模式。我结合论文中的描述和自己的理解,整理成一张速查表。失败类型典型表现常见阶段诊断信号任务误解代理从第一步就在解决另一个问题早期首次编辑文件与任务无关上下文丢失需要的信息出现过,但代理没有再次利用中期反复读取同一个文件却没形成修改工具使用错误命令参数拼错、找错文件路径任意阶段高频出现非零退出码验证缺失没有运行测试就宣告完成后期生成补丁后未执行验证命令恢复僵局失败后不断用相同方式重试中后期重复动作占比过高任务误解这类失败最致命,因为偏离方向越久,代理积累的错误上下文就越多,后期几乎不可能自行纠正。作者在论文里提到,从失败轨迹的反向追踪看,相当比例的最终失败,根因都能追到轨迹前五步之内。这个结论听起来有点反直觉:我们总认为复杂的失败应该有复杂的原因,但实际上很多灾难性结果都是最早期的一个小偏差被不断放大造成的。上下文丢失也很有意思。CLI代理往往会读取大量文件,然后把相关代码片段塞进上下文里。但当轨迹很长、文件很多时,模型的选择性注意力会出现漂移,它可能把早期读到的关键文件忽略掉,转而去修改那些关系不大的部分。这时轨迹的长度反而成了风险因素。作者给出一个很直觉的统计:失败密度和轨迹长度呈正相关,越长的轨迹越容易积累更多失败事件。这说明长轨迹不仅要考虑性能损耗,还要考虑信息遗忘带来的可靠性问题。工具使用错误在传统评测里往往被看作是琐碎的小问题,但在这篇论文里,它被提升到了一个很关键的位置。因为CLI代理一旦开始执行命令,它就在和环境进行真实交互,一个错误的路径、一个非预期的参数,都可能直接摧毁掉之前的所有有效工作。论文数据中很大一部分失败事件是命令非零退出,而代理面对这种错误时的常见反应是加大输出信息的宽度,反复打印错误日志,而不是停下来检查自己写出的命令。这种“盲人摸象”式的行为,是一个非常典型的工程化失败信号。验证缺失是最让人头疼的一种失败。代理明明没有跑过测试,却非常有信心地输出“已完成”。论文把它归类为失败,我觉得完全正确。代码改动不是写作,没有测试的确认,改动是否成立都是未知数。作者通过轨迹分析发现,验证缺失和任务失败之间存在强相关性,而且验证缺失很难被传统指标直接发现——从最终patch看,代码可能有模有样,但一跑测试就露馅。恢复僵局则是很多人在实际使用中的体感来源:代理卡在同一个坑里反复横跳。它可能先尝试方案A失败,然后不做任何修正地又试一遍方案A,或者把参数稍微改一点,却依然谈不上策略更新。作者用“重复动作占比”这个指标把这类轨迹识别出来,指出恢复僵局的本质不是“尝试不够”,而是“没有形成对失败假设的修正”。这就像一个实验者每次重复同一个实验,却期望得到不同结果。3.2 失败的时间分布与级联效应光有类型还不够,作者进一步分析了失败在时间轴上的分布特征。如果把所有失败事件按任务轨迹的相对位置来切分,会看到一个非常明显的模式:失败事件并不是均匀分布的,而是存在两个高峰。第一个高峰出现在轨迹早期,也就是代理刚接到任务、还没完全理解仓库结构的时候。这个阶段的失败多与任务误解、错误的信息获取策略有关。第二个高峰则出现在轨迹后期,常见于验证阶段,比如测试失败、遗漏测试、补丁冲突。两个高峰之间反而相对平稳,说明如果代理熬过了早期的方向选择,中段通常不会出现特别剧烈的崩溃,真正的问题往往是以一种慢性累积的方式蔓延。这里值得展开说一下级联效应。论文中有一个很扎眼的发现:一次看起来不太重要的失败,比如某条命令运行失败,如果代理没有正确识别失败原因,它会围绕着这个失败事件反复打转,为了“绕过”这个问题产生一连串新动作,而这些新动作又可能引入新的错误。最后整条轨迹就像滚雪球一样,从一个小失败滚成完全失控的状态。用一个生活化的类比:就像你做饭时不小心放多了盐,正常的处理方式是尝一下味道、判断咸度、采取补救;而一个处于失败级联中的代理,可能会把整锅菜倒掉,重新开始,然后在新的锅里又放多了盐,如此往复。这种级联效应的存在,意味着我们不能只关注失败事件本身,还要关注失败之后的“决定链”。论文里用“早期失败信号”来预测最终结果,原理就在这里:一个未能被正确处理的失败,很容易成为后续级联失败的种子。3.3 失败的可预测性与早期预警既然失败存在规律,自然引出一个很实用的问题:我们可以从轨迹早期就预测到它大概率会失败吗?作者的答案是肯定的,而且给出了一些具体的预测信号。最显著的信号包括:初始动作与实际任务无关、首次文件读取集中在错误区域、前几条命令中有非零退出码、以及在给出最终结果前没有触发任何验证命令。当然,单一信号未必可靠,但多个信号叠加时,预测的准确率会显著上升。作者构建了一个基于早期轨迹特征的预测模型,效果远好于随机猜测。这也就意味着,未来的CLI代理完全可以在运行过程中阶段性地问自己一句:“我是不是已经跑偏了?”,而不是等到最后验收才被测试打脸。这项研究最让我欣赏的是,它给“失败”赋予了结构。失败不是一个随机噪声,而是由多个阶段、多种类型、多种信号组成的过程。理解这个过程,就和理解程序运行时的调用栈一样,是调试Agent的第一步。4. 实操启示:如何利用这份解剖改进自己的Agent4.1 给Agent开发者的三项改进读完论文不能白读,总要落实到工程上。如果你正在维护一个CLI编码代理,我认为可以从论文结论中提取出三个优先度极高的改进方向。第一,把轨迹日志做成结构化的第一等公民。现在很多代理框架的日志是给开发者看调试用的,而不是给Agent自己看的数据结构。论文的分析之所以能成立,完全是因为轨迹中的每个动作都有清晰的action type、exit code和context信息。如果你的日志不是结构化的,那就赶紧改造。哪怕暂时不做论文里的过程化指标,结构化的轨迹也能让你在用户报bug时快速复盘。第二,设计“早期跑偏检测”的规则引擎。不需要一开始就上模型预测,你可以先实现一些硬规则,比如:代理的前三次文件读取如果都指向任务描述中没有提到的区域,就触发一次检查;如果五步内出现三次非零退出码,就强制代理暂停并重新概括任务。这种规则简单有效,论文里的信号统计就是它最直接的依据。第三,把验证器放在链路里,而不是放在终点。代理不应该等到最后才通过“运行所有测试”来确认结果。更好的做法是让代理在每次修改关键文件后都运行一个窄范围内的定向测试,用测试结果来指导下一步修改。论文里的验证缺失失败类型已经证明,缺少验证的轨迹风险极高。你的Agent越早拥有验证的习惯,最终的成功率就会越高。4.2 给Agent使用者的任务设计建议如果你只是CLI代理的使用者,可能觉得自己改不了内部逻辑,但任务输入和管理方式也会极大影响失败轨迹。论文里关于任务误解的发现直接指向一个结论:初始prompt是真的值得花时间写的。我的建议是,在给代理分配任务时,不仅要说“修什么”,还要说清楚“怎么算修好”。比如你让它修复一个空指针异常,可以额外提供复现命令和预期行为:“运行pytest tests/test_handler.py,当前在测试test_load时会报TypeError,修复后该测试应通过,且其他测试不受影响。”当任务描述里包含明确的验收标准时,代理早期就不会完全迷失在代码搜索中,失败的集中期能得到显著缓解。另外,限制执行范围也很重要。CLI代理如果拥有无限权限,它会热衷于做一些不相关的实验。你可以通过环境配置约束它只能访问某个目录,或者禁用危险命令。论文数据里工具使用错误如此突出,一部分责任其实在用户侧——我们给了太宽泛的自由,却没有给它足够的护栏。4.3 搭建一个简易的轨迹失败监控脚本理论知识说得再多,不如动手做一个最小例子。我已经开始在自己维护的一个CLI代理工具上加了轨迹日志,并用一个非常简单的Python脚本做了初步监控。这里分享一个最基础的版本,统计每条轨迹中的失败事件类型和分布。import json import sys from collections import Counter def load_trajectory(path): with open(path, "r", encoding="utf-8") as f: return json.load(f)["steps"] def classify_failure(step): if step.get("action") == "RunCommand" and step.get("exit_code", 0) != 0: return "tool_error" if step.get("action") == "EditFile" and step.get("validated") is False: return "missing_verification" if step.get("action") == "RunCommand" and step.get("command") in step.get("recent_commands", []): return "repeated_retry" return None def monitor(path): steps = load_trajectory(path) total = len(steps) if total == 0: print("empty trajectory") return failures = [] recent_commands = [] for i, step in enumerate(steps): if step.get("command"): recent_commands.append(step["command"]) label = classify_failure(step) if label: failures.append((i, label, step)) if len(recent_commands) > 10: recent_commands.pop(0) stats = Counter(label for _, label, _ in failures) print(f"total steps: {total}") print(f"failure events: {len(failures)}") for label, count in stats.most_common(): print(f"{label}: {count}") for i, label, step in failures: print(f" step {i}: {label} -> {step.get('command')}") if __name__ == "__main__": monitor(sys.argv[1])这个脚本的逻辑很简单:把非零退出码标成tool_error,把没有验证标记的编辑标成missing_verification,把近期重复的命令标成repeated_retry。实际使用中你会发现,哪怕不做任何复杂的数据分析,光是扫一眼这些标签的分布,就能判断代理是在中段挣扎,还是在起点就已经跑偏。针对不同标签设计不同的恢复策略,是论文方法论最直接的一种落地方式。4.4 从监控到自动化恢复监控只是第一步,再往前走一步就是自动化恢复。我最近在实践里的一个体会是,不要试图一次性解决所有失败类型,而是先从最高频、最机械的失败入手。比如工具使用错误,完全可以在监测到非零退出码后,让代理读取命令帮助文本,或者要求它重写命令并附带参数来源;比如验证缺失,可以在代理输出完成信号时强制拦截,要求它先运行一次目标测试。论文里那种把失败看成过程的态度,在自动化恢复的设计里也有很强的指导意义:失败之后的第一个动作不是继续重试,而是“定位失败类型”。很多Agent之所以卡死,就是因为把“失败”当成一个整体来应对,没有区分它是命令错误、验证缺失还是方向偏航。一旦你能在失败事件发生后快速贴上标签,你就已经在往正确的路径上逼近了。5. 批判性思考与讨论5.1 论文的局限在哪再好的论文也有边界。首先是任务集和环境的代表性。CLI编码代理的失败模式极易受任务类型影响,如果一个任务本身描述模糊、验收标准不清晰,那任何代理都会大概率失败。论文挑选的任务集虽然涵盖了多种类型,但终究是被限定的范围。在更开放的、接近真实产品迭代的开发任务中,失败轨迹的分布很可能完全不同。其次是失败标注的主观性。把“验证缺失”和“任务误解”分开看似简单,但在具体轨迹边界上经常存在灰色地带。比如代理在某个文件里改了十行代码,其中有几行确实与任务相关,剩余几行则是顺手重构。这种部分偏离算不算任务误解?不同的标注者可能给出不同答案。论文采用了人工校验来减缓这个问题,但它并没有完全消除。5.2 从失败过程研究到鲁棒性评估论文的影响力不应只停留在论文阅读层面。它给Agent评估提供了一个新方向:过程化的基准测试。传统benchmark只给一个pass@k,太粗。未来的评估完全可以记录“恢复率”、“失败类型分布”、“第一处不可逆错误出现的步数”这些指标。甚至可以做失败注入测试:故意在环境里放一个损坏的依赖、一个冲突的分支,看看代理能不能识别出异常并调整策略。这种评估更贴近真实工程中的应变能力,而不只是静态的代码修改能力。还有一个延伸方向是把失败过程用于数据构造。既然知道了失败类型和轨迹特征,就可以设计针对性的数据增强,比如在训练数据中加入更多的早期上下文丢失场景,或者加入需要恢复僵局的轨迹。这对提升CLI代理的鲁棒性,可能比单纯扩大模型参数更有效。5.3 对开发者工具生态的影响最后聊聊对工具生态的影响。过去我们调试IDE插件,靠的是断点和log。而CLI代理是一个决策系统,最简单的断点无法体现它的“思维过程”。这篇论文强调的轨迹结构化,其实正在催生一类新的开发者工具:Agent调试器、轨迹回放器、失败模式分析面板。你可以把完整任务回放成时间线,看到代理每一步在读什么文件、执行什么命令、改了哪些行,然后在失败点打上标签,反向寻找早期病因。我甚至觉得,未来的CLI代理会自带“失败复盘”功能:在一次任务失败后,它能自动输出一份失败轨迹摘要,包含失败类型分布、关键偏离点、恢复建议。这会让使用者从“代理没做好”的模糊抱怨,变成“它在第三步就误读了模块范围”的精准反馈。对于长期依赖AI编码工具的人来说,这种透明度比模型能力的微小提升更有吸引力。5.4 还有哪些值得继续追问的问题论文读完,留在我心里还有几个没有完全解决的问题。一个是失败过程的“可恢复性”到底由什么决定:是模型能力、任务难度、还是上下文管理策略?作者对轨迹做了分组分析,但这个因果链条还没有完全建立起来。另一个是多代理协作场景下的失败传播:如果是一个代理负责重构,另一个代理负责测试,失败会不会呈现完全不同的形态?现在所有分析都基于单代理,这还是被简化过的世界。最后一个问题是关于失败信号的时效性。早期预测模型确实能提前判断风险,但预警之后怎么干预,干预会不会打断正常的探索过程,这里面还有很大的工程空间。把一个高风险轨迹及时叫停,和把一个正在探索有效路径的轨迹误伤,两者可能只差一个很微弱的信号。这恐怕也是未来研究最值得投入的地方。读到这儿,你应该能理解我为什么花这么大力气来推荐这篇论文了。它没有给出一个“让代理永不失败”的万能药,而是提供了一种看待失败的方式,一种把混乱轨迹转译成可读结构的方法。我自己开始实践之后,最大的感受是:代理的失败不可怕,可怕的是失败没有留下任何可回放的痕迹。最后再分享一个小技巧。我在自己的Agent配置里加了一条干预规则:如果连续三分钟没有产生任何有效文件变化,同时执行了三条以上相同的命令,就自动暂停并向用户发送一段当前状态摘要。这个规则完全来自论文里的“恢复僵局”失败模式。实测下来,它显著减少了我对着终端干瞪眼的时间。希望你也能把这篇论文里的解剖方法用起来,在自己和Agent的协作过程中,找到那些早期跑偏的种子。