运行时引擎:那个站在天才身后的人
引言
他是一位镇上的钟表匠,全城最灵巧的手都在他的工坊里。他能一眼看穿一座钟的误差来自哪一根弹簧,能在脑海中推演出一百个齿轮咬合的精确时序。但这位钟表匠有一个古老的毛病——他一旦开始工作,就会忘记时间。他会在修一座塔钟时,因为发现齿轮上一个细微的瑕疵,转而把整个机芯拆散重装;他会把一张还没确认尺寸的草图,当成定稿交给学徒去铸造;他会在连续工作十几个小时后,把上一座钟的零件和这一座钟混在一起,而他自己浑然不觉。
镇上的人爱他,也怕他。爱他的天才,怕他的混乱。
后来,他的工坊里来了一位助手。这位助手不会修钟表,看不懂齿轮,甚至不会上发条。但她只做几件事:钟表匠开始工作前,她确认桌上摆的是哪座钟的零件和图纸。钟表匠每做完一个步骤,她在记录本上画一个勾。钟表匠如果连续工作超过四个时辰,她提醒他喝水、看窗外,必要时把当前进度和未完成的步骤写进一张清单。钟表匠说"这个零件可以装了"时,她先拿游标卡尺核对草图上的尺寸,确认无误后,才允许零件进入下一道工序。
最让镇上人惊讶的是,自从这位助手来了之后,钟表匠再也没有送错过零件,也再也没有一座钟因为图纸错误而返工。但人们问她是谁,她只是笑笑。史书里也只写"钟表匠修好了镇上的大钟"。
助手不需要被记住。她的存在,就是让天才不被自己的混乱所困。
TL;DR
运行时不替模型推理,而是守住边界:确认任务上下文、记录状态、验证输出、管理预算、处理错误、跨会话交接。模型负责推演齿轮,运行时负责确保钟表匠每次落手时,手里拿的是对的零件,心里记得的是对的钟。当钟声响起,一切听起来都像是钟表匠独自完成的——而那个"独自完成"的错觉,恰恰说明运行时做得足够好。
目录
- 1. 运行时到底在管什么
- 2. 智能体循环与终止
- 3. 消息、状态与提交边界
- 4. 流式与事件:让工坊外面的人看见里面
- 5. 错误处理:哪些可以交给钟表匠,哪些必须助手自己来
- 6. 长时任务中的漂移与纠正
- 7. Token 预算与上下文策略
- 8. 控制平面:外部世界要有干预权
- 9. 多智能体协作:不是一个人造钟
- 10. 两种设计风格的权衡
- 写在最后
1. 运行时到底在管什么
普通聊天应用的主流程很短:接收消息 → 调用模型 → 返回文本。三步结束,像修一块普通手表。
但 Agent 不一样。它的流程是:接收任务 → 组装上下文 → 调用模型 → 识别行动 → 执行工具 → 接收结果 → 更新状态 → 决定继续或结束。这不是修一块手表,这是造一座钟,可能持续几分钟,也可能持续几小时。
模型只负责根据当前输入提出下一步。运行时负责让这一步真正发生,并决定发生之后该做什么。它从记忆系统取得上下文,请模型生成下一步,通过工具层执行行动,再把结果写回状态。
所以运行时引擎本质上就是 Agent 的执行调度中心。它不亲自推理、搜索、操作文件或管理长期记忆,但它协调这些能力,并管理整个会话的生命周期。
这个中心位置意味着它承担最终控制责任。模型提出行动,但不直接执行。工具层执行行动,但不决定任务何时结束。记忆系统提供上下文,但不驱动循环。运行时引擎把这一切串起来,并在任何环节失控时踩刹车。
2. 智能体循环与终止
Agent 的运行常被套上"思考、行动、观察"的框架。但这容易让人误解,以为循环是模型在独自进行一场内心独白。
实际上,对运行时来说,它只需要识别模型输出中的可执行意图:是文本回复?工具调用?停止请求?还是结构化结果?
现实环境不断提供新信息。工具可能失败,文件内容可能和预期不同,测试结果可能要求改变方案。如果系统只执行预先生成的一条固定链路,任何意外都可能让整条链路作废。循环允许智能体在每次行动之后重新判断:结果是否符合预期?是否需要换工具?是否已经完成?是否应该停止,避免继续消耗资源?
循环让智能体能适应环境变化,运行时则负责划定这种适应的边界——比如什么时候该停,什么时候该报错。
写这段循环代码本身不难,难的是每一行背后的工程规则:状态怎样记录、事件怎样发送、错误怎样分类、工具何时并发、预算何时触发压缩、暂停发生在哪个安全点。
终止条件不能含糊
没有清晰终止条件的循环很容易无限跑下去。"模型似乎做完了"不能成为唯一标准。生产系统需要把停止条件写成可检查、可记录的规则:
- 无工具调用:模型给出最终文本,不再请求行动
- 目标达成:任务检查器确认结果满足要求
- 最大轮数:防止模型持续尝试而无法收敛
- Token 或成本耗尽:剩余预算不足以支持下一轮
- 时间或 API 次数耗尽:避免长时间占用资源
- 用户取消:接收外部停止信号
- 安全终止:权限、风险或 kill switch 触发
- 不可恢复错误:当前状态无法继续执行
这些规则就像工坊里的工序清单和工时沙漏。不是不信任钟表匠,而是知道人在疲劳时判断会变形。
3. 消息、状态与提交边界
在普通聊天中,一条消息通常只有角色和文本。但在 Agent 运行时中,一条消息可能包含多种内容块:文本块、工具调用块、工具结果块、推理状态块、多模态块。工具调用和工具结果必须通过唯一 ID 对应。否则,当一轮产生多个工具调用时,运行时无法判断每个结果属于哪个请求。
在常见模型协议中,assistant 发出工具调用,工具执行结果再以 user 侧内容返回。这里的 user 并不表示真人用户,而表示"来自模型之外的新观察"。运行时必须维持协议要求的顺序,否则下一次模型调用可能无法理解工具结果,甚至直接被 API 拒绝。
状态与消息历史不是一回事
消息历史记录"发生过什么",运行时状态还需要记录"当前处于什么位置"。一个可用的状态通常包括:会话 ID、原始目标与当前子目标、消息历史、当前轮次与执行阶段、已使用 Token 和时间、正在执行或等待中的工具调用、取消/暂停/失败/完成状态、检查点与恢复信息。
只保存消息不保存执行阶段的话,恢复后系统知道过去说过什么,但不知道下一步该继续等工具、重新调模型,还是已经结束。就像助手交接班时只给了零件清单,没给"当前装到第几层齿轮"这个信息。
候选输出与已提交状态
运行时最容易忽略的问题,是模型输出在何时真正"生效"。
模型生成一段文本,只是候选输出(candidate)。这时候如果它推理错了,运行时重试一轮、换一条 prompt,代价很低。但一旦这段输出被写入了消息历史、外部数据库或长期记忆,它就从"一次可以推翻的猜测"变成了已提交状态(committed)。
提交后的错误会级联传播。模型把一次幻觉当作事实写进记忆,后续几十步规划都会基于这个错误地基。它调用一个危险的 API,如果只是停留在工具调用请求阶段,外部验证器可以拦截;但如果调用已经执行、数据库已经被修改,错误就从语言层变成了物理层。
所以运行时的关键职责之一是建立 commit boundary。
- 低风险操作(读取、搜索、计算)可以直接执行,结果作为观察进入下一轮。
- 高风险操作(写文件、改数据库、发邮件、删除资源)应该在验证器或审批点通过后才提交。
- 写消息历史也不是无代价的。一次错误推理一旦被写进对话,后续模型都会把它当作已发生的事实来引用,除非你手动裁剪或重置。
检查点的设计也要考虑这个区别。保存状态不是简单备份聊天记录,而是保存一份"已验证的最小状态集":原始目标、已确认的事实、未完成的任务、关键约束。恢复时从这份状态集出发,而不是让新智能体去猜上一轮的中间产物到底哪些可信。
4. 流式与事件:让工坊外面的人看见里面
流式响应常被理解为"让用户更早看到文字"。但对运行时来说,它还有更重要的作用:在模型输出尚未完全结束时,逐步识别文本块、工具调用块和状态变化。
不过,工具参数可能以多段 JSON 增量到达。运行时可以立即转发文本增量,但必须等到工具参数完整且通过解析后,才能真正执行工具。这就像助手看到钟表匠在画草图,但只画了一半,她不会提前去叫学徒铸造。
运行时内部的关键动作应该转换为事件:agent 开始与结束、turn 开始与结束、文本增量到达、工具调用开始、工具执行完成或失败、Token 预算告警、暂停/恢复/取消、不可恢复错误。
事件让界面、日志、监控、审计和测试不用深入运行时内部,也能知道系统正在发生什么。没有事件流的运行时,就像一个没有计时器的工坊——里面可能正在发生一切,但外面的人一无所知。
并发与背压
多个工具调用只有在互不依赖时才能并发。运行时不能只看"这一轮有多个工具调用"就盲目并发。它还需要考虑:工具是否声明为可并发?是否修改相同资源?是否存在输入输出依赖?权限审批是否已经完成?并发上限是否会压垮外部服务?
如果运行时产生事件的速度超过客户端或日志系统的消费速度,事件会不断堆积并耗尽内存。背压机制通过有界队列、等待、丢弃低价值事件或降采样,让生产速度服从系统承载能力。背压是生产级运行时才需要的能力。实验代码可以省略,但真实系统不能假设消费者永远足够快。
5. 错误处理:哪些可以交给钟表匠,哪些必须助手自己来
不同错误需要不同恢复策略。
工具错误(文件不存在、参数错误、权限拒绝)可以转换成观察,让模型调整方案。这相当于把局部错误变成环境反馈,让循环有机会自己修正。模型尝试读取一个不存在的路径,运行时把"文件不存在"包装为带错误标记的结果,反馈给下一轮。模型随后可以搜索正确路径或向用户询问。
但并非所有错误都适合交给模型处理。认证失败、持续服务故障、越权访问和状态损坏通常需要运行时直接中止或升级处理。就像助手发现齿轮尺寸和图纸不符,她不会去问钟表匠"要不要继续"——她直接拦住。
重试适合处理短暂故障,例如网络抖动、超时和限流。一个可靠重试策略需要:只重试可能恢复的错误、设置最大次数与总时间、使用退避和随机抖动避免同时重试、保证操作幂等、记录每次失败原因、达到阈值后触发断路器或降级。
无限重试不会让失败消失,只会让它更贵。
检查点跨越进程级故障
当任务运行很久时,只在内存中保存状态不够。检查点需要记录恢复执行所需的最小完整信息:原始目标与已验证进度、消息历史或压缩摘要、当前阶段和轮次、已完成/待执行/执行中的行动、资源消耗与权限状态、恢复时需要重新验证的外部条件。
恢复不是简单加载聊天记录。运行时必须重新确认外部世界是否已经变化,以及某个工具调用是否可能执行过但结果尚未落盘。就像助手在交班前不仅要确认记录本已写,还要确认零件已经摆好、图纸已经核对——而不是假设"上一班应该做了"。
6. 长时任务中的漂移与纠正
智能体执行时间越长,越容易偏离原始目标。忘记用户真正要求的交付物、把中间任务误当作最终目标、不断扩大任务范围、重复相同工具调用却没有新信息、在失败路径上持续投入资源——这些都是漂移。
漂移不一定来自模型能力不足。过长的消息历史、含糊的目标、缺少完成标准和错误反馈噪声都会放大漂移。
检测漂移可以从三个层次入手:
- 启发式检查:重复行动、连续失败、长期无进展。成本低,容易落地。
- 目标与约束检查:是否仍满足原始范围、时间和工具限制。结果稳定,可解释。
- 语义或评审检查:当前计划与原始目标是否一致。能发现隐性偏离,但成本更高。
纠正策略从轻到重:重新注入原始目标和完成标准;要求模型总结已验证进展并重新规划;阻止重复或越界行动;回滚到最近检查点;压缩或重置污染严重的上下文;暂停任务,请用户决定下一步。
上下文重置不是简单清空历史。运行时需要保留原始目标、已验证结果、重要约束和失败经验,再从干净上下文重新开始。这相当于助手在钟表匠连续工作四个时辰后,强制要求他休息,同时把交接单写得足够完整,让接替者能无缝上手。
7. Token 预算与上下文策略
一次模型调用需要容纳系统指令、工具 Schema、消息历史、当前用户输入、检索与记忆内容、模型输出空间,以及某些模型的推理预算。如果运行时把窗口全部塞满历史信息,模型将没有空间生成结果。预算管理需要在调用前估算输入,并为输出保留安全余量。
运行时需要管理三类预算:单次调用预算(确保当前请求不超过模型窗口)、单个任务预算(限制一个 Agent Loop 的总 Token、轮数、时间与 API 调用)、系统级预算(控制并发、总成本和外部服务配额)。
上下文不足时常见策略包括:删除低价值旧消息、保留原始目标和关键约束、将较早历史压缩为结构化摘要、把稳定事实写入长期记忆、裁剪过长工具输出仅保留结论、在污染严重时执行上下文重置。
压缩会丢信息,所以最好记录压缩时机、策略和保留内容,否则后续调试很难定位模型为什么"忘记"了某个事实。
压缩与重置是两种策略
上下文满了通常有两条路:compaction 和 reset。
Compaction 是把早期对话摘要化,替换原文,让同一智能体继续。它保留连续性,但有个问题:模型接近上下文上限时会产生"上下文焦虑"(context anxiety),开始过早收尾、回避复杂推理。Compaction 不能消除这种焦虑,因为智能体仍然背着同一个历史包袱。
Reset 是彻底清空上下文,让新智能体从一份结构化 handoff artifact 重新开始。它提供干净 slate,但代价是 handoff 必须足够完整。一个可靠的 handoff 至少包含:原始目标、已验证进度、未完成任务清单、关键约束、失败经验。
两种策略适合不同场景。短程任务、调试交互通常用 compaction,因为保持连续性更重要。长程开发、多 session 编码更适合 reset + handoff,因为每个新 session 都需要快速知道"我现在该做什么",而不是从几万字的压缩对话里去推断。
模型能力也在改变这个选择。Anthropic 的实践显示,Sonnet 4.5 在长程任务上几乎依赖 reset,而 Opus 4.6 的能力提升让某些原本 load-bearing 的 reset 可以去掉,成本和延迟都下降了。这说明运行时不是越复杂越好,而是需要定期审视:每个控制层的存在假设,是否已经被模型能力覆盖。
8. 控制平面:外部世界要有干预权
运行时不能只顾自己跑,还得让外部系统安全地干预。控制平面提供的能力包括:查看当前状态与资源消耗、实时订阅行动事件、暂停/恢复/取消任务、在高风险工具执行前请求审批、从检查点恢复、修改允许的预算或范围、触发 kill switch。
如果系统在文件写入一半、数据库事务未提交或工具执行结果尚未记录时强行暂停,恢复后很容易出现不一致。所以运行时需要定义安全点:调用模型之前、模型响应完整解析之后、工具调用之前、工具结果已经持久化之后、一个 Turn 结束之后。暂停请求可以随时到达,但运行时应在下一个安全点真正进入暂停状态。
生产系统常在固定位置开放干预点:模型调用前检查上下文和预算、工具调用前做权限审批、工具调用后做输出治理、状态提交前做审计、任务边界检查是否继续。这样,权限、安全、成本和人工审批可以作为独立策略接入,而不必把所有规则塞进主循环。
9. 多智能体协作:不是一个人造钟
前面的章节把运行时看作一个单智能体循环。但复杂任务往往需要多个智能体分工,而且任务可能跨越多个上下文窗口。
单循环在简单任务上够用。但复杂任务中,planner 的宏观视角和 executor 的微观执行会互相干扰;generator 自我评估时又有 leniency bias,倾向于给自己的工作好评。把不同职责拆给不同智能体,能避免这些结构性冲突。
典型分工:
- Planner:把模糊目标展开为可执行的功能清单和技术方案。
- Generator:按清单逐项实现,每个 sprint 只做增量。
- Evaluator:用外部工具验证结果,提供具体反馈而非笼统评价。
Evaluator 不是可选的组件。模型自我评估时几乎总是偏宽松——就像钟表匠很少会说自己的零件有问题。外部验证是运行时的重要防线。feature list 的 pass/fail 状态应由外部验证决定,而非模型自评。
跨会话的冷启动
长程任务的真正困难不是单个循环内的推理,而是跨会话恢复。每个新 session 开始时,智能体没有上一轮的记忆,相当于一个新学徒面对陌生的零件箱。
Initializer agent 的思路是在第一个 session 结束时,留下一套 handoff artifact:
feature_list.json:结构化功能清单,每个功能带 pass/fail 状态。claude-progress.txt:进度日志,记录已做和待做。init.sh:环境启动脚本,让下一个 session 直接跑起来。- git 历史:带描述的 commit,说明每段代码的来历。
这比把对话历史压缩后传给新智能体更可靠。压缩后的摘要容易丢失关键约束、误判任务范围,而结构化的 handoff 文件让新 session 能在几十秒内 get up to speed。
新 session 的启动例行不应直接开始工作,而是:确认工作目录、读取进度日志和功能清单、查看最近修改、启动开发服务器并跑基础 E2E 测试、确认基本功能正常后,再开始新任务。这套流程的目的不是浪费时间,而是防止"在已有 bug 上堆新代码"。
多智能体运行时需要在 handoff 边界做几件事:确保当前智能体把进度写成结构化文件、给下一个智能体准备最小但完整的状态集、在切换时重新验证外部世界状态、记录交接日志用于后续审计。这不是可选的优化,而是长程任务可靠性的底线。没有 handoff 的运行时,本质上是把跨 session 的恢复责任推给下一个智能体去猜。
10. 两种设计风格的权衡
运行时设计通常有两种取向。
异步、事件驱动风格重视低延迟、流式反馈和并发执行。适合交互式 Agent、并发工具、需要实时 UI 的场景。代价是事件顺序和并发状态更难调试,恢复时必须准确重建未完成任务状态。
线性、阶段化流水线风格把 intake、上下文组装、推理、工具执行和持久化按明确顺序执行。适合重视确定性和完整审计的长时任务、每个阶段都需要保存结果、工具之间存在较强依赖的场景。代价是首次响应和整体延迟可能较高,用户实时交互体验较弱。
成熟系统往往混合两者:对外使用事件流提供实时体验,对内使用明确阶段、状态机和检查点保证确定性。
运行时不是越复杂越好
每个 wrapper、reset、verifier、planner、memory rule、permission gate,本质上都代表一个假设:模型自己做不好这件事,所以我在外面加一层控制。但如果模型能力变了,这些控制可能就不再必要,甚至会拖后腿。
Anthropic 从 Opus 4.5 到 4.6 的演进中,sprint construct、context reset 等原本 load-bearing 的组件被逐步移除。好的运行时不仅会加控制,还要知道什么时候删控制。这是工程中最难的部分:承认某个你精心设计的机制,可能只是因为模型还不够强而存在。模型变强后,你要有勇气拆掉它。
写在最后
很多年后,钟表匠老了,收起了放大镜。最后一位学徒问他:"师父,您修了一辈子钟,到底有什么秘诀?"
钟表匠沉默了很久,望向工坊角落里那个正在整理记录本的身影。他说:"我只会做齿轮。但那个女人,她确保我每次落手的时候,手里拿的是对的齿轮,心里记得的是对的钟。她从不替我思考,也从不在报时声里加上自己的名字。你们从来没有见过她,因为她站在我身后。"
学徒顺着师父的目光看过去,那里只有一张旧工作台,和几本翻烂的记录本。助手已经走了,但她留下的一切还在运转。
钟表匠顿了顿,又说:"如果有一天,你也能做出一个人人都称赞的钟,而所有人都以为是你一个人完成的——那说明,你也终于拥有了一个好助手。"
运行时不为模型增光,也不为模型抢功。它只是在模型可能迷失的地方,默默标记出边界。然后当钟声响起,一切听起来都像是钟表匠独自完成的。
而那个"独自完成"的错觉,恰恰说明运行时做得足够好。
本文整理自 Harness 核心子系统设计与多篇行业实践的综述,包含 Anthropic 的长程 Agent 框架、CMU/Yale 的 Harness Engineering 综述、以及 Datawhale 的 State-Aware Runtime 思考。