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

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

学习目标:

  • 认出循环失控的四种典型形态——误差累积、上下文膨胀与腐化、死循环与空转、预算耗尽——并说清每一种是循环的哪个性质在作祟
  • 为每一种失控挂上对应的兜底闸门:检查点加早停、上下文治理、最大轮次加无进展检测、预算上限,并解释闸门拦的是什么
  • 判断一段「原地打转」的循环该加哪种硬闸,而不是靠换模型或改 prompt 去救一个缺边界的循环

前置要求:读过第 2、3 课,手里有一个由 stop_reason 驱动、带最大轮次的循环骨架 | 上一课 第 3 课 << | 下一课 第 5 课 >>

一个能转的循环,也能转脱缰

前两课你把循环立了起来:stop_reason 当 while 条件,再给它配上第 3 课那组显式停止条件,模型不肯收尾时你也能把它按住。听起来这循环已经挺稳了。可「能按住」只是最后那道保险,它拦的是「循环停不下来」这一种毛病。循环出问题的方式不止一种,而且大多数时候,进程并没有卡死、CPU 也没跑满——它在安安静静地、一圈一圈地把事情办坏。

这一课要把「循环失控」这个笼统的词拆成四种能对号入座的形态:误差累积、上下文膨胀与腐化、死循环与空转、预算耗尽。它们的共同根子都在同一个地方——Agent 是个自己动态决定下一步做什么的系统,这份自主正是它有用的原因,也正是它会跑偏的原因。Anthropic 说得很直白:Agent 的自主性意味着更高的成本,以及误差沿循环累积放大的可能1;而且模型有可能连续运转很多轮,你得对它的决策有一定程度的信任才敢放它跑1。信任不等于放任——这一课讲的就是,在信任之外,你还得给每一种失控准备一道兜底闸门。

先说清楚一件事,免得你把这一课读拧了:这四种失控不是「小概率事故」,而是循环这种结构自带的倾向。你不去管它,它默认就朝这几个方向滑。所以兜底不是等出了事再打的补丁,是循环从一开始就该焊上的护栏。下面一种一种来。

失控一:误差累积——一步错,步步错

先看最隐蔽的一种,因为它全程不报错。回想第 2 课那个值班机器人:它先重启服务、再读日志、最后汇报。这三步是串起来的——第二步基于第一步的结果,第三步基于第二步的结果。现在设想第一步就悄悄错了:模型把服务名看成了 api-staging,重启的是预发环境,而工具返回的「重启成功」它照单全收。接下来它去读日志、发现没有新报错,于是喜滋滋地汇报「重启完成,日志干净」。每一步的工具调用都成功了,没有一行异常,可整件事从第二步起就建在一个错误的地基上,越推越偏。

这就是误差累积(compounding errors):Agent 的自主运行意味着误差会沿着一圈圈循环累积、放大1。它和第 2 课那种「代码写错了导致死循环」不是一回事——那种是机制故障,一眼能看出来;误差累积是决策层面的偏差,每一步单看都合理,错误却在链条里复利式地滚大。循环转得越多、链条越长,一个早期的小偏差最后能长成多离谱,就越难预料。

兜底靠两样东西配合。第一是检查点:在链条里那些「一旦错了后面全白做」的节点上停一下,让循环把中间状态暴露出来接受核验——Anthropic 把这类停顿描述为「在检查点或遇到障碍时暂停、等待人的反馈」1,下一课会专门讲这个停顿点怎么设计。第二是早停:与其让一个已经跑偏的循环把预算耗光,不如在检测到异常时就让它停——这正是第 3 课那组显式停止条件的用途,也呼应了「用停止条件来维持控制」这条通用建议1。核心思路是:既然误差会随轮数累积,就别让循环在无人核验的情况下闷头跑太长的链条。

失控二:上下文膨胀与腐化——历史越滚越沉

第 2 课埋过一个伏笔:循环每转一圈,那句 messages.push 就往历史里塞两条消息,只增不减。当时说「留给后面还账」,现在就是还账的时候。

先看「膨胀」这半边。一个在循环里运转的 Agent,会不断产出更多可能与下一轮推理相关的数据2,而这些数据每一轮都被原样带进下一次请求。任务只跑三五轮时无所谓,可一旦循环需要几十轮,历史就滚成一大坨——每一轮请求都在重复搬运越来越长的上下文,请求变慢、变贵,这是最直接的代价。

