第 02 章

Agent Loop 工程 · 从循环到 production harness

从「听懂 agent loop 是什么」升级到「能搭一个生产环境敢用的循环」:把循环看成一台可检视的状态机,决定它什么时候必须停、最多花多少钱,让它在崩溃后能恢复,理解推理模型怎么改变循环设计,以及什么时候根本不该上自主循环。前置:已完成基础路书。

约 3 小时
开篇 · 把一个朴素的循环,变成能托付任务的机器

这是 Agent 工程路书的第 2 章,约 3 小时,纯 Mode A。它是你亲手搭第一个真正能跑的 agent 的起点。

你从基础路书拿到了一个词:agent loop。你知道它的形状是 think-act-observe,知道模型想一步、做一个动作、看结果、再想下一步,直到完成。这一章不重讲这个形状,它回答一个完全不同的问题:从听得懂这个循环长什么样,到能写一个生产环境敢用的循环,中间隔着什么?答案是隔着一整章工程。真实的 production loop 不是三行 while not done,它是一台带多重终止保护、能从崩溃里恢复、还要替模型搬运推理状态的状态机。这一章就是带你把这台机器一个零件一个零件搭起来。读完后,你能把一个朴素的循环升级成一个有终止保护、能从错误里恢复、能跨工具调用保持推理状态的 harness,并且看到一份真实 SDK 的循环源码时,知道它每一支分支在干嘛。

7 节:0 从循环 vocab 到 production harness · 1 loop 作为状态机 · 2 termination 与 budget(生死线)· 3 错误恢复与 durable execution · 4 reasoning-model 原生 loop · 5 workflow 还是 agent · 6 long-horizon 会压垮什么 · 7 一个 framing 注(loop 是模态无关的)。

边界 hard line(贯穿全章):基础路书 Ch4 已经教过 agent loop 是什么,这一章不重教那个词汇,只讲怎么把它做成工程。通用的分布式状态存储、消息队列、K8s 编排归系统设计路书,这里只讲作为 harness 工程师怎么用它们让循环扛崩溃。reasoning model 内部怎么训出推理能力归模型设计和后训练路书,这里只讲 reasoning model 怎么逼着你改 harness 设计。

0. 从「循环长什么样」到「循环能跑」

先把这一章存在的理由说清楚,否则你会觉得它在重复基础路书。基础路书 Ch4 给了你一张图:模型想一步、调一个工具、把结果喂回去、再想,直到它觉得做完了。那张图是对的,但它是一张静态示意图,它告诉你循环长什么样,没告诉你这个循环在真实世界里会怎么坏掉。而真实世界里它会以各种方式坏掉:模型答案早就出来了却停不下来、一个工具超时整个流程卡死、跑到一半进程崩了二十分钟的进度全没、换了个 reasoning model 之后循环行为整个变了样。把这些坑一个个填上,朴素的循环才变成能跑的循环,这就是 Ch2 跟 Ch4 的全部分工:Ch4 给你循环的形状,Ch2 给你能跑的循环。

那么能跑的循环具体多了哪些东西?我把它拆成六层工程,这六层就是这一章后面六节的骨架。第一层是循环本身的架构,把它从一张示意图升级成一个你能检视、能调试的状态机。第二层是终止与预算,决定循环什么时候必须停、最多花多少钱,这是 agent 的生死线。第三层是错误恢复,包括进程内的重试,以及让循环扛得住整个进程崩溃的 durable execution。第四层是 reasoning model 带来的全新一层,2024 年之后的模型把推理塞进了权重里,这逼着 harness 必须学会跨工具调用搬运模型的推理状态。第五层是判断力:什么时候根本不需要一个自主循环,一段写死的代码流程更好。第六层是长程任务会怎么压垮循环的两条隐含假设。

在动手之前,有一个认知要先校正,它是这一章的第一刀。你从教程里学 agent,常常以为自己要亲手写那个 think-act-observe 的格式:在 prompt 里描述思考怎么写、动作怎么写、怎么解析模型输出里的工具调用。停一下,这件事在 2026 年基本不该由你做了。模型已经被专门训练成会吐结构化的工具调用(这是基础路书讲的 function calling),而那个反复调用、查停止原因、执行工具、再调用的循环,是 harness 这层软件的活,不是你的活。今天主流的 Agent SDK 会替你把这个循环跑完,你坐在外面接收它流式吐出来的消息就行。你真正的活,是配置这个循环:给它什么工具、什么时候让它停、最多花多少钱、上下文怎么管、推理状态怎么保留。这一章教的就是这些你的活。

agent loop 工程的本质,不是写循环,是给循环装上它在生产环境活下来所需要的全部护栏和肌肉。

1. Loop 作为状态机

1.1 把 think-act-observe 重画成状态机

你在基础路书 Ch4「Agent 范式入门」见过那张循环图(想回顾随时点回去),但它有一个隐藏的问题:它把循环画成了一条平滑的环,好像每一轮都长得一样。但真实的循环每一轮结束时,模型的输出会落进几种完全不同的情况,而 harness 要根据落进哪一种,决定下一步往哪走。把这件事想清楚的最好方式,是把循环重画成一个状态机:每次模型调用返回后,harness 检查它的输出属于哪一类,据此转移到不同的下一步。最干净的画法是四个出口加一个计数器。模型这一轮的输出会落进四种之一:它给出了最终答案,循环就该终止并返回结果;它要求调用一个或多个工具,harness 就去执行、把结果追加回历史、然后再转一圈;它要求把控制权交给另一个 agent,harness 就切换当前 agent 再继续(这是多 agent 的 handoff,留到 Ch8);或者它触发了一个需要人来批准的动作,harness 就暂停下来,把待批准的动作交还给调用方,等人点头再恢复。每转完一圈,一个轮次计数器加一,一旦超过预设上限,harness 强制抛出异常停下。

