Skip to content
云梯

子 agent 全部成功而工作流失败:51 次运行记录中的失败分层

51 次真实运行数据拆解多 agent 工作流的失败分层,以及 1 小时+ 长任务如何设计才能避免空耗。

你让 Peri 跑一个多 agent 重构,等了一个多小时回来,结果面板显示 14 个子 agent 全部成功,工作流状态却是 failed,错误信息是 None。没有失败的 agent,没有报错,任务就是没成功。这个案例真实存在,名字叫 prompt-security-contracts-implementation,跑了 1 小时 06 分。

这不是个例。翻 .claude/workflow-runs/ 下 2026-08-01 至 2026-08-06 的 51 次运行记录(含重跑,按脚本运行次数计),有 12 次失败或中断,约占 23%。没有一次是因为任务本身太难。失败全部发生在任务外围,脚本生成、传输层、收尾阶段。

这篇用真实运行数据拆开每一层失败长什么样、影响多大,以及 1 小时+ 的长任务如何设计才能避免空耗。

失败分层,12 次失败没有一次死在任务上

失败类型次数发生时机典型耗时
脚本语法错误5脚本生成后,秒级秒级
LLM 传输层故障3运行中段/后段23 分钟 ~ 8 小时 20 分
用户手动 kill1运行后段3 小时 43 分后
收尾状态不一致3收尾阶段9 分钟 ~ 1 小时 06 分

另外还有 1 次 成功但产出无效,模板占位符未替换,状态是 completed 但返回的是字面量。这条记录本身存疑,后面单独说。

5 次秒级失败约占四成,重跑即可恢复。消耗最大的是小时级的 4 次,最长的一次 8 小时 20 分。

一个前提需要说清,51 是目录数,不是唯一任务数。019fc025 这类 run_id 会因为语法错误重跑出现两次,所以下面的比例按 脚本运行次数 理解,不应理解为 51 个任务

秒级失败,生成脚本有 bug 是常态,重跑成本最低

5 次失败都发生在脚本生成阶段,错误都是 JS 语法错误

Unexpected token 'if'
Unexpected identifier '边界
missing ) after argument l
await is only valid in async
Unexpected token ')'

第二个错误和中文内容相关。工作流脚本由 Peri 生成后直接执行,生成时的转义和引号处理偶尔出错。这是已知噪音,不是新问题。compact-slices-4-7.mjs 的注释里明确写过反引号已用 \x60 转义,脚本作者早就在防这一层。

证据形态很干净,019fc025 那个 failed 运行目录里,state.json 存有完整脚本,但没有 journal.jsonl,说明脚本在解析阶段就挂了,一个 agent 都没跑起来。修正后立即重跑成功,这个 run_id 出现两次就是那次重跑留下的痕迹。

结论很简单,看到秒级失败别慌,让 Peri 修了重跑,成本几乎为零。别为它设计重试、降级之类的复杂机制。

小时级失败,跑得越久,越可能死于环境而非任务

peri-3.0-refactor 第一次运行,1 小时 40 分后死在模型侧。journal 共 16 条,最后一条(seq 15)是下面这条

{"kind":"dead","reason":"runagent-threw","detail":"LLM error: model retry exhausted after 6 attempts; last failure: transport"}

模型侧传输层重试 6 次耗尽,整个工作流崩溃。更麻烦的是连锁反应,null 结果进入 parseVerdict 直接抛 Cannot read properties of null (reading 'match'),state.json 里记录的 error 其实是这个下游异常,不是根因。排查时先看到的是误导信息。

第二次运行,3 小时 43 分后用户手动 kill。这次 journal 22 条全部 ok,已经走到第 21 步(L4 review)。任务本身没死,但用户判断不值得继续等。长任务的收敛性风险在这里,每一步都成功,不等于总时间可控。

没有 checkpoint 时,1 小时+ 的投入在传输层故障面前全部作废——本文数据里最长的一次跑了 8 小时 20 分,失败后一样清零。超长跑不是理论问题,自主运行记录里也有接近 5 小时(284.7 分钟)的长跑。

最隐蔽的失败,子 agent 全成功,工作流却 failed

三次 全 ok 但 failed 的情况

  • prompt-security-contracts-implementation,14 个 agent 全部 ok,跑了 1 小时 06 分,status=failed 且 error=None
  • code-review-panel,5 个 agent 全部 ok,status=failed,state.json 里根本没有 error 字段
  • cancel-chain-fix-v2,9 个 agent 全部 ok,跑了 25 分钟,status=failed,同样没有 error 字段

为什么查不到原因?journal 只记录子 agent 结果(ok/dead),不记录收尾阶段事件。error=None 意味着失败发生在脚本自身的收尾逻辑里。但这是推断——journal 全 ok 而 failed 时,失败原因只能猜,别把猜测当成事实。

方向性佐证来自脚本的收尾契约,plan 输出必须带 SUMMARY/DEPENDS/FILES 三行,review 输出必须以 VERDICT: APPROVE/REJECT 结尾,全部靠正则解析。解析失败有层层兜底,找不到 VERDICT 按 REJECT 处理、plan 解析失败用 FALLBACK_DEPS 保守依赖。但 parseVerdict(null) 会直接崩,防线本身也有 bug。

另一种 成功但无效 的情况,langfuse-subagent-refactor 状态 completed、跑了 1 小时 32 分,return_value 四个字段却全是 ${implSpec}${phase1} 这类占位符字面量,模板填充环节没生效。这条记录本身存疑(可能不是最终形态),引用需谨慎。

结论,看到 全 ok 但 failed,先怀疑收尾和契约解析,别立刻怀疑任务无效。子 agent 的产出都在,工作流状态只是收尾阶段的问题。

解法,分阶段跑 + 可续跑

同一任务三次运行,是最直接的对比组

运行状态耗时结局
peri-3.0-refactor 第 1 次failed1 小时 40 分transport 重试耗尽
第 2 次killed3 小时 43 分用户放弃
resume2 续跑completed10 分钟全部 8 个迁移点收尾

resume 续跑把之前所有子 agent 结果作为已完成状态,只补没跑完的部分。前两次并非空耗,约 5 小时的产出被续跑完整消费,最终拿到全部 8 个迁移点的 code + review 记录。

给 Peri 的指令写法是,把大重构切成阶段,每阶段跑完就是 checkpoint。现成范例是 peri-3.0-refactor.mjs 的编排:8 个 plan 并行规划,按依赖关系分波、波内按文件冲突分组执行,逐 issue code,review 审批环。基本原语只有 4 个,phase()parallel()agent()log()

对 Peri 说 把任务切成几个阶段,每个阶段跑完就是 checkpoint,我可以随时续跑,比 一口气做完 稳得多。

行动清单

四句话

  1. 秒级失败直接重跑,别慌
  2. 1 小时+ 长跑,先让 Peri 分阶段并确认可续跑
  3. 看到 全 ok 但 failed,先查收尾契约,别怀疑任务
  4. 跑前问一句 最坏情况能接受丢多少进度,接受不了就分阶段

旁证,132 条自主运行记录里 131 条 completed、1 条 running,median 14.1 分钟、mean 29.5 分钟。定时自主跑大多不长,但超过 1 小时的有 18 条,超过 2 小时 7 条。长任务超时是实际存在的场景,不是理论问题。

长任务要设计成可重启的,而不是指望一次成功。

下一步