返回博客

Agent 跑了两小时,结果是零——它是怎么卡进 Doom Loop 的

作者 约 5 分钟读完

Agent 跑了几小时但任务进度是 0,这就是 Doom Loop。讲清四种成因、工业级断路器和检测器怎么破,以及为什么模型外面必须补五层控制——架构问题,不是模型问题。


Agent 跑了两小时,结果是零——它是怎么卡进 Doom Loop 的

你让 Agent 帮你做一件事。

它开始工作,终端一直在滚:

Thinking...
Acting...
Thinking...
Acting...

两小时后,你去看结果。

任务进度:0。

它没有卡死,没有报错,甚至还在"努力"。但它什么都没做完。

这就是 Doom Loop——Agent 持续执行,却始终没有有效进展的状态。

跟普通的死循环不一样。死循环 CPU 跑满、进程僵住,监视器一眼就能看出来。Doom Loop 不会:它每一步都按 ReAct 的逻辑跑,工具在调用,模型在思考,token 在烧,日志一直在滚——指标都正常,只是这些动作合起来没在推进任务。

Agent 自己也感觉不到。每一轮"思考 → 行动 → 观察"对它来说都符合训练时见过的模式。要等到你回来看进度、或者月底拿到 LLM 账单,才会发现它陷在原地。

Doom Loop 是怎么来的

Agent 的底层是 ReAct 循环:思考 → 行动 → 观察 → 再思考。

循环本身没问题,问题在于有四类场景会让它原地打转。

四种典型的 Doom Loop 模式

1. 相同调用,相同结果

Agent 用完全相同的参数反复调用同一个工具,每次得到一样的返回,却始终提取不到新信息。模型下一轮还是做出相同决策,严格意义上的确定性死循环。

2. 调用根本不存在的工具

模型幻觉出一个工具名,每次调用都返回"工具不存在",但它把这个当成"可能暂时不可用"继续重试。这类错误永远无法通过重试解决。

3. 轮询工具卡死

Agent 反复调用状态查询工具,每次得到 { status: "running" },因为它在等一个永远不会发生的事件——被轮询的进程已经僵死了。Agent 不知道该何时放弃。

4. 两个操作交替振荡(Ping-Pong)

读文件 → 文件不存在 → 创建文件 → 权限不足 → 读文件…… A→B→A→B 无限交替,每一侧都在"工作",整体任务毫无推进。

四种场景的共同点:工具一直在调用,但每一轮都没有产出新信息。

工业级 Agent 怎么破

以 OpenClaw(Claude Code 同类架构)为例,它构建了两道防线。

两道防线:重试断路器 + 滑动窗口循环检测

第一道:重试端断路器

容错重试本身必须有上限,否则它会变成另一种 Doom Loop。

OpenClaw 的做法:

  • 上下文压缩最多触发 3 次,防止无限压缩导致信息损失
  • 总重试次数硬限 32–160 次(根据候选模型数量动态计算),所有错误路径共用同一个计数器,超出即终止
const MAX_OVERFLOW_COMPACTION_ATTEMPTS = 3;

if (runLoopIterations >= MAX_RUN_LOOP_ITERATIONS) {
  // 超出重试限制,终止整个运行
}

第二道:工具循环检测

基础原语:哈希指纹 + 滑动窗口。

每次工具调用都生成一个指纹——工具名 + 参数哈希,保留最近 30 条历史,在这个窗口里检测异常模式。

两级告警:

  • warning(≥10 次):向 Agent 返回提示,给它一次自主纠正的机会
  • critical(≥20 次):忽略 Agent 的判断,直接终止会话

这比硬断更符合 Agent 的自主决策模式。

四种检测器:

  • 未知工具重复——同一个不存在的工具被调 ≥10 次,直接 critical,不经过 warning
  • 轮询无进展——相同结果出现 ≥10 次给 warning 提示操作方向,≥20 次终止
  • 通用重复调用——同参数 + 同结果 ≥20 次,critical 终止
  • Ping-Pong 振荡——A-B-A-B ≥20 次且两侧结果都无变化,critical 终止