输入调用 LLM(历史 + 工具集)检查模型输出落入哪一类 最终答案终止 · 返回结果 要调工具执行 · 结果回灌 交接 agent切 agent(→Ch8) 等人批准暂停 · 批准后恢复done回灌再转一圈每转一圈 turn += 1 · turn > max_turns → 抛 MaxTurnsExceeded 强制停
Figure · 一轮 LLM 调用返回后,harness 看输出落入四个出口的哪一个 · 只有①终止离开循环,②③④ 回灌再转一圈 · 每转一圈 turn += 1,超过 max_turns 强制抛异常停 —— 对应正文 第 1.1 节

这个四态加计数器加上限的结构不是教学上的简化,它就是真实 SDK 的字面实现,下一节你会在源码里看到一模一样的东西。把它当成这一章的脊柱:后面讲终止和预算,挂的是这个计数器和上限;讲错误恢复,处理的是要调工具那条分支里工具失败的情况;讲人类批准,就是等人批准那个状态。先把这台状态机的骨架记牢,后面所有零件都装在它身上。

1.2 读一份真实 SDK 源码:那个 while True

说状态机是 SDK 的字面实现,不是比喻。打开 OpenAI 开源的 Agents SDK,它的运行核心(run.py 里)就是一个 while True 循环。循环里每跑完一个轮次,代码会算出一个叫 next-step 的东西,然后分四支转移:如果是最终输出,就校验输出类型、跑一遍收尾钩子、然后 return,循环结束;如果是 handoff,就把当前 agent 换成下一个 agent,继续循环;如果是再跑一次,说明工具已经执行完、结果已经追加进去了,继续循环;如果是中断,就暂停下来,把待批准的工具调用交还给调用方做人类批准。看到了吗,这四支跟上一节那张图严丝合缝。计数和上限也在那里:每转一圈,代码把当前轮次加一,然后检查有没有超过 max_turns,超过就抛一个名为 MaxTurnsExceeded 的异常。这个 max_turns 有个默认值,但我必须诚实提醒你,这个默认值在不同版本里变过(社区里能查到等于 10 的记录,文档和教程里还流传过 5 的旧说法),而且 SDK 的模块边界和行号会随版本漂移。所以你真去读源码时,先把仓库锁到一个具体 commit 再读、再引用,别照搬任何教程里写死的行号或常量,这是读所有活跃开源 harness 的基本纪律,Ch11 整章源码巡礼都会反复强调它。

抛异常并不是唯一的收场方式。这个 SDK 还提供了一个错误处理器的配置项,让你在撞上 max_turns 这类情况时返回一个受控的最终输出,而不是直接抛异常炸给上层,这是一个很实用的工程选择:生产环境里你往往希望 agent 优雅地说我尽力了、这是目前的结果,而不是抛一个栈给用户看。最后还有一个最容易让初学者犯迷糊的点,源码替我们澄清了:一次 Runner.run() 调用在对话层面算一个 turn,但它内部可能跑了好多次 LLM 调用、执行了好几个工具、甚至切换了 agent。换句话说,turn 不等于一次 LLM 调用。你以后看任何 agent 框架的文档,看到 turn 这个词都要先问清楚它指的是哪一层,否则你对预算和轮次上限的理解会差出一个数量级。

1.3 谁来写这个循环:Client SDK 还是 Agent SDK

知道了循环长什么样,下一个问题是:这个循环谁来写?这正是讲清楚什么是 harness 的最佳切入点。用 Anthropic 的两套 SDK 对照最清楚。裸用 Messages API 的 Client SDK,意味着循环由你自己写,你的代码大概长这样:

python
# Client SDK:你自己写循环
response = client.messages.create(...)
while response.stop_reason == "tool_use":
    result = your_tool_executor(response)   # 你的代码执行工具
    response = client.messages.create(tool_result=result, **params)
# 通常终于 stop_reason == "end_turn"

这段就是无数工程师反复手写的同一段样板代码:调用、检查停止原因是不是要用工具、本地执行工具、把结果接回去、重复。而 Agent SDK 把这段循环替你写了,你的代码缩成这样:

python
# Agent SDK:harness 替你写循环
async for message in query(prompt="修复 auth.py 里的 bug"):
    print(message)

模型在里面跑完整个循环(读文件、跑命令、改代码),你只是迭代它流式吐出来的消息。这一屏对照就是 harness 这个词最直观的样子:harness 是替你跑循环的那层软件。不过这里有个 2026 年的修正我得诚实标注,否则你会学到一个过时的对立。Client SDK 就必须手写循环这个说法已经不太准了,Anthropic 现在在 Client SDK 里也提供了能自动跑工具循环的能力。所以更准确的区分不是自动循环对手动循环,而是:Agent SDK 的独特价值在于它带着一个持久的 shell 和文件系统上下文、内置了一批工具、提供更高阶的 agentic 行为,而不只是替你 looping 这一件事。你选 Agent SDK,是因为要那套持久环境和高阶能力,不只是图省一段循环代码。

1.4 另一条路:把控制流画成一张图

到目前为止我们讲的循环,控制流都是隐式的:模型自己决定下一步去哪,harness 只是忠实执行。还有一条相反的路,代表是 LangGraph,它把控制流外化成一张明确的有向图:节点是步骤,边是流转,状态在节点之间传递。这两条路是一个重要的设计取舍,值得你拿在手里掂量。隐式循环这边,模型自主决定下一步,自主性高,但可检视性低,你很难提前知道它会怎么走。显式图那边,控制流写死在图的拓扑里,可视、可调试、确定,但 agent 的自主程度低。所以选择标准很清楚:当你要的是确定性和可检视性、胜过模型的自主发挥时,选显式图,典型场景是审批密集、合规要求高、流程基本固定的业务;反过来,当任务本身就需要模型灵活应变时,隐式循环更合适。这条取舍在下一节(workflow 还是 agent)会用一个更大的框架重新出现。

显式图还顺手解决了一个我们下一节才正式讲的问题。LangGraph 把状态在每个节点完成后落库这件事做成了一等公民,于是进程崩了可以从最后一个检查点恢复,而不用从头重跑。这正是 第 3 节 要讲的 durable execution 的一种具体实现,你先记住这个前向引用:控制流画成图,顺带就把崩溃恢复变简单了。

1.5 Streaming 还是 blocking