更麻烦的是「腐化」这半边,它伤的是质量而不只是成本。模型有一份「注意力预算」,它在解析大量上下文时要动用这份预算,而每引入一个新 token,都会消耗掉其中一点2。后果是:随着上下文窗口里的 token 越来越多,模型准确回忆起其中某条信息的能力反而越来越差2。这个现象常被叫作 context rot(上下文腐化)——不是历史被删了,而是关键信息被淹没在一大片噪声里,模型「看得到但抓不准」。于是循环转得越久,模型对早期那条关键指令、那个约束条件的把握越松,行为就越容易漂移。

这里有个尺度必须说准,否则你会防错方向:这种退化是一条随长度平缓下滑的性能曲线,不是过了某个长度就突然崩掉的悬崖2。别把它理解成「上下文一超标就废」——它更像是水位慢慢涨、船身慢慢沉,你在任何一个水位都还能开,只是越来越吃力。承认这一点,才能得出正确的应对姿势:上下文得当成一种有限、且边际收益递减的资源来对待2,往里塞的每一段历史都要问一句「这还值不值」。

兜底叫上下文治理:主动管理循环产出的历史,而不是无脑 push。具体手段——压缩旧轮次、摘要化早期结果、丢弃不再相关的中间产物——正是本系列第 5 门课(记忆与状态)的正题,这里不重复展开。你现在只要接住这个循环视角的结论:治理上下文,本质上是在给循环的「单圈开销」踩刹车,让它转得久也不至于越转越钝。

失控三:死循环与空转——原地打转停不下来

前两种失控里循环还在「往前走」,只是走歪了或走沉了。这一种更干脆:循环根本不往前走,却也不停下。它分两副面孔。

一副是死循环,你在第 2 课的 Level 2 练习里已经见过——循环体末尾漏了给 response 重新赋值,stop_reason 永远停在老值,while 条件恒为真,进程卡死、同一个工具被反复调用。这是纯粹的代码机制故障:宿主代码写错了,跟模型的决策没关系,补上那行重新请求就好。

另一副更隐蔽,叫空转(也叫活锁):代码完全正确,循环每一圈都在合法地发请求、执行工具、读到新的 stop_reason,但它就是不产生任何新进展。典型场景:模型反复调用同一个搜索工具,每次拿回来的都是几乎一样的空结果,它却不换思路,下一轮接着搜同样的东西。从 stop_reason 的角度看这循环「很健康」——一直是 tool_use,一直在正常运转;可从任务的角度看,它在原地踏步,把轮次和 token 一圈圈烧掉,什么也没推进。

死循环靠改代码解决,空转不行——代码没错,你没法靠「修 bug」把它救回来。空转要靠两道硬闸拦:

  • 最大轮次(第 3 课那道闸):给循环一个绝对的圈数上限,不管它转得多「健康」,到顶就停。这是最后的保险丝,保证再怎么原地打转也烧不过 N 圈。
  • 无进展检测:这是专门对付空转的那道闸。思路是让宿主自己盯着「有没有新东西发生」——比如记录最近几轮工具调用的名字和结果,如果连续 N 轮都是同一个工具、拿回几乎相同的输出,就判定「没进展」,主动跳出循环。它比最大轮次更早介入:不用等烧满 50 圈,第 3 轮开始重复你就能拦下来。

两道闸的分工是:无进展检测负责「早点认出原地打转」,最大轮次负责「就算没认出来,也兜住一个绝对上限」。它们都不指望模型自己醒悟——因为空转的根子恰恰是模型不会醒悟,边界只能由循环外面的宿主代码来划。

失控四:预算耗尽——多轮加大上下文,账单失控

最后一种最好懂,也最疼。

前面三种失控,几乎都会以「花钱」的形式表现出来。Agent 的自主性意味着更高的成本1:模型可能连续运行很多轮1,而每一轮的上下文又在变大——轮数 × 每轮 token 数,两个都在涨,账单是相乘着往上走的。一个卡在空转里的 Agent,可能在你没注意的一夜里烧掉一大笔 API 费用,却什么都没做成。

这里的风险还不只是钱。跑很多轮意味着你必须对模型的决策有一定程度的信任1——可你未必想把这种信任给到「无上限」。

兜底:预算上限。 给循环设一个显式的预算天花板,可以按两种口径来记:

  • 按轮次。 最简单,就是第 3 课的最大轮次,本质上也是一种预算。
  • 按 token(或按钱)。 更贴近真实成本:累计消耗的 token 数(或估算出的费用)一旦触顶,立刻停。

