Agent 该什么时候停——终止策略的三种流派

2026-10-15·终止与自愈·终止与自愈

行业已从 max_turns=25 分化成三种终止流派------硬上限、预算计量、行为分析。大多数框架仍在第一种,少数产品走向第二种和第三种。真正的前沿不是 N 设多大,而是能不能在第 10 步就区分'正常工作'和'死循环'。

每个 Agent 开发者都遇过的场景

你搭了一个 Agent,设了 max_iterations=25。大多数时候 5-10 步完成。然后有一天它接到一个大型代码重构任务------读了 15 个文件、改了 8 个、跑了 3 轮测试------在第 25 步被框架砍掉了。改了一半的代码留在那里,测试没跑完。

你提高到 50。然后某天 Agent 陷入死循环------反复读同一个文件,读了 50 次才被砍掉,烧了 $4。

N 应该设多少? 这个问题本身就说明 max_turns 不是正确的抽象。

Agent 行业迭代极快。2024 年"几乎所有框架 max_turns=25"是事实。到了 2026 年 Q4,行业已经分化------不是线性演进,是三种流派并存。搞清楚每种流派在解决什么问题、在什么维度上失效,比讨论 N 应该是 25 还是 1000 有意义得多。


流派一:硬上限------数到 N 就停

原理:数步数、数消息、数 token、数时间。到了就停。

这是截至本文写作时最普遍的做法------不是因为它好,是因为它简单。一行计数器解决"防止无限循环"的最基本需求。

LangGraph:从 25 调到 1000,问题没变

LangGraph 的 recursion_limit 在 2024 年默认 25------大量用户抱怨"Agent 没完成就被砍了"。v1.0.6(2026 年)把默认值调到了 1000(LangGraph 官方文档 graph-api.md)。

1000 解决了"正常任务被截断"的问题。但如果 Agent 在第 5 步就进入死循环,它得空转 995 步才停------比 25 还糟。调高 N 减少假阳性(误杀正常任务),同时增加假阴性(放过死循环更久)。两者不可兼得。

LangGraph 的真正进步是新增了 RemainingSteps 和 IsLastStep------Node 可以在运行时检查"还剩多少步"并优雅降级(LangGraph graph-api.md, "Proactive recursion handling")。但这仍然是计数器语义:正在做 200 步重构的 Agent 和第 5 步就死循环的 Agent,RemainingSteps 看到的数字一样。

CrewAI:max_iter=20,且有 Bug 停不住

CrewAI 把 max_iter 从 25 调到了 20(CrewAI docs, agents page)。但更有意思的是 GitHub issue #3847(2026 年仍 open):handle_max_iterations_exceeded 被调用后 break 语句在某次提交中被意外删掉了------Agent 到达上限后继续跑 。一位用户在 issue 中写道:"任务描述稍有歧义时,LLM 会忽略框架的硬停信号,因为它觉得自己'就差一步了'"。

这揭示了硬上限的一个隐蔽风险:框架实现本身也可能出 Bug ------在一个复杂的异步执行流程中,一个计数器和一个 break 之间可能隔着几十行代码,而这几十行代码的任何一次重构都可能让 break 丢失。

OpenAI Agents SDK:max_turns + error_handlers

OpenAI 的 Agents SDK 提供了 max_turns------到达后抛 MaxTurnsExceeded,可通过 error_handlers={"max_turns": handler} 优雅降级(OpenAI Agents SDK docs, running_agents)。还提供了 reset_tool_choice 自动重置为 auto 以防工具调用死循环。

但没有内置行为检测。社区 issue #3353(BudgetGuard 提案)的开题者写得很直接:"#2848 有用户报告 Agent 循环触发了数千次 API 调用。官方建议'run 结束后看 result.usage'或'设平台级月度预算'------前者太晚(runaway 已经发生),后者太粗(一个坏 run 能烧完整月预算)。"

AutoGen:10 种终止条件,全是阈值

AutoGen 在可组合性上做了行业最丰富的设计------约 10 种 TerminationCondition,可以用 &(AND)和 |(OR)自由组合(AutoGen 官方文档, termination tutorial):MaxMessageTermination / TokenUsageTermination / TimeoutTermination / TextMentionTermination / FunctionCallTermination / ExternalTermination / FunctionalTermination 等。

可组合性本身是明显进步 ------max_msg | text_termination 比单一 max_turns=25 好得多。但每一个组件仍是阈值:数消息、数 token、数时间、看文本。没有一个条件能回答"Agent 是不是在兜圈子"。