循环架构还有一个真实的工程维度,基础路书在讲 API 三种模式 时碰过一次,这里从循环的角度再看一遍。同一个底层循环,结果可以用两种方式投递给你:阻塞式是等整轮跑完一次性把结果给你,脚本、批处理、跑评测用这种;流式是一个 token 一个事件地往外吐,交互式的 agent、给用户看的界面用这种。关键是要记住,它们是同一个底层循环,只是投递方式不同,OpenAI 那三个运行方法共用同一个 while True。流式带来一个对终止和预算的真实影响,值得你现在就警惕:一旦 token 已经流出去了你就收不回来了,这意味着你想在中途叫停一个跑飞的循环时,已经吐出去的内容已经产生了成本、也可能已经触发了下游动作。后面 第 2.4 节 会讲到一个 reasoning model 死循环每分钟烧掉几十美元的案例,流式让这种中途叫停比阻塞式更棘手;流式还会影响你在哪里接入轨迹观测,这条线连到 Ch9 的评测。

2. Termination 与 budget:agent 的生死线

2.1 停止信号有两类,必须同时上

如果说上一节的状态机是骨架,这一节就是保命的那根弦。你在 基础路书 Ch4 第 1.4 节 终止条件 已经学过这根弦的基本面:循环必须能停,基本停法有 5 种,production 至少要 max_iter 加预算加超时三件套兜底,否则就是定时炸弹(想回顾点链接)。这一节不重复那一层,它解决的是 Foundations 故意留白的工程问题:那几种停法里哪些靠得住、哪些会骗你,以及怎么把预算从一个写在文档里的数字变成循环每一步真的在执法的护栏。要做对这件事,得先把停止信号分成两类来看。

第一类是语义终止,也就是模型或任务自己判断做完了。最理想的形态是模型自然停下,比如 Anthropic 的 stop_reason 变成 end_turn,或者 OpenAI 那边产出了结构化的最终输出。但这个最理想的形态恰恰最不可靠,模型经常不会自己停,所以业界形成了一个关键共识:别指望模型自然终止,给它一个干净的终止动作。具体做法是给模型一个专门的完成工具(常叫 done),这个工具没有真正的执行函数,模型调用它就等于发出我做完了的信号,循环因为没有可执行的东西而停下。还有一个相关的细节也属于语义层:工具的返回要给出明确的终态,返回成功或失败,而不是含糊的可能还有更多结果。一个语义上空洞的返回(格式合法但没有有效数据)会骗模型以为还有进展、于是不停重试,有人实测过,把含糊返回改成明确终态,工具调用次数从 14 次降到了 2 次。

第二类是确定性护栏,也就是系统强制叫停,根本不信任模型的概率判断。这里有一组护栏:轮次上限(就是上一节那个 max_turns)、整个运行的 token 预算硬上限、墙钟超时、美元成本上限,再加一个连续错误熔断器(连续多次出错或反复做同一个动作就熔断)和一个对重复调用的去重(同一个工具同一组参数被反复调用超过阈值就拦截,回灌一句重复调用已拦截逼模型换条路)。这一类护栏的共同点是它们不讲道理、到了线就停,正因为不讲道理,它们才靠得住。这里有个教学落点要点出来:基础路书或者很多教程会告诉你设置轮次上限、预算、超时,这三个都属于第二类确定性护栏,但很多死循环的真正原因不是没设上限,而是模型压根没拿到一个干净的停信号,也就是第一类语义终止没做好。所以这一章要你记住的是:两类信号必须同时上。确定性护栏是底线兜底,语义终止是让循环在正常情况下优雅收尾,缺了哪一类都会出问题。

2.2 把预算变成可执法的护栏

预算不能只是一个写在文档里的数字,它得是循环每一步真的在执行的东西。把它拆成四个可以执法的维度加一个熔断器,你就有了一套生产级的预算护栏。四个维度各管一摊:token 维度,每一步累加用量,过了上限就停,而压缩上下文能降低这个分母(那是 Ch6 的活);时间维度,墙钟超时,长程 agent 尤其需要;成本维度,token 数乘单价累加,跨多个模型跑时按各自实际单价算,这条线连到 Ch12 的生产经济学;工具调用维度,限制调用的次数和频率再加上去重,这条线连到 Ch3 的工具治理。这四维之上再扣一个熔断器,N 步、X 美元、连续多次错误任一触发就直接杀掉整个运行。这个熔断器被很多有经验的工程师称为最被低估的一个模式,原因很朴素,有个开发者复盘说,他还没注意到,一个 agent 循环已经烧掉了四十美元的 token。

比硬杀更高级一档的,是优雅恢复的阶梯。循环卡住时不是立刻杀,而是逐级升级:先给一个反思提示、建议它换个工具试试;还卡就压缩上下文、重启推理;最终实在不行,带着已经拿到的部分结果优雅终止,把已有的交出来、再解释一下为什么没全做完。这个阶梯让你的 agent 在撞上预算线时表现得像个尽责的同事,而不是一个突然死掉的进程。

2.3 这套护栏的由来:回到 AutoGPT

为什么终止和预算被反复称作生死线、而不是可选的优化?本路书 Ch1 第 1.2 节 玩具与第一批教训 已经讲过那个故事:2023 年的 AutoGPT 给个目标就让模型全自主跑,结果签名失败模式就是死循环,找不到解法就困在里面,卡着卡着把 API 费用烧光,那个能连续自主跑很久不耗尽预算的承诺彻底破产。这一节不重讲那段历史,只点出它留下的工程遗产:你今天看到的所有标准实践,token 预算、按难度分级路由到不同模型、上下文裁剪、熔断器、缓存,全都是那次撞墙之后长出来的教训。这也是一个清晰的 Layer B 时间锚:终止和预算这套工程,是 2023 年 AutoGPT 撞墙的直接产物;把这一点记在心里,你就不会把这些护栏当成可有可无的装饰。

2.4 reasoning model 让「停」变得更难

