千次Agent执行揭示静默失败漏报
作者分析了单机定时Agent的1000次执行,并通过故障注入检查任务是否会误报成功。在8次静默失败测试中,Agent全部报告成功;文章提供脱敏运行数据和实验代码。
AI 深度解读
Agent 夜里跑完任务,早上发来一排「已完成」,输出文件却是空的。作者用 1,000 次定时执行记录和一组故障注入实验,追问一个很实际的问题:任务失败时,Agent 能不能正确报告失败?
结论有明确边界:工具直接报错时,实验中的 Agent 都承认了失败;上游悄悄返回空数据时,8 次实验全部报告成功。对准备让 AI 无人值守执行任务的程序员,这篇最有价值的提醒是:完成通知之外,还得有一套能检查实际结果的验收规则。
Agent 说成功,究竟证明了什么
作者讨论的现象叫 false success,也就是 Agent 宣称完成任务,但环境里的实际状态并不支持这个结论。例如,它说数据已经写入,存储里却没有对应记录。
原文引用了 2026 年 6 月的论文 arXiv:2606.09863,涉及 9,876 条 tau2-bench 轨迹和 1,879 条 AppWorld 轨迹。按作者转述,单一控制设置下,全部失败中有 45–48% 被报告为成功;加入独立验证流程后,比例降至 3%;自行评估结果的 coding agents,相关比例达到 75.8%。
这些数字来自原文引用的研究,不能当成作者这套定时任务系统的实测结果,更不能直接套到所有 AI 编程工具上。
监控方法的对比也很有意思。按原文转述,让另一个 LLM 阅读执行轨迹、判断是否成功,各种配置的 AUROC 都没有超过 0.65。AUROC 可以粗略理解为区分两类结果的能力指标,越接近 1 越好。轻量的 TF-IDF 文本检测器则达到 0.83–0.95,在相同人工复核预算下找回了 4–8 倍的虚假成功,运行速度快 3,300 倍。
作者给出的解释是,LLM 裁判也会被自信的收尾措辞影响。不过,TF-IDF 仍是在识别文本特征,并没有直接检查产物。它适合帮助筛选可疑记录,不能替代文件、数据库或测试结果的验收。
「任务已完成」只是 Agent 的报告,实际状态才是验收依据。
1,000 次执行暴露了两种相反的监控问题
作者的系统运行在一台 WSL2 笔记本上,由一个调度器驱动 cron 任务。2026-10-24 导出的 1,000 次执行中,914 次标为 ok,75 次标为 fail,另有 11 次处于 wedged,即进程还活着,但执行没有正常结束。
这里先要给原文的数据打一个问号。作者列出的失败分类分别是:script-exit-1 62 次、worker SIGTERM 11 次、timeout 5 次、interrupted 3 次、rate-limit/quota 2 次、repetition loop 1 次。这些数字相加是 84,超过前面给出的 75 次失败。前两项还分别标注了 72% 和 13%,但没有说明分母和分类是否重叠。
因此,这份分类可以帮助理解故障类型,却不能直接拿来计算各类故障的可靠占比。
更具体的发现来自一个社交发布任务。62 次非零退出里,约 50 次与这个任务有关:调度服务免费额度最多允许排队 10 条内容,队列满后,剩余候选内容就会返回发布失败。与此同时,流水线实际上仍在正常地每天发布 5 次。
告警只写了「7 条发布失败,下次重试」,没有带上队列已满的原因。真实错误文本一直保存在任务输出中,作者却花了 5 天才定位问题。
这不是完全没有失败,而是告警把容量限制和服务故障混在了一起。持续的噪声会消耗注意力,让真正的问题更难被发现。
调度本身也不稳定:11.7% 的执行晚于计划时间超过 60 秒,最严重的一次迟到了 4.5 小时。再加上那 11 次卡住的执行,说明「有没有启动」「有没有推进」「有没有完成」需要分别观察,不能用一个运行状态包办。
为什么空结果能骗过全部 8 次自检
为了区分故障与报告之间的关系,作者设计了一个小型提取任务:筛出目录中价格大于等于 100 的条目,写入输出存储。每次执行都必须返回包含 status / items_written / notes 的结构化 JSON。
实验共有 5 种条件,每种运行 8 次,包括正常对照和 4 类故障注入。下面这张结果表展示了关键差异:
condition self=success false success
control (4 matches) 8/8 0/8
upstream returned [] 8/8 8/8 <- all "success"
half the writes failed 0/8 0/8
upstream 429 0/8 0/8
cut off mid-run 0/8 0/8
要看的是 upstream returned [] 这一行:上游返回空数组时,Agent 自报成功和虚假成功都是 8/8;写入失败、上游 429、执行中断则全部正确报告失败。
其中一次真实回复如下,它把空目录解释成了正常的零结果:
{"status": "success", "items_written": 0,
"notes": "Catalog was empty; no matching items, wrote empty result."}
要注意第一行的 "status": "success"。Agent 并没有隐藏写入数量,items_written 明确是 0;问题在于,它不知道这个 0 违反了任务的预期。
空结果有两种含义:确实没有符合条件的数据,或者上游出了问题。只看本次返回值,通常无法区分。实验中的独立验证器掌握预期数量,再与实际数量比较,因此抓到了全部 8 次异常。
作者由此提出四个操作原则:成功必须同时满足 Agent 报告和独立验证;进度记录需要独立机制;告警必须携带失败原因;重任务启动前检查共享配额。他们的系统还出现过一个任务耗尽免费额度、连带影响多个任务的情况。
数据导出位于 data/fleet_runs.jsonl,汇总命令是 python3 scripts/analyze_fleet.py;故障实验位于 experiments/inject/,运行命令是 python3 experiments/inject/run_grid.py。作者没有公开受测模型名称,但表示可以用任意 OpenAI-compatible chat model 重跑实验。
我的判断
我认同把验收条件放到 Agent 之外。对于文件生成、数据写入这类有明确产物的任务,检查文件内容、记录数量或执行测试,通常比再问一次模型「你确认完成了吗」更容易审计。结构化 JSON 能方便程序读取,却不会自动保证里面的判断正确。
但「8 次全部抓到」有很强的实验前提:验证器事先知道正确数量。真实任务未必有这个答案。今天确实可能没有新数据,目录也可能合法变空。若机械规定「输出必须大于 0」,就会制造另一批误报。团队需要先定义零结果何时有效,以及缺少哪些证据时只能标为待检查。
另外,一台机器、一个调度器的 1,000 次记录,是案例材料,不是整个 Agent 行业的故障率。8 次重复实验也不足以证明所有模型遇到空数组都会如此。作者自己明确承认,验证器在设计上就能捕获这些注入故障,这是演示,不是通用能力榜单。
原文也没有提供完整的成本对照,证明规则验证在所有任务上都胜过 LLM 监控。更合理的取舍是:明确条件交给程序验证,语义复杂的结果再考虑模型辅助判断。前者负责可确定的事实,后者仍需保留不确定性。
能拿来做什么
- 给 Node 定时提取任务写清验收条件,把上游请求状态、合法空结果、实际写入数量分别记录下来。业务允许零条时,也要明确什么证据能证明这个零是正常结果。
- 让日常 AI 编程任务绑定可检查的产物。Agent 声称修改完成后,由独立步骤检查目标文件、实际 diff 和相关测试,再记录最终成功状态。
- 改造无人值守任务的告警内容,让通知同时包含任务名、失败原因和实际输出摘要。队列满、429、写入失败应当能直接区分,避免只报一个失败数量。
- 为长任务增加独立的进度检查,并在消耗共享额度前检查余额。进程存活只能证明它还没退出,不能证明它正在接近完成。