Skip to content
罗盘与心

Agent 空转的统计指纹:79,099 条消息中的四类检测规则

从 79,099 条运行消息归纳 Agent 空转的 4 种统计指纹——死循环、无效重试、振荡循环、无进展循环——以及运行中即可判定的干预信号。

连续 83 次调用同一个工具轮询后台任务结果,同一个错误原样重试 7 次,单会话 Grep 搜索 73 次。这是 79,099 条真实运行消息中记录到的行为模式。

重度使用编码 Agent 的人大多被同一个问题困扰,它到底在干嘛,该等还是该打断。判断依据通常是感觉。Peri 对自己 18 天的运行记录做了一次只读分析(528 个会话、79,099 条消息),结论是空转并非随机,而是系统性的缺陷行为,每种模式都会留下可枚举的统计指纹。这篇文章给出 4 种指纹的检测规则和对应案例,解释为什么重试停不下来,最后给出运行中即可判定的干预信号。

79K 条消息的数据底稿

数据来自 2026-05-15 到 2026-06-01 共 18 天的运行记录,528 个可见会话、555 个 SubAgent 会话、79,099 条消息,全部来自本地 threads.db 的只读分析,零侵入。分析用纯统计启发式(正则匹配 + 计数阈值 + 序列模式),每条发现带置信度分级,规则匹配的置信度高,相关性推断的置信度低。

最值得注意的基础事实是消息构成——工具消息占 51.8%,用户消息只占 4.1%(3,255 条),每条用户指令平均触发 21.7 条内部消息。Agent 绝大部分时间在自我运行,空转的空间天然存在。

一个必须交代的偏差,85% 的会话(449/528)来自 perihelion 项目自身,是拿 Agent 开发 Agent 的 dogfood 场景。结论对同类编码任务有参考价值,但样本不覆盖所有项目类型。

四种卡住指纹

检测规则本身可枚举,这是整个分析的核心主张。四类空转的检测规则和对应案例如下。

指纹检测规则案例置信度
死循环同工具 + 同参数 ≥3 次ExecuteExtraTool 连续 83 次轮询 AgentResult92%,CRITICAL
无效重试同一错误连续 ≥2 次且策略不变Read 因 Missing file_path 重试 7 次80%
无进展循环同工具 + 结果等长 ≥3 次单会话 Grep 73 次,占 76% 消息65%
振荡循环A→B→A→B 交替 ≥6 次严重振荡约 15 个会话75%

指纹一:死循环

同工具 + 同参数 ≥3 次。第一期报告录到 9 个会话、167 次完全重复的无效调用,最严重的是 ExecuteExtraTool 连续 83 次轮询 AgentResult,置信度 92%,定为 CRITICAL。第二期报告(06-17,150 会话)又录到 8 个死循环,AgentResult 轮询 17 次和 11 次、/bg 后台状态检查 9/7/6/5 次、git add -A && git status 循环 6 次。83 次连续调用意味着 ReAct 循环里没有任何去重保护,模型在同一个问题上反复空转。

指纹二:无效重试

同一错误连续重试 ≥2 次且不改变策略。17 个会话检出重试循环,最极端的是 Read 因 Missing file_path 连续重试 7 次,每一轮的 tool_use 都犯同样的错,分析原文是 LLM 没有从错误消息中学到任何东西。全窗口 129 次工具错误里,Missing file_path 占 42 次(Read 28 / Write 14),32.6%。这类循环的代价,是每一轮重试的模型调用成本。

指纹三:无进展循环

同工具 + 结果等长 ≥3 次。179 个会话存在重复调用但结果长度不变的情况,影响 97 个会话,Edit 最极端 17 次、Write 11 次、Read 8 次。06-17 报告里有一个极端个案,单会话 Grep 73 次,占该会话 115 条消息的 76%。全窗口 Grep 重复搜索率 16.9%(116/687),45 个重复 pattern,被搜最多次的是 ^##,搜了 6 次。搜索本身不贵,但每次结果都塞回上下文,重复搜索是双重浪费。

指纹四:振荡循环

A→B→A→B 交替。245 个会话存在两工具交替模式,影响 193 个会话。要说明白的是,大部分振荡是正常行为——Grep↔Read 是最强的共现对(301 次),搜索后读、读后搜索就是代码理解的标准流程,Read↔Edit 同理。真正的振荡定义为 ≥6 次交替且无实质进展,按这个标准严重振荡约 15 个会话。这是四种指纹里最需要人工判断的一种,规则可枚举,但振荡本身不能一刀切。