你可能会想,2024 年之后模型变聪明了、会推理了,死循环这种低级问题是不是该自动消失了?恰恰相反,reasoning model 让终止这件事变得更微妙、而不是更简单,这一点很反直觉,值得专门讲。会推理的模型在循环里有一个反复出现的毛病:答案其实已经得出了,它却继续推理下去。有一个被记录下来的极端案例,一个 agent 跑了八百多步推理、每分钟烧掉几十美元、却永远不交付,它不停地精炼逻辑、质疑自己的结论、要求更多数据;另一个编码助手撞进递归循环,烧掉了十几亿个 token。为什么会这样?有研究从模型层面给了解释:当正确地往前推进这个动作很难、而继续循环这个动作很容易得到时,模型会给容易的循环动作更高的概率而卡住,再加上 Transformer 对时间相关模式有某种归纳偏好,让同样几个动作反复被选中;调高采样温度能缓解,但治标不治本。

所以这一节最该让你记住的一句话是:reasoning model 时代,显式的完成工具和确定性护栏不是更次要了,而是更重要了,因为模型更擅长把自己绕进推理的牛角尖。顺带说一个会在 Ch9 反复用到的判断:这种死循环是 harness 的失败,不是模型的失败。模型没拿到干净的终止信号、没有护栏拦它,这是你这个 harness 工程师的责任。学会把失败的责任分到正确的那一层,是后面评测和诊断章节的核心能力,这里先埋下种子。

3. 错误恢复与 durable execution

3.1 进程内:重试、部分失败、把错误喂回循环

循环的每一步都可能失败:LLM 接口返回 429 限流或超时、工具抛异常、网络抖一下。production loop 要有一层错误恢复,先讲进程还活着时这一层怎么做。最基本的是重试加指数退避,但这里有一条铁律:只对幂等的步骤自动重试。只读的工具、LLM 调用本身,这些重试是安全的;而有副作用的步骤,写文件、下单、发邮件,绝不能闭眼重试,你得先查这个操作的幂等键、确认它是不是已经做过了,再决定要不要重放,这条幂等的线 第 3.4 节 会接着讲。除了重试还要处理部分失败:一轮里并行调了好几个工具,有的成功有的失败,正确的做法是保留成功的结果、把失败的那个连同错误信息回灌给模型、让它自己决定重试还是换条路,而不是整轮丢弃重来。

这里有个很优雅的设计值得点出来:把错误当成一个 observation 喂回循环。还记得 think-act-observe 里的 observe 吗?一个工具失败了,它的错误信息就是一次 observation,模型看到这条路走不通会调整策略,这其实是 Reflexion(留到 Ch7)那种自我反思能力在进程内的雏形。但要小心和 第 2.1 节 那个坑撞车:错误信息要明确,别给一个语义空洞的返回,否则模型又会被骗着以为有进展。还有一点把这一节和上一节缝起来:连续错误的计数会触发 第 2.2 节 那个熔断器,而你要学会区分可以重试的瞬时错误和该叫停的持久错误,这个区分本身就是判断力。

3.2 durable execution:让循环扛得住进程崩溃

进程内的恢复管的是步骤失败了怎么办,但还有一类更狠的失败:整个进程崩了。机器重启、服务重新部署、容器被回收,你那个跑了二十分钟、烧了不少 token 的 agent,进度全没了。durable execution 这个编程模型就是来解这堵墙的。它的定义是这样:runtime 自动把工作流的每一步都检查点(checkpoint)到一个数据库里,进程崩溃、重启、重新部署之后,工作流从最后一个完成的检查点恢复,而不是从头重跑;对 agent 来说,这意味着那些昂贵的 LLM 调用、工具调用、多步推理链,不会因为基础设施出故障就丢失或者被重复执行一遍。为什么 agent 越来越需要这个?因为任务越来越长:一个研究任务跑二十分钟、一条多阶段的数据处理流水线、一个带着外部副作用的多步流程,丢进度的代价变得不可接受。用一句话概括这个转变,一个 agent 不再是一次请求配一次响应那么简单了。

3.3 两种流派:journal 重放还是节点落库

2026 年的 durable execution 平台分成两大流派,机制不同,对你设计 agent 循环的含义也不同。这一节不是要你记住产品名,是要你拿到一种 durable 素养,看到任何一个这类平台都知道它属于哪一派、有什么坑。第一派是日志重放,代表是 Temporal 和 Restate:runtime 维护一份事件日志,记下每个完成的步骤,崩溃恢复时工作流函数从头重新执行一遍,但日志里已经有结果的步骤直接返回缓存值、不重跑,直到遇到第一个不在日志里的步骤才真正继续。这一派有一个 Ch2 必须讲清的陷阱:它要求工作流是确定性的,而 LLM 调用本身是非确定的,所以你必须把 LLM 调用包成一个独立的步骤(在 Temporal 里叫 activity),让它的结果在第一次执行时就写进日志、重放时永不重跑。这是 durable execution 新手最常踩的坑:把非确定的 LLM 调用或工具调用当成普通确定步骤直接塞进重放路径,恢复时就会产生不一致。Temporal 比较重,要独立的 serverworker;Restate 轻一些,它像一个反向代理坐在你的 agent 进程前面,接管连接、透明处理故障检测和重试,适合边缘和 serverless 那种 Temporal 嫌重的场景。

第二派是节点落库,代表是 LangGraph 和 DBOS:每个工作流节点完成后把状态写进数据库。LangGraph 用的是检查点机制,这正是 第 1.4 节 那个显式图模型的天然延伸,图都画出来了,在每个节点后落一次状态是顺理成章的事。DBOS 是这里面最轻的,它就是一个进程内的库,不需要任何新基础设施,把应用数据和执行状态都放进 Postgres(或者 SQLite),装上库、给工作流和步骤加上注解、照常跑就行,执行时它自动把进度检查点进去,出错从最后完成的步骤恢复,已经完成的模型调用和工具调用从数据库里取回而不是重新执行。它的约束跟第一派一样:工作流要确定性,有副作用的 I/O 放在步骤里,步骤失败会从头重启。

