Agent Mentor Learn
Agent Harness 基础:循环与控制 · 第 3 / 6 节

第 3 课:停止条件:Agent 什么时候该收手

学习目标:

  • 说清为什么只靠模型自己回 end_turn 收尾不够,讲明「你得信任模型、但它可能连跑很多轮」这对矛盾
  • 给一个骨架循环加上最大轮次这道硬闸,并说清计数器为什么必须放在循环外才能生效
  • 区分「硬停」(到上限就强制停止)和「软停 / 挂起」(停下等人、可恢复),并能为一个 Agent 列出它必须停下的几个条件

前置要求:读过第 2 课,会写由 stop_reason 驱动的 while 循环、知道这个循环靠 end_turn 自然结束 | 上一课 第 2 课 << | 下一课 第 4 课 >>

别指望模型自己喊停

第 2 课那个骨架循环,停下来靠的是一件事:某一轮模型不再要工具,stop_reasontool_use 翻成 end_turn,while 条件不成立,循环自然结束1。换句话说,「这一圈到底还转不转」这个决定,被整个交给了模型——模型说「我说完了」,循环才停。

平时这没问题,可你得看清这是谁在拍板。Agent 本来就是一个由模型自己动态决定接下来做什么、用什么工具的系统2,收不收手也在它自己手里。这份自主正是 Agent 有用的地方,但同一枚硬币的反面是:自主意味着更高的成本,以及误差沿着一圈圈循环累积、放大的可能2。更要紧的一句——模型有可能连续跑很多轮,你只能对它的决策给予某种程度的信任,才敢放手让它跑2

问题就卡在「信任」这个词上。信任不等于放任。如果模型某个环节卡住了、或者被某次工具返回的内容带偏,一直不肯回 end_turn,那个只认 stop_reason 的循环并不会替你着急,它会一圈一圈地陪着模型空转下去——因为你写的 while 条件里,压根没有「转够了就停」这回事。所以,光靠模型自我终止是不够的。你需要一些由宿主这边说了算、不看模型脸色的停止条件。

先给循环装一道硬闸:最大轮次

最基本的那道停止条件,Anthropic 的工程指南里就点到:给循环设一个停止条件是很常见的做法,比如限定一个最大迭代次数,用来把控制权攥在自己手里2。翻成代码,就是给循环加一个「最多转几圈」的上限。

在第 2 课的骨架上,改动很小:

关键有两处。第一,let turns = 0 声明在循环外面。它得跨越一圈圈循环活着、把每一圈都累加进去,这道闸才知道「已经转了几圈」;一旦挪进循环体,就是下一节练习要拆的那种坑。第二,这道闸不关心模型为什么还在要工具——是卡住了、绕圈了、还是被工具输出带偏了,它一概不问;只要圈数撞到上限,宿主就停手、不再发下一次请求,把控制权收回到自己这边。

这就是「硬停」:到了边界无条件停下,循环到此为止。它跟 end_turn 那种「模型觉得完事了」的软收尾是两码事——一个是你给的天花板,一个是模型自己的判断。注意 MAX_TURNS 该设多大没有标准答案,取决于任务本来大概要几轮;这里填 10 只是个占位,重点是这道闸存在、且真的能拦住失控的循环。

硬停之外,还有一种停:停下来等人

最大轮次这类闸有个共同点:撞上就是终点,循环结束、不再自动继续。但停止条件不止这一种。同一篇文章还提到另一类:Agent 可以在检查点、或遇到阻碍时停下来,等人给出反馈再走2。这类停跟硬停不是一回事,值得单独拎出来分清楚:

  • 硬停:到上限就停,循环彻底结束,不再自动往下走。最大轮次、预算耗尽都属于这类。它是终态
  • 软停 / 挂起:循环主动停在某个检查点,把控制权交给人,等人回话之后可以从原地接着跑。它不是结束,是可恢复的暂停

差别落在代码里,就是「返回什么」不一样。硬停返回的是一个终局结果,事情到此为止;软停则要保留状态:返回一份能从原地续上的现场快照——把当前的 messages、卡住的那个待执行动作一起交出去,人处理完之后拿着这份快照就能继续:

典型的软停场景,是模型下一步要干一件不可逆的事——删库、群发邮件、把订单提交出去——你想让人先看一眼再放行;或者模型自己报告「我卡住了,需要更多信息」。这里先把「硬停 / 软停」这条界线立住就够了:needsHumanApproval 到底怎么判、人回话之后循环又怎么从快照恢复,是第 5 课「人在环」的正题。

一个判断框架:先问「它在什么条件下必须停?」