流派一的局限性

一个跑了 30 步的 Agent 可能是:

情况 该停吗
读了 15 个文件 + 改了 8 个 + 跑了 3 轮测试 ❌ 正常工作
读了同一个文件 30 次,每次参数一样结果一样 ✅ 死循环
试了 30 种修复方案,每次测试都失败 ⚠️ 伪进展

计数器对这三种情况一视同仁。 不管 N 是 25 还是 1000------它区分的只有"N 步"vs"N+1 步"。

真实案例:一个 LangChain 四 Agent 市场调研管线进入反馈循环------分析器生成报告,验证器要求"进一步分析"------循环了 11 天 ,烧了 ** 47,000∗∗,产出为零(vectara/awesome−agent−failures)。另一个案例:847步推理,每分钟47,000**,产出为零(vectara/awesome-agent-failures)。另一个案例:847 步推理,每分钟 47,000∗∗,产出为零(vectara/awesome−agent−failures)。另一个案例:847步推理,每分钟47,无最终答案(Particula, 2025)。即使设了 recursion_limit=1000,这些案例只会在 1000 步之后才停。


流派二:预算计量------数钱不数步

原理:用户不在乎 Agent 跑了几步,在乎花了多少钱。从"数步数"转向"数花费"。

这是 2026 年出现的新方向,代表产品是 Claude Code 和 Codex CLI。

Claude Code:--max-budget-usd

Claude Code 的交互模式依然没有 max_turns ------模型自己决定什么时候停(返回 end_turn)。但在 SDK/headless 场景,v2.1.217 新增了 --max-budget-usd------按美元设预算(Claude Code 官方文档, cli-reference)。

bash 复制代码
claude -p --max-budget-usd 5.00 "重构这个模块"

子 Agent 的花费计入总预算。到达上限后,返回 error_max_budget_usd 结果,停止一切后台子 Agent。

这是一个范式转变 ------200 步高效重构花 3不停,20步死循环花3 不停,20 步死循环花 3不停,20步死循环花10 就停。因为死循环的 token 消耗率通常高于正常工作(反复重读长上下文),预算限制在统计意义上比步数限制对死循环更敏感。

Claude Code 同时保留 --max-turns 作为可选兜底,以及 Stop Hook 机制(Agent 完成后 hook 可以 block 停止让它继续,最多 8 次连续 block)。

Codex CLI:max_budget_tokens + Goal 状态机