日志重放 · Temporal / Restate崩溃后从头重跑,日志里有结果的步骤直接返回不重做1. 读取任务✓ 已记录2. 调 LLM 决策✓ 已记录3. 执行工具(有副作用)✓ 已记录4. 写结果恢复:从头跑 → ①②③ 命中日志秒回 → 从④继续节点落库 · LangGraph / DBOS每步完成把状态写库,崩溃从最后检查点续1. 读取任务✓ 已记录2. 调 LLM 决策✓ 已记录3. 执行工具(有副作用)✓ 已记录4. 写结果恢复:直接从第 3 个检查点往下续💥 进程在此崩溃
Figure · durable execution 两流派 · 进程崩在第 3 步后:日志重放从头重跑、用日志跳过①②③只重做④;节点落库直接从第 3 个检查点续 · 共同硬规则:LLM 调用非确定,必须包成可记录的「步骤」,否则恢复会不一致 —— 对应正文 第 3.3 节

3.4 你能今天就上的最简三件套

上面两派听起来都有点重,但 durable 这件事不是非得一上来就引入一个大平台。2026 年的共识里有一条最实在:durability 不只是存对话状态。很多人以为把对话摘要存下来就算有持久化了,但对话摘要利于聊天连续性,真正利于恢复、审计和安全执行的,是持久化的任务状态、事件日志和工作区产物。所以你能今天就动手的最简三件套是这样:给每个任务一个稳定的 ID,持久化这个任务的状态(它跑到哪了),给每个有副作用的工具包一层事件日志加幂等键。这三件事不需要任何花哨的平台,但立刻让失败变得可检视、让重试变得更安全。重放一个有副作用的工具调用之前先查这个操作是不是已经成功了或者正在进行中,用幂等键加一个操作账本加外部 ID 来保证,这就是 第 3.1 节 里那条有副作用的步骤别闭眼重试的具体落地。一个生态信号能帮你判断这件事的分量:连 AWS 都给 Lambda 加了 durable execution、最长能跑一年,Cloudflare 也在它的 Durable Objects 上文档化了长程 agent,长寿命的 agent 正在变成默认形态。

3.5 边界:#8 用它,#2 造它

这一节最容易越界,所以专门划一条线。讲 durable execution 时,你很容易滑进分布式系统的实现细节,那不是这一章该教的。

边界 · harness 工程师用 durable execution,系统工程师造它