有了硬停和软停这两类,设计一个 Agent 时就有了一个顺手的起手式:先别急着写循环,先把这一个问题问清楚——它在什么条件下必须停? 把答案列出来,通常逃不开这四条:

  1. 任务达成——模型回 end_turn。这条最软,由模型自己判断,但你仍要确认它是真做完了,而不是中途放弃。
  2. 到轮次上限——硬停。就是上面那道 MAX_TURNS 闸,兜住「转起来收不住」的最坏情况。
  3. 遇到需要人拍板的阻碍——软停 / 挂起,停下等人反馈2。不可逆操作、或模型明确报阻塞时触发。
  4. 预算耗尽——硬停。token、花费、耗时任一撞顶就停。这条的细节(怎么算、怎么埋点)留到第 4 课。

把这四条摆在一起,能看出一件事:停不停的决定权并不都在模型手里。第 1 条归模型,第 2、4 条归宿主(跟模型怎么想无关,到点就停),第 3 条是两边共管。第 2 课那个骨架循环只实现了第 1 条,把另外三条全漏了——这一课先补上第 2 条这道最基本的硬闸,第 3、4 条分别是第 5 课和第 4 课要接着补的。

这个框架的价值不在于死记四条,而在于养成一个习惯:动手写循环之前,先把「必须停」的条件显式地数一遍,别让它们隐在「反正模型会停」的默认假设里。

停止条件是低成本高回报,不是过度设计

可能有人嘀咕:加这些闸,是不是把简单的循环搞复杂了?这就要提一句 Anthropic 反复强调的原则——只在复杂度能明确改善结果时,才考虑把它加进来2。这句话常被用来劝人别堆砌花哨机制,但它对停止条件恰恰是放行的。

算一笔账就清楚了。一个最大轮次计数器,不过是循环外一行声明、循环里一次判断,几行代码的成本;而它挡住的那类失败——循环收不住空转、成本失控地往上冲、甚至在一个被操纵或异常的输出上执行了不可逆动作——代价要大得多。Agent 的自主本来就带着更高的成本和误差累积的风险2,一道停止条件正是冲着这个风险去的最便宜的刹车。

所以停止条件不是那个「只在能明确改善结果时才考虑加」的复杂度2——它明确地清过了这道门槛:它把最坏情况的边界从「无限」压到了「有限」,这本身就是可验证的结果改善。它是让一个自主循环变得敢放手去跑的最基本控制,而不是锦上添花的装饰。

小结

  • 循环靠 end_turn 自然结束,但那是模型自己的判断;你只能对它的决策给予某种程度的信任,而它可能连续跑很多轮,所以只依赖模型自我终止是不够的2
  • 最基本的硬闸是最大轮次:一个很常见的做法就是用「最大迭代次数」这类显式停止条件来保持控制2;计数器必须放在循环才能跨圈累加,挪进循环体就成了拦不住任何东西的死代码
  • 硬停之外还有软停 / 挂起:在检查点或遇到阻碍时停下、等人反馈2——它不是终态,而是能从现场快照续上的可恢复暂停
  • 设计 Agent 时先问「它在什么条件下必须停」:任务达成(end_turn,归模型)、到轮次上限(硬停,归宿主)、遇到要人拍板的阻碍(软停,共管)、预算耗尽(硬停,第 4 课细讲)
  • 停止条件是低成本高回报的控制:自主本就带来更高成本与误差累积2,一道 max turns 闸恰好兜住最坏情况;「只在能明确改善结果时才考虑加复杂度」这条原则2,它明确清过了——把无限压成有限,就是可验证的改善

>> 第 4 课:失控与兜底:死循环、空转、预算耗尽

Footnotes

  1. How tool use works — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works

  2. Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents 2 3 4 5 6 7 8 9 10 11 12 13 14

练习

01

你在设计一个「自动修 CI」的 Agent:它拿到一条失败的流水线,可以调 read_logs(读日志)、edit_file(改代码)、run_tests(跑测试)、push(推送到远端触发新一轮 CI)这几个工具,目标是把测试改绿。请为它回答:

Level 1:给一个 Agent 数清它的停止条件
  1. 列出这个 Agent「必须停下」的至少四个条件。
  2. 对每个条件,标注它属于硬停还是软停 / 挂起,并说明这个「停」的决定权在模型、在宿主、还是两边共管。
  3. push(推送到远端、会真正触发别人也看得到的 CI)这一步,你会把它设计成需要软停等人放行吗?给一句理由。
完成标准 · 本地勾选
02

同事想给循环加一道最大轮次的保险,写成了下面这样。他在只需要一两轮工具的任务上试了试,「看着没问题」;可一旦模型陷进「反复调同一个工具、迟迟不回 end_turn」的状况,这道闸完全没拦住,循环还是失控地转了下去。找出根本原因,并改对。

Level 2:这道「最大轮次」闸为什么没兜住
完成标准 · 本地勾选