还有一个容易被忽视的场景:上下文压缩完成后,Agent 会继续执行同样的工具调用,再次卡死,再次压缩,周而复始。压缩并没有打破循环,只是把崩溃推后。

OpenClaw 的解法是,压缩成功后立刻开启一个 3 次机会的观测窗口——如果窗口内某个工具以完全相同的参数产出完全相同的结果,直接终止会话。

if (matches.length >= state.windowSize) {
  // 压缩没能打破循环,直接终止
  return {
    shouldAbort: true,
    detector: "compaction_loop_persisted",
    message: "CRITICAL: compaction did not break the loop. Aborting...",
  };
}

Doom Loop 只是 Agent 失控里最容易被看见的那一种

一张 Claude 工程师分享的图,把长任务 Agent 最常见的三件事说得很准:

带不住状态,估不准工作量,评不好自己的输出。

这三件事一起出现,就是长任务最常见的失控链路——状态、计划、验证一起垮。

状态。随着对话变长、工具调用变多、错误信息堆进来,context 里的噪声开始变多。到了后半程,Agent 还在继续工作,但已经不像在沿着原来的任务前进,更像是在当前上下文里找一个看起来合理的出口。

规划。你让它"改一下文档生成流程",它可能把整个内容系统都扫一遍。你让它"做一个可用 demo",它可能只搭一个 UI 空壳。前几步看起来很顺,等 context 用掉一大半,真正难的部分还没碰,后半程开始收缩范围,最后用"核心功能已完成"收尾。

验证。接口能返回 200,Agent 就说功能可用。它没有点过页面,没有走过真实用户路径,没有检查数据是否真的写入。Agent 很擅长给自己的工作做一个体面收尾,但这种收尾经常只是在替一次很浅的验证包装结论。

模型外面那五层

要解决上面这三件事,模型外面得有五层东西。

模型外面五层控制:状态 / 规划 / 执行 trace / 验证 / 硬停止

1. 状态。 progress.md 或者数据库,任务目标、当前进度、决策记录、验证结果都落这里。可读回、可续跑、可审计——对话断了,现场还在。

2. 规划。 不是 checklist,是调度单。当前阶段做什么、输入输出是什么、用什么验证、最多跑多久、失败几次后停止、哪些操作需要人工确认。好的规划单是给 Agent 限自由度,不是给它列选项。

3. 执行 trace。 每次工具调用的输出必须落盘。只在上下文里闪一下的调用结果,会被后续噪声淹没。

4. 验证。 三档堆在一起:机器验证(测试 / lint / build / schema validation)、环境验证(Web 应用要用浏览器点,不只 curl)、独立评价(生成者和评价者分开,Evaluator 必须有权判 FAIL)。

5. 硬停止。 任务范围扩大、预算逼近上限、验证连续失败、操作涉及删除 / 发布 / 生产数据——这几条要写死,不能靠模型临场判断。

怎么判断一个 Agent 工作流靠不靠谱

不看模型名,我会先扫这几样。

状态有没有外部存储——任务进度、决策记录是不是只活在 context 里。

规划有没有阶段边界和验收标准,还是给个目标让它一路跑。

工具调用结果进没进可审计的 trace。

谁来判完成——独立评价机制比 Agent 自己说"可以了"靠谱多了。

什么时候它必须停——预算上限、连续失败、动到生产数据,这些要写死。

任何一项答不清,模型越强,跑偏时造成的动静越大。


Doom Loop 是架构问题,不是模型问题。断路器和循环检测能挡掉最坏的情况,但让长任务真正可靠,还得靠上面这五层一起在线。

如果你正在做长任务的 Agent,回头看上面那几条,哪一项答不上来就先补哪一层。换更强的模型解决不了它们。


最后留个钩子。开头那四种 Doom Loop,你的 Agent 最近一次跑飞,是栽在哪一种上的?

评论区扣个数字就行:

1 相同调用相同结果 / 2 调用幻觉出来的工具 / 3 轮询一个死掉的进程 / 4 两个操作 Ping-Pong 来回振荡

我赌 3 最多——等一个永远不会来的状态,是我自己被坑得最惨的一次。也欢迎补个 5:你踩过、但上面四种都没覆盖的卡法。