Agent 工程路书(#8)教的是 durable execution 的 agent-loop 语义:为什么 LLM 调用必须包成可日志化的步骤、检查点该打在循环的哪个点、崩溃恢复后模型怎么从工作区产物重建上下文、幂等键怎么保护有副作用的工具。也就是作为 harness 工程师怎么选、怎么接:Temporal 重而动态、DBOS 零额外基础设施、Restate 与 SDK 无关且适合边缘,这三者的取舍。

底层的分布式状态存储、一致性协议、消息队列、编排基础设施本身怎么实现,归系统设计路书(#2)。一句话分工:#8 教怎么用 durable execution 让我的循环扛崩溃,#2 教 durable execution 引擎底层的分布式系统怎么造。这一章引用 Temporal、DBOS 时严守这条线,不碰 Raft 或 Postgres 内部。

4. Reasoning-model 原生 loop

4.1 reasoning 搬进了权重,harness 要跟着变

这一节讲的是 2024 年之后对 agent 循环设计影响最大的一件事,也是把读过 ReAct 论文和能搭一个 2026 年真能跑的 agent 区分开的那道坎。它的根源你在 Ch1 认知弧 第三条见过:推理从一个 prompt 技巧,变成了训练进模型权重的能力(o1、o3、DeepSeek-R1、Claude 的扩展思考都是)。这件事对 harness 工程有三个直接后果。第一,别再手写思维链脚手架:对一个专门训练过的推理模型,你在 prompt 里写请一步步思考收益微乎其微,却白白膨胀 token 和延迟。第二,prompt 给目标、不给步骤:过度规划的 prompt 反而会伤害推理模型的表现,你把目标说清楚、把约束讲明白,让它自己规划。第三,也是最硬的一条,跨工具调用保留模型的推理状态成了一个硬性的 harness 要求,这意味着循环里要搬运的状态不再只是消息历史了,还包括模型那一轮私下的推理。这三条都是纯工程侧的事,模型内部到底怎么推理出来的归模型设计路书,这条线我后面会一直守着。下面两节给你两家的一手 API 契约,你会看到保留推理状态具体是什么意思。

4.2 OpenAI:把推理状态接力下去

OpenAI 这边有个特殊情况:大多数强模型都是推理模型,答你之前先私下推理,但它把这段推理轨迹藏起来了,不像别家在响应里给你看完整思维链。问题就来了,在一个多步的工具调用循环里,如果你不把上一轮那段藏起来的推理带到下一轮,模型就丢了自己的思路,它家的 Responses API 就是为解决这个而生的。保留推理状态有两种方式:一种是有状态的、也是它推荐的,每次调用返回一个响应 ID,你下次请求时把 previous_response_id 传进去,OpenAI 在服务端替你管历史(包括那些藏起来的推理片段)、自动接上;另一种是手动的,适合工具很多、你想自己管上下文的场景,把上一轮响应的输出(包含推理片段)追加进下一轮的输入,规则是最后一条用户消息和你的工具调用输出之间的所有条目必须原样传回、一个都不能动。这里有个结构细节会绊倒从老接口过来的人:在这套接口里,工具调用和它的输出是两个不同的条目,靠一个 call_id 关联起来,传回时你得理解这个关联关系。

不保留会怎样?它家的失败是静默的,这反而更危险。官方文档说得很直白,如果第二轮把第一轮的推理片段忽略掉、移除掉,那后面的调用就拿不到完整的缓存命中,因为那些推理片段从 prompt 里缺失了。后果是两层:一层是缓存效率下降(它们披露过一组数字,从老接口换到 Responses API,缓存利用率从 40% 升到 80%,不过这是厂商自述的口径,你当方向参考、别当普适定律),另一层更要命,是模型的多工具表现下降,说白了就是又贵又变笨,而且没有任何报错提醒你。它们还披露过保留推理片段在 SWE-bench 上大约带来 3% 的提升、在另一个基准上约 5%,同样是厂商口径,记住方向就行。

4.3 Anthropic:thinking block 必须原样传回

Anthropic 这边的推理状态载体叫 thinking block,在工具调用循环里它有一套比 OpenAI 更刚性的 API 契约,刚性到漏了直接报错、不是静默变笨。契约的核心是:工具调用期间,你必须把上一条 assistant 消息里的 thinking block 原样、完整、不修改地传回 API 才能维持推理的连续性,而且这一串连续的 thinking block 必须和模型当时生成的输出完全一致,你不能重排、不能改动。漏了会怎样?直接抛错,不是性能下降。这比 OpenAI 那种静默降级要刚性得多,某种意义上也更友好,因为它逼你当场就改对,而不是上线后慢慢变贵变笨还查不出原因。具体实现上,第一轮你把 thinking block 抽出来,第二轮把它和工具调用块一起放进 assistant 消息里、再跟上工具结果;还有个顺序约束,开了推理又有工具调用时,thinking block 必须是 assistant 消息的第一个内容块。

这套机制还有几个会咬人的工程细节,我列出来让你有个印象,真用到时回 Anthropic 扩展思考文档查。token 预算上有个陷阱:开了交错思考(interleaved thinking)时,思考的 budget_tokens 可以超过 max_tokens,因为它代表的是一整个 assistant 轮次里所有 thinking block 的总预算,这跟你直觉里预算应该小于上限不一样,做 token 会计时要小心。缓存上也有个失效点:改了思考相关的参数(开关或预算变化)会让消息层的缓存断点失效,而思考型任务常常跑超过五分钟,所以建议用更长的缓存窗口来维持命中。还有一个版本演进的提醒,跟读源码那条纪律一样:开启交错思考的那个 beta 请求头在新模型上已经不需要了(新模型用自适应思考时自动开启),但旧模型还得手动带,这些参数名和启用条件演进很快,写代码前先 pin 一下你用的模型版本和文档。

4.4 两家对照,和一条设计原则

把两家的契约并排放,你会看到同一个工程命题的两种解法。下面这张表是收口用的,前面两节的 narrative 已经把概念立起来了,这里只是给你一个随时能回查的对照。

维度OpenAI(Responses API)Anthropic(扩展思考)
推理状态载体推理片段(默认服务端藏起)thinking block(带加密签名)
跨工具调用保留previous_response_id 或手动传回输出条目必须把原样的 thinking block 传回上一条 assistant 消息
漏掉的后果缓存↓、性能↓(静默)直接报错(刚性)
token 会计陷阱推理片段算 input,影响缓存交错思考时 budget_tokens 可超 max_tokens
缓存失效点丢推理片段则无法完整命中改思考参数则消息缓存失效

把这张表浓缩成一句话,就是这一节的锚:

reasoning-model 时代,循环里要搬运的状态不只是消息历史,还包括模型的推理状态;harness 必须把它跨工具调用完整搬运,否则要么报错、要么悄悄变笨变贵。

这是一个纯粹的工程命题,它和模型内部到底怎么推理是正交的两件事,后者归模型设计路书。

保留推理状态turn 1+ reasoning 状态tool callturn 2+ reasoning 状态tool callturn 3+ reasoning 状态✓ 缓存命中 · 保持聪明丢掉推理状态turn 1+ reasoning 状态tool callturn 2✗ 推理状态丢了tool callturn 3✗ 推理状态丢了✗ 缓存 miss · 变贵变笨
Figure · reasoning-model 时代,循环要搬运的不只是消息历史,还有模型的推理状态 · 跨 tool call 保留它 → 缓存命中、保持聪明;丢掉它 → 缓存 miss、又贵又笨(OpenAI 静默降级 / Anthropic 直接报错)—— 对应正文 第 4 节

4.5 顺带一提:compaction

还有一个 2026 年的新原语正好卡在这一节和长程任务之间,值得一提。长循环里 token 会不断累积,顶到上下文窗口、推高成本和延迟,OpenAI 的 Responses API 新增了一个 compaction 能力,在保持语义状态的前提下自动把上下文压小:渲染的 token 过了阈值就自动跑,吐出一个压缩后的条目、把之前的上下文裁掉。你现在只需要知道,长循环需要一种自动管理自己上下文预算的 harness 级机制,而平台开始提供这种原语;更完整的上下文工程,怎么选择、压缩、检索、卸载,以及上下文为什么会随长度退化,是 Ch6 一整章的事,这里只点一句、不展开。

5. Workflow 还是 agent:先用简单的

5.1 两种系统:预定代码路径 vs 动态自主

前面四节都在教你怎么把一个自主循环搭好搭稳,这一节要踩一脚刹车,讲一个判断力问题:很多时候你根本不需要一个自主循环。Anthropic 在「Building Effective Agents」里给了一个特别干净的二分,值得你记牢。一类系统叫 workflow,是 LLM 和工具通过预先定义好的代码路径来编排的,控制流写死在代码里;另一类才叫 agent,是 LLM 动态地指挥自己的流程和工具使用、自己掌控怎么完成任务。这两类的区别轴是可预测性对自主性:workflow 可预测、可控,agent 灵活、自主,但成本更高,而且有误差累积的风险(每一步都可能引入一点错,长循环里会滚雪球)。这个二分其实就是 第 1.4 节 那个隐式循环对显式图取舍的放大版,只不过这次是从要不要上自主性这个更高的层面问。

5.2 五个 workflow pattern

在跳到完全自主的 agent 之前,有五个 workflow pattern 是你该先试的梯子,它们覆盖了大量看起来需要 agent、其实不需要的场景。下面这张表给出每个 pattern 和它的适用场景,这是 narrative 之后的收口表,不是用来代替理解的。

Pattern一句话什么时候用
提示链(prompt chaining)任务拆成顺序步骤,每步处理上一步输出,中间设程序化的检查关卡任务能干净地拆成固定子任务时,用延迟换准确
路由(routing)先分类输入,再导向专门的下游处理有明显不同的类别、分开处理更好时
并行(parallelization)多个 LLM 并行跑(拆独立子任务,或同任务多次投票)再聚合子任务可并行提速,或要多个视角提高置信度时
编排者-工人(orchestrator-workers)一个中央 LLM 动态拆任务、派给工人、再综合结果复杂任务,而且你预测不了需要哪些子任务时
评估者-优化者(evaluator-optimizer)一个 LLM 生成、另一个评估反馈,成环迭代有清晰的评估标准、迭代精修有可衡量价值时

注意编排者-工人那一行的适用条件:你预测不了需要哪些子任务。这正是区分该上 workflow 还是该上 agent 的判据,记住它,下一节会用到。这五个 pattern 里涉及多个 agent 协同的深入工程(编排者-工人、去中心的 handoff)留给 Ch8,这一节只在 workflow 还是循环这个取舍层面引入它们当判断锚。

5.3 能用简单方案就别上 agent

把上面两节收口成一条原则,它来自同一篇文章,我建议你把它贴在显示器上:先从简单的 prompt 开始,用全面的评测去优化它,只有当简单方案不够用时才加上多步的 agentic 系统。换句话说,自主 agent 不是默认选项,是最后手段。这条原则之所以重要,是因为它对着 agent 工程里一个最常见的冲动:一上来就搭一个花哨的自主循环。但自主循环意味着更高的成本、更难的调试、误差累积的风险,而那五个 workflow pattern 就是先试简单的的具体梯子,你顺着试下来,大量场景在路由或提示链这一层就解决了。真正需要一个自主循环的判据很明确,就是上一节那句:当你预测不了任务需要哪些子任务时。这条判断力会在 Ch1 那个认知弧第一条上回响,可靠性来自系统结构、而不是把循环搭得越复杂越好,有时候最可靠的系统结构恰恰是一段不带自主性的、写死的代码路径。

6. Long-horizon:长程会压垮什么

最后一节讲长程任务。前面搭的循环在几分钟、十几轮的任务上跑得很好,但当任务拉长到几十分钟、跨越很多步甚至很多次会话时,循环有两条隐含的假设会崩塌,这一节的目的不是教你怎么解决长程问题(那分散在后面好几章),而是教你认出这两堵墙、并把它们交给对的工具。第一条崩塌的假设是上下文装得下整个任务:Anthropic 在长程 harness 的复盘里记录了一个很典型的失败,agent 倾向于一口气想做太多,本质上是想一把梭哈把整个应用做完,结果常常在实现到一半时耗尽上下文。第二条崩塌的假设是一次会话能跑完:长程 agent 必须在离散的会话里工作,而每一个新会话开始时,对之前发生过什么没有任何记忆,在这种情况下还有一个相关的失败模式是过早完成,agent 做了部分进度就宣布做完了,或者没好好测试就把一个功能标记成完成。

怎么撑过去?Anthropic 给的范式是用两个 agent 接力:一个初始化 agent 首次跑时造好一个初始化脚本、一个进度文档、再打一个初始的 git commit;之后的编码 agent 在后续会话里增量推进,靠 git commit 和那个进度文档留下结构化的更新。核心洞见是,要让 agent 在带着一个全新的、空白的上下文窗口启动时能快速搞清楚工作进行到哪了,靠的就是那个进度文档加上 git 历史。但这里我要严守边界,把每堵墙交给它该去的章节:上下文耗尽和上下文退化的深入处理(上下文是有限资源、怎么压缩、怎么卸载到文件系统)是 Ch6;离散会话之间怎么续接产物,以及异步、后台、headless 的部署形态(Devin、Codex Cloud、各种后台 agent)是 Ch12;进程崩溃恢复就是 第 3 节 那个 durable execution,只不过那是从故障视角看、这里是从会话边界视角看同一件事。

所以 Ch2 关于长程的那一句话是:长程让循环的两条假设崩塌,一条是上下文装得下整个任务(撞上去就交给 Ch6),一条是一次会话能跑完(撞上去就交给 Ch12 的产物续接或 第 3 节 的 durable execution),这一章的活是认出这两堵墙、把它们交给对的工具。

7. 一个 framing 注:loop 是模态无关的

收尾前埋一个给下一章的引子。你这一章搭的状态机看起来全是围绕文本和工具的,但它其实是模态无关的:把那个感知、推理、动作、再感知、终止的状态机原封不动拿过来,把观测从文本和工具结果换成一张屏幕截图,把动作从工具调用换成点击坐标、键入、滚动,你就得到了一个能操作图形界面的 GUI agent,结构上它就是同一个 agent loop,只是换了一套感知和动作的接口。这件事的边界要先划清楚,免得你以为这一章漏讲了什么:GUI 的动作空间怎么设计(那十几个界面动作、用标记截图还是用无障碍树来表示界面)是 Ch3 把工具设计推广到 GUI 的事;观测用像素还是用结构化界面树这个取舍是 Ch6 的事;而 VLM 怎么从像素里看懂界面、定位那个按钮在哪,是多模态路书的事,不归 Agent 工程。Ch2 只点这一句:循环是模态无关的,同一台状态机换个感知和动作的接口就是 GUI agent,这是 Ch3 会展开的那个把工具设计推广到界面的思路的直接结果。

综合 · 你搭起的第一台机器

回头看你这一章搭起了什么。你拿到了一台状态机当脊柱,四个出口加一个会溢出的计数器,而且你在真实 SDK 源码里看到了它的字面实现,知道了一次 run 不等于一次 LLM 调用、知道了读活跃开源 harness 要先 pin commit。你给这台状态机装上了生死线,两类终止信号必须同时上,语义层给一个干净的完成工具、确定性层用轮次和预算和熔断器兜底,而这条生死线的由来是 2023 年 AutoGPT 那场烧钱。你给它装上了错误恢复,进程内的幂等重试和把错误当 observation 喂回去,以及让它扛住整个进程崩溃的 durable execution,还拿到了能今天就上的最简三件套。你理解了 reasoning model 怎么逼着 harness 多搬运一层状态,两家的契约一个静默降级、一个直接报错。你拿到了一条判断力,能用 workflow 就别上 agent、自主循环是最后手段。最后你认出了长程任务会压垮的两条假设。

把这些零件合起来,你脑子里现在应该有一台完整的机器:中间是那台 think-act-observe 的状态机,外面包着终止护栏、预算护栏、错误恢复层、崩溃恢复层、推理状态搬运层。这台机器就是 harness 这个词的具体形态,也是 Ch1 那个命题 Model + Harness = Agent 里 Harness 那一半第一次变得有血有肉。你从会用 agent 产品往会做 agent 工程迈出了第一步,而且是最关键的一步,因为后面所有章节,工具、安全、上下文、记忆、多 agent,都是往这台机器上继续装零件。

你完成了 Agent 工程路书的第一个真正的工程章节。合上书,去动手:用任意一家的 SDK 搭一个最小的多工具循环,故意不设轮次上限跑一个会绕圈的任务,看着它怎么停不下来,再把 第 2 节 的护栏一条条加上去,亲手感受一下生死线的意思。下次回来,带着这台搭好的状态机进 Ch3。

本章术语速查

下面按本章顺序复习。加粗是术语,后面是一句话解释;带链接的词可点进术语表看更完整的解释(术语表正在逐步为每个词配可视化)。

Loop 架构

  • 状态机 循环每轮按模型输出转移到四个出口:最终答案 / 调工具 / 交接 / 等批准
  • turn 一次 Runner.run 算一个对话 turn,内部可能跑多次 LLM 调用
  • max_turns / MaxTurnsExceeded 轮次硬上限,及超过时抛的异常
  • Harness 包在模型外、让它能可靠干活的整层工程
  • Client SDK vs Agent SDK 你自己写循环 vs harness 替你写
  • 显式图(LangGraph) 把控制流外化成一张有向图
  • blocking vs streaming 同一个底层循环的两种结果投递方式

Termination 与 budget

  • 语义终止 模型自己判断做完了:自然停 / 完成工具 / 明确终态
  • 确定性护栏 系统强制叫停:轮次 / token / 时间 / 成本上限 + 熔断器 + 去重
  • 完成工具(done tool) 无执行函数的专用工具,模型调它=发出停的信号
  • 熔断器(circuit breaker) N 步 / X 美元 / 连续多次错误任一触发就杀掉整个 run
  • 优雅恢复阶梯 卡住时逐级升级:反思 → 压缩重启 → 带部分结果终止

错误恢复与 durable execution

  • 幂等重试 只对无副作用的步骤自动重试加退避
  • 错误作 observation 把工具失败信息回灌循环,让模型自己调整
  • durable execution 每步检查点落库,进程崩溃后从检查点恢复而非从头重跑
  • 日志重放(Temporal / Restate) 崩溃从头重跑、跳过已记录步;LLM 调用须包成可记录步骤
  • 节点落库(LangGraph / DBOS) 每节点完成把状态写库,从最后检查点续
  • 幂等键(idempotency key) 重放有副作用工具前先查它是否已执行,防重复

Reasoning-model loop

  • 推理状态 循环要搬运的不只是消息历史,还有模型私下的推理
  • previous_response_id OpenAI 在服务端接力推理状态的方式
  • thinking block Anthropic 的推理载体,须原样传回否则直接报错
  • budget_tokens / max_tokens 交错思考时前者(整轮思考预算)可超过后者
  • compaction 长循环自动压上下文的平台原语(深入归 Ch6)

判断力

  • workflow vs agent 预定代码路径 vs 模型动态自主,轴是可预测性对自主性
  • 五 pattern 提示链 / 路由 / 并行 / 编排者-工人 / 评估者-优化者
  • simplicity doctrine 能用简单方案就别上 agent,自主循环是最后手段
  • long-horizon 两堵墙 上下文装不下(→Ch6)/ 一次会话跑不完(→Ch12)

进阶词汇(context engineering / multi-agent / trajectory eval 等)归后续章节,完整术语见术语表 /glossary

参考文献

Mode A · 引用出处(正文 inline,集中列在此)

  • Building Effective Agents(Anthropic · 2024)· workflow vs agent 二分 + 五 pattern when-to-use + simplicity doctrine · 本章 第 5 节 主源
  • OpenAI Agents SDK · run.py(openai/openai-agents-pythonwhile True 状态机 + 四支 next-step + MaxTurnsExceeded · 第 1 节 一手源码(引用前 pin commit)
  • Claude Agent SDK overview · Agent SDK vs Client SDK · 第 1.3 节 harness 替你写循环的一手对照
  • Better performance from reasoning models, Responses API(OpenAI Cookbook)· 第 4.2 节 推理片段保留 / previous_response_id / call_id / 缓存 40→80%(厂商口径)
  • Building with extended thinking(Anthropic)· 第 4.3 节 thinking block 必传回(漏则报错)/ budget_tokens / 缓存失效 / beta header 版本演进
  • Effective Harnesses for Long-Running Agents(Anthropic · 2026)· 第 6 节 离散会话 + 两失败模式 + 初始化 agent 解法
  • AutoGPT(2023)· 第 2.3 节 死循环烧钱的 why-now 实证锚
  • DeepSeek Agent Harness 研发 / PM JD(无公开链接 · 收录于 teaching-system/research/agent-engineering/)· harness engineering 作为独立子学科的岗位证据

深 dive 资源(可选 · 想往下走再看)

说明:Agent 工程领域目前没有合格的人讲解系统教学视频(产品演示和框架教程不等于系统讲解),所以本章是纯 Mode A,由原创讲解 + 一手源码/文档/复盘扛起。这一判断和基础路书 Agent 词汇章、以及本路书 Ch1 一致。

下一章

下一章(Ch3)进入第二站的第二块 · 工具生态 + MCP 工程。你这一章把循环搭起来了,但循环要真能干活,得能接得上工具,而且接得又多又好又安全。Ch3 会讲工具是写给模型读的、要专门设计(那个叫 ACI 的思想),会把 MCP 这个你在基础路书听过名字的协议拆开看它怎么解决工具碎片化,还会把工具的概念推广到 GUI 这个 第 7 节 刚埋下引子的方向。