为什么重试停不下来

四个根因,都是从数据里反推出来的,并且都有对应的修复。

错误消息太短,LLM 学不到东西。 Missing file_path parameter 只有三个词,模型不知道该怎么修,只能原样重试。改写后是这样。

The 'file_path' parameter is required for the Read tool. Provide the absolute path to the file.

错误消息是给 LLM 看的,不是给人看的。信息量不足的错误消息会直接催生重试循环,这是连续重试 7 次的直接诱因。

轮询没有退出机制。 AgentResult 每次返回相同文本(No completed background agent results available),模型没有任何理由停止,于是有了 83 次轮询。后台任务(background task)的结果查询缺一个明确的停止查询信号。

上下文丢失导致重复搜索的恶性循环。 报告原文的因果链是,上下文丢失 → 忘记搜索结果 → 重复搜索 → 上下文进一步膨胀。压缩(compact)之后 Agent 忘了之前搜过什么,从头再搜一遍,单会话 73 次 Grep 就是这个循环的极端产物。

后台任务语义缺陷是轮询依赖的同源问题。 默认 15 秒超时会静默杀掉后台长任务,Agent 只能反复查询状态确认。8 月的审计里,一个 3 分钟的后台监控实验被 15 秒默认超时静默杀掉;已知 15 秒限制后仍未传 timeout,后台 npm test 被杀,但子进程孤儿存活跑完了测试,通知误报失败,Agent 读日志才发现实际通过;另有一次前台 600 秒超时,进程树被杀、输出全丢,测试结果丢失。

修复闭环:从报告到代码到验证

时间线是完整的,06-01 第一期报告(现象)→ 同日修复设计(5 项代码改动 + 1 个 bug issue)→ 06-17 第二期报告(初验)→ 08-02 审计(现状)。

四项主要修复。

  1. 错误消息改写。Read/Write/Edit/Glob 的缺参错误全部改成带修复指引的完整句子。
  2. AgentResult 引导文本。改为 Do not retry this query — continue with other work instead,明确告诉模型别查了、去干别的,06-17 报告确认已落地。
  3. 连续失败检测。框架层追踪同工具同错误,连续 5 次后向模型注入纠正消息,内容为 Stop retrying and analyze the root cause. Consider using a different approach or asking the user for guidance.
  4. 工具名归一化。工具名带大小写差异或常见别名(task→Agent、shell→Bash、reading→Read)时自动匹配到真实工具,对应第一期录到的 51 次幻觉工具调用、23 个会话。

验证结果,06-17 窗口(150 会话、6,870 次工具调用)整体工具失败率 1.8%,Write 失败率 0.0%,最长连续失败 4 次(第一期是 7 次),92.2% 的失败段长度为 1——失败从雪崩变成了偶发。

运行中的三个实时信号

不用等报告,运行中就能判断。三个信号,任何一个出现就该警惕。

  1. 同一工具/同一模式反复出现。3 次以上就该注意,尤其是带相同参数的时候。
  2. 长时间无输出。Agent 每个动作都有产物,连续沉默说明在无效循环。
  3. 后台任务反复查询。同一个结果查了一遍又一遍,就是轮询成瘾的现场。

干预动作很简单,直接打断或提问,别继续等。给 Agent 补运行时信息(错误场景、环境状态)比让它继续猜的成本更低——审计里 Agent 猜了 20 条消息没结果,一条 AskUserQuestion 就拿到了关键症状(Enter 卸载后卡死)。用户是唯一掌握运行时信息的人。

两个反面提醒,防止过度反应。交互比 196:1 不一定是缺陷,那条会话是用户发了一条长指令后 Agent 自主跑了 196 条消息、期间零干预的复杂任务。超长会话(>200 条,占 19.9%)的工具错误率平均只有 0.4 次,长期运行是稳定的。

判断框架就一句话,看有没有进展,而不是有没有说话。工具调用不断换新参数、每次都有实质结果,是长跑,继续等;同一个工具同一个参数反复出现,是空转,打断它。

下一步

  • 快速上手 — 第一次用 Peri 跑真实任务
  • 长任务 — 1 小时+ 的任务如何避免空耗,后台任务与超时语义
  • 上下文工程 — 上下文压缩与重复搜索的恶性循环怎么避免
  • Agent Loop — 空转发生在 ReAct 循环的哪个环节