Codex CLI(OpenAI 的 Rust SDK)支持 max_turns + max_budget_tokens 双限制(docs.rs/codex-cli-sdk)。但更有意思的是它的 Goal 系统 (openai/codex #23094):Agent 可以通过 update_goal 工具把状态设为 blocked("我卡住了")或 usageLimited("API 配额不够了")。两种状态都会暂停 goal continuation 循环,等待用户 /goal resume。

这是一种模型主动声明停滞的机制------不是外部数到 N 才停,是 Agent 自己说"我做不下去了"。但它需要 Prompt 引导("至少尝试三次后才能声明 blocked"),且依赖模型的诚实性。

流派二的局限性

预算计量对齐了用户真正关心的维度(钱),但有两个盲区:

  1. 价格依赖:不同模型 token 价格差 100 倍。同一个 $5 预算在 GPT-4o-mini 上能跑几千步,在 claude-4.6-opus 上只能跑几十步。预算线的含义随模型变化
  2. 死循环仍在空转 : 5预算的死循环只是在5 预算的死循环只是在 5预算的死循环只是在5 后停,那 5全是浪费。预算限制了∗∗损失上限∗∗,但没有∗∗提前检测∗∗到循环。行为分析能在第10步就发现死循环,预算计量在5 全是浪费。预算限制了**损失上限**,但没有**提前检测**到循环。行为分析能在第 10 步就发现死循环,预算计量在 5全是浪费。预算限制了∗∗损失上限∗∗,但没有∗∗提前检测∗∗到循环。行为分析能在第10步就发现死循环,预算计量在5 花完才知道

流派三:行为分析------看模式不看数字

原理 :不数步数也不数钱------检测行为有没有异常。

这是截至本文写作时最前沿、也最复杂的方向。核心思想:同一个工具被调了 20 次,重要的不是"20"这个数字,是"20 次调用之间有没有变化"。

Hermes:签名级检测(开源社区最接近的方案)

Hermes Agent(Nous Research, 开源)在 2026 年叠加了三层行为检测:

StallDetector(~200 行代码,PR #41477):计算每批工具调用的"签名"------工具名 + 参数 MD5。连续 3 批签名相同,或窗口 7 批中 5 批相同 → 第一次注入系统消息"你在重复",第二次硬停。

ToolLoopDetector (PR #6784,三策略并行):generic_repeat(同工具同参数 N 次)、poll_no_progress(同结果哈希 N 次)、ping_pong(A-B-A-B 交替模式)。还能从模型 reasoning 中提取意图工具名,检测"推理说对了,调用写错了"的不一致。

Session Caps (PR #66600,灵感来自 Claude Code v2.1.212):会话内最多 200 次 web_search + 200 次 delegate_task------防无人值守场景的单类工具 runaway。

Hermes 的做法是开源社区里最接近行为分析 的方案。但签名级检测有一个盲区:两次 edit 的参数 MD5 不同(编辑内容确实不同),但如果每次编辑后测试都失败------这种"换着花样犯同一类错"的伪进展模式,签名检测捕捉不到。

我们的实践:多维度 CycleDetector + ErrorStreak

我们的做法在 Hermes 签名检测的基础上多走了两步------工具分类 和伪进展独立检测。

三个检测维度同时运行:

  1. 完全相同检测:工具名 + 参数 + 结果完全一样------连续 5-7 次就确定是死循环。和 Hermes StallDetector 类似,但阈值按工具类型不同
  2. 模式连续性检测:同类工具连续调用(连续 50 次 read 不做任何 write)------可能迷失方向
  3. 伪进展检测 (Hermes 没有的):独立的 ErrorStreak 安全线------连续 N 次工具调用全部返回错误。操作在变化(签名不同),但结果始终是失败。只要有一次成功就重置计数

工具四级分类------不同工具用不同阈值:

分类 工具示例 模式阈值 理由
探索 read、grep、search 100 读 100 个文件是正常搜索
动作 edit、write、exec 50 50 次编辑通常说明在反复修改
规划 plan 30 反复修改计划几乎可确定在循环
自定义 exec:readonly 自定义 按场景覆盖默认阈值

进度感知的减半计数(Halving) :如果工具参数在变化(每次 edit 内容不同),疑似循环的计数减半。为什么是减半不是归零?因为"参数变化但全部失败"仍然可疑------减半意味着需要指数级更多的变化调用才能掩盖一次真重复。正常工作中很难触及,伪进展中会被累积捕获。

两阶段响应:先自愈再问人

检测到异常后不直接杀------分两步:

第一次触发:注入一条系统消息"你似乎在重复操作,考虑换个方向"。重置计数器。大部分"循环"只是模型的临时困惑,一条提示就能打破。

第二次触发:升级到 HITL------问用户"Agent 似乎困住了,继续 / 停止 / 给指导?"。用户有 60 秒回复,超时 = 拒绝(fail-closed)。

教训:为什么每个检测维度需要独立的自愈机会

早期设计中,三个检测维度共享一个全局的"已自愈一次"标记。结果:Identical Call 触发自愈,模型改变了方法(Identical 重置),但随后陷入新的 Tool Streak 模式------因为全局标记已经用过了,Tool Streak 直接跳到 HITL。

修复:每个维度有独立的自愈计数器。Identical Call 的自愈不消耗 Tool Streak 的机会。这样一个 Agent 可以在 Identical Call 维度被纠正一次、Tool Streak 维度再被纠正一次------总共两次自愈机会,覆盖两种不同的"临时困惑"。

实际效果

在我们的引擎中,这套机制(多维度行为分析 + 两阶段响应)替代了 max_turns:

  • 正常任务不被截断------200 步的大型重构能跑完
  • 死循环在 10-20 步内被检测到------而非等到硬上限
  • 兜底硬上限设在 5000------那不是 max_turns,是"防引擎 Bug 的安全网"

不在同一问题空间的两个特例

Aider:没有工具循环,不存在死循环问题

Aider 经常被拿来和 Claude Code、Cursor 比较,但它的架构完全不同(Aider 底层分析, dev.to/masihmoafi)。Aider 不是 while-tool_call 循环------它的唯一循环是"反射":编辑后发现 parse 失败 / 文件不匹配 / lint 报错 / 测试失败,把问题发回模型重试。这个反射最多 3 次 (max_reflections=3),四种触发条件共享预算。

Aider 没有 max_turns 不是因为信任模型,是因为架构里不存在无限循环的可能。它回避了终止问题而非解决了它。

Cursor:有 stall 检测,但循环仍是活跃痛点

Cursor 是闭源产品。从用户论坛和官方回复可以确认(Cursor 论坛, #161234, #158619):有 stall 检测(流式响应无进展 ~30s 后 abort + retry)、有 terminal timeout(系统级,不可用户配置)。

但死循环仍是 2026 年的活跃用户痛点。论坛持续有报告"Agent 在 Plan Mode 下花 2-3 小时执行 echo、监控后台任务、搜索探索,没有实际进展"。官方承认是 Opus 4.7 Plan Mode 的已知行为,暂无 ETA,建议换模型或换模式。

这说明即使最商业化的产品,也还没有完全解决"Agent 看起来在忙但没在干正事"的问题。stall 检测能抓住"完全卡死",抓不住"忙碌但无效"。


三种流派对比

维度 硬上限 预算计量 行为分析
代表 LangGraph / CrewAI / OpenAI SDK / AutoGen Claude Code / Codex CLI Hermes StallDetector / 我们的 CycleDetector
检测什么 步数/消息/token/时间 美元或 token 花费 操作签名 / 工具模式 / 连续失败
能区分正常工作和死循环吗 ❌ 部分(死循环消耗更高) ✅
假阳性 高(正常长任务被截断) 低(用完预算才停) 低(有自愈缓冲)
假阴性 高(死循环在 N-5 步后才停) 中(花完才知道在浪费) 低(10-20 步内检测到)
实现复杂度 一行计数器 中(需嵌入价格表) 高(行为窗口 + 工具分类 + 阈值)
适用场景 Demo / 短任务 / 原型 SDK / headless / SaaS 长任务 / 生产级 / 无人值守

实际搭配:截至本文写作时最成熟的方案都是组合使用:

层 机制 角色
最外层 硬上限(5000 步) 防引擎 Bug 的安全网
中层 行为分析(CycleDetector + ErrorStreak) 检测真循环和伪进展
内层 预算计量 + 应用层决策 引擎报数,应用层按商业逻辑停

那些看似需要 max_turns 的场景

"防止失控" ------行为分析在十几步内就能检测到真循环。兜底硬上限(5000 步)是"安全网",不是 max_turns------正常任务不可能触及。

"控制成本" ------"花多少算多"是商业决策。正确的做法是引擎计量消耗并提供信号,应用层决定停不停。Claude Code 的 --max-budget-usd 走在了这个方向上------用美元而非步数做预算。

"给用户进度感"------进度不该用"已消耗 / 最大步数"表达。更好的信号:Plan 步骤完成率(5/8)、token 消耗占预算比(65%)、模型自己的判断。


一句话总结

终止的正确抽象不是"跑了多少步",是"行为有没有异常"。 行业已从 max_turns=25 分化为三种流派------硬上限(大多数框架还在这里)、预算计量(Claude Code / Codex 在探索)、行为分析(Hermes 和我们的实践)。真正的前沿在于:能不能在第 10 步就区分正常工作和死循环,而不是等到 N 步或 $N 才知道。



引用来源

本文所有数据均可通过以下链接验证。

相关推荐
用户95179422510292 小时前
拆解 Claude Code 的 Harness:51 万行源码里的七个设计判断
agent
Sailing2 小时前
🚀🚀【封闭结束4】Agent 工具调用工程化:把 Tool Runtime 做扎实
人工智能·面试·agent
jerrywus2 小时前
chrome-devtools-mcp 总弹授权、抢标签页?我是这样一步步配好的
前端·agent·mcp
武子康2 小时前
CUDA OOM 不只是“显存不够”
人工智能·后端·agent
小虎AI生活3 小时前
深入理解 AI 跨软件取数:从豆包 SAEP 封门到 Claude Duet 的三道安全闸门
aigc·ai编程
小易老师AI实战3 小时前
MCP服务快速实现智能体/RAG项目的插拔功能
agent·rag·mcp·fastmcp·大模型工具调用·streamable‑http
古雨蓝枫4 小时前
DeepSeek Harness(DSH)使用指南:功能、自定义模型与插件
ai·ai编程·deepseek·dsh
爱编程的小新☆5 小时前
LangChain 智能体实战:构建技术学习规划 Agent
langchain·agent·工具调用·结构化输出·deepseek·流式输出