关键不只是「停」,还要如实报告。到顶就停、并且老实说明「我是因为预算耗尽停的,任务还没完成,目前进展到这里」。最怕的是预算烧光了还假装交付了一个完整结果——那又绕回了失控一的误差问题。停得诚实,人才知道下一步该怎么接。

把四道闸门收在一起

回头看这四种失控,你会发现它们其实同源:循环缺少边界,而模型自己意识不到、或管不住这个边界。

  • 误差累积 → 检查点 + 早停,把错误斩断在还小的时候
  • 上下文膨胀腐化 → 上下文治理,给循环每轮减负
  • 死循环 → 改代码(补上漏掉的重新赋值);空转 → 无进展检测 + 最大轮次,替模型喊停
  • 预算耗尽 → 预算上限 + 如实报告,给成本封顶

这四道闸门不是可选的装饰。它们是让「自主」不等于「失控」的必要条件。一个没有兜底的循环,跑顺的时候看起来很美,可一旦某轮出岔子,它没有任何机制把自己拉回来——它只会把小问题滚成大事故。

也要留意别走到另一个极端:这些机制本身也是复杂度,你应当在它确实能改善结果时才加1,而不是给每个玩具循环都套上一堆闸门。判断的标准始终是:这个循环失控的代价有多大?代价越大,闸门越该齐。

装了闸门,下一个问题自然浮现:当闸门拦下了循环、或者循环遇到自己拿不准的岔路口,人该怎么介入进来打断、纠偏、接管?那是第 5 课的主题。

小结

  • 循环不会自己喊停:while 只认它的条件,模型只会一轮一轮往下发请求,边界得由 harness 来守。
  • 误差累积——模型这一轮的输入多来自上一轮自己的产出,一步错会被后续每轮继续沿用放大,加上它可能连续跑很多轮,自主性本就伴随更高成本与误差累积的可能1;兜底是检查点 + 早停。
  • 上下文膨胀腐化——循环里数据只增不减2,每个新 token 消耗注意力预算2,token 越多回忆越差,上下文是边际收益递减的有限资源2,且退化是渐变而非断崖;兜底是上下文治理(第 5 门课那套历史管理)。
  • 死循环与空转是两回事:死循环是宿主代码写错(如漏了给 response 重新赋值),靠改代码解决;空转是代码没错、模型反复调同一个解决不了的工具原地打转,靠无进展检测(连续 N 轮无新结果就停)叠加最大轮次做保险丝——别用无进展检测去治死循环,也别指望改代码能治空转。
  • 预算耗尽——轮数与每轮上下文相乘推高成本1;兜底是按 token 或按轮次的预算上限,触顶即停并如实报告进展。
  • 这四道闸门同源:循环缺边界、模型管不住自己;它们不是装饰,是让自主不滑向失控的必要条件,但也只在确实改善结果时才加1

>> 第 5 课:干预与操控:打断、转向、人在环

Footnotes

  1. 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

  2. Effective context engineering for AI agents — Anthropic Engineering — https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents 2 3 4 5 6 7 8

练习

01

下面是三个跑飞的 Agent 循环,各描述了一种失控。请为每一个:(1) 判断它属于本课讲的哪一种失控;(2) 指出最该加的那道兜底闸门;(3) 用一句话说清这道闸门拦在了循环的哪一步。

Level 1:给失控现象配兜底
  • 现象 A: 一个写周报的 Agent,第 2 轮把「本周」误读成了「上周」,之后十几轮全都在总结上周的数据,最后交出一份日期全错但看起来很完整的周报。
  • 现象 B: 一个查资料的 Agent 调用某个 API 时收到「密钥无效」错误,它决定重试,又收到同样的错误,如此往复,已经第 12 轮。
  • 现象 C: 一个 Agent 任务复杂,正常运行,但因为轮数多、每轮上下文又大,一晚上下来 API 账单远超预期。
完成标准 · 本地勾选
02

有人说:「我已经设了最大轮次 50,够了,循环再离谱也就跑 50 轮,没必要再搞什么无进展检测。」

Level 2:为什么「无进展检测」比「只靠最大轮次」更值得加

请反驳或补充这个说法。要求:(1) 说清只靠最大轮次的循环,在遇到「第 5 轮就开始空转」的情况时会浪费什么;(2) 解释无进展检测和最大轮次为什么是互补而非替代关系;(3) 结合本课「成本相乘上涨」和「误差累积」的论点,说明早停的价值。

完成标准 · 本地勾选