运行框架
运行框架是模型周围的运行时层:它把模型输出转成工具调用,再把工具结果转回模型输入,负责管理上下文窗口、路由工具、沙箱化执行,并暴露出一组动词,让人能够暂停、重定向、分叉,或终止一个运行中的循环。一个画成流程图时很普通的循环,一旦进入生产环境就需要这些机制;这套机制的形状,对评估分数的影响也不亚于一次模型更换。
一个不肯保持简单的循环
契约本身很小。创建一个绑定到智能体定义的会话。驱动它的循环:把上下文送给模型,分发它返回的工具调用,追加结果,如此重复,直到完成或被中断。崩溃或等待之后能恢复。能在半途中断。能在某个检查点分叉。画成流程图,这个循环就是一个方框,加一条指回自身的箭头。
可一旦接触到生产环境,它就不再平常。某个工具跨回合握着一个连接,于是循环在步与步之间不再无状态。上下文窗口填满,于是循环必须决定遗忘什么。用户在一条沙箱命令执行到一半时按下取消,于是循环必须停掉某个它并不直接掌控的东西。两个副本都以为自己拥有同一个被调度的滴答,于是循环必须与一个看不见的自身副本协调。运行框架,正是这些摩擦要么被吸收、要么外泄出去的地方。它掌管的是机制,而非策略:它不决定谁可以调用(身份,第 56 章),不决定模型从哪里来(模型接入),也不决定哪些动作需要人介入(监督,第 55 章)。它决定的是这些决策如何被落实,而其中最重要的机制,就是停掉循环的能力。
只画抽象结构时,循环就是一个方框加一条回边。图 41.2 补上的,是这些生产摩擦各自作用在循环的哪一处:四个听起来互不相干的问题,落在同一个结构上的四个不同位置。
吸收这些摩擦的,是接下来的六个机制。每一个都始于一个简单版本,在负载下于某个特定的点失效,然后获得了那个点恰好要求的机制。这六个机制连起来就是运行框架:实例从哪里来,取消如何抵达工作,谁被允许运行,锁究竟保护什么,一个填满的上下文窗口里什么得以存活,以及模型如何得知一个工具的存在。每一个都有产生它的演化线索,也有权衡开始吃力的拐点,最后以无人值守长时间运行所需的运维加固收尾。
实例从哪里来
第一个决定,是一份智能体定义,一段提示加一组工具加一个模型绑定,在何时变成一个活的实例。最直接的起点是单例:一个运行器在所有会话间共享,仅以一个 session_id 相互隔离,每次请求零分配。隐藏的代价不是性能,而是发布粒度。用单例,提示词是部署期的常量,于是一个坏提示会拖垮 100% 的会话,直到回滚完成。OpenAI 的 GPT-4o 谄媚事件就是这种形状:更新在 2025 年 4 月 25 日推出,回滚在 4 月 28 日开始,完整撤销大约花了一天 (OpenAI 2025)。运行时注册表改为按每次请求解析一个提示版本,这让一次回归只触及金丝雀切片,而稳定版本继续服务其余所有人。爆炸半径如今等于金丝雀,而非整个机队。代价是多一次解析跳转、一个跨副本的一致性问题,以及每次事故里多出的一个变量。
拐点在于它何时不再可选。单例在一两个智能体类型上没问题,过了三四个就开始吃力,因为一个智能体里的提示回归会逼出一次回滚,把另一个智能体里一处无关的修复也一并撤销,只因它们共享了一次部署。那次共享部署的事故,就是版本化不再只是锦上添花的时刻,因为不去做它的代价,已经变成了没人动过的代码里的一次回归。
取消如何抵达工作
取消能多快生效,取决于级联里最不配合的那一跳。运行框架可以即刻看见一个中断标志,但这个标志必须一路向下传到每一个可能正在执行中的层:模型调用必须遵守它的上下文截止时间,工具分发层必须把取消传播进工具,沙箱命令必须尊重 SIGTERM,而一次有状态的协议调用必须尊重 RPC 级的取消。一个无视截止时间的模型调用,会在取消之后继续占着会话数十秒。一个无视 SIGTERM 的沙箱命令,会占满整个宽限期。所以「运行时支持取消」是一句关于级联中每一跳的断言,单单一个不配合的层,就能让取消形同虚设。这就是为什么中断是一个原语而不是一个特性:人在回路审批所依赖的那次暂停,要么穿过每一跳真正生效,要么就只是表面动作。哪些动作需要审批,这个决定属于监督;让审批得以发生的那次暂停,是运行时的动词,而那些把它作为一等中断暴露出来的框架,让那次暂停变得可检视,而非隐式 (LangChain 2025)。图 41.3 追踪了这条级联,以及每一个不配合的跳所添加的延迟。
试着把某一跳的 obeys 设为 False,看停止耗时跳到那一跳的宽限期:取消只能和它最盲目的那一层一样快。
# 每一跳要么快速遵守中断,要么无视它、只在自己的宽限期到期时才停止。
# 停止耗时取决于最慢的那一跳。
hops = [
{"name": "model call", "obeys": True, "grace_s": 30.0},
{"name": "tool dispatch","obeys": True, "grace_s": 5.0},
{"name": "sandbox cmd", "obeys": False, "grace_s": 10.0}, # 无视 SIGTERM
{"name": "RPC call", "obeys": True, "grace_s": 8.0},
]
flag_latency = 0.01 # 运行框架几乎即刻设置中断标志
stop_times = [flag_latency if h["obeys"] else h["grace_s"] for h in hops]
for h, t in zip(hops, stop_times):
print(f"{h['name']:14s} stops after {t:6.2f}s")
print(f"\ntime-to-stop = max = {max(stop_times):.2f}s "
f"(bounded by the most-blind hop)")
这个动词有四种形状,而人的控制质量,受限于运行框架提供的是哪一种。硬取消是不保留状态的 kill(session),简单而粗暴,让每一次纠正都成为一次丢失上下文的事件。协作式暂停在步边界检查一个中断标志,并把状态序列化下来。队列式引导把用户输入追加到下一回合而不暂停,很快,却可能在看见这条消息之前就交付了一个陈旧的决定。定点恢复把引导与一次回退结合起来。一次审批所依赖的暂停,恰好与运行时实现的这几种里最强的那一种一样好,不会更好。
谁被允许运行
当循环按调度运行时,每个副本都跑自己的调度器,于是没有协调,每个副本都会触发每个滴答。通常的修正办法是一个在运行前获取的智能体级分布式锁。它能防止两个副本并发触发同一个滴答,却并不能让滴答的副作用恰好一次。如果一个滴答发了 Slack、开了工单,然后在记录「完成」之前崩溃,恢复会重放它,于是 Slack 消息发了两次。锁协调的是谁来运行;恰好一次要求协调的是外部世界已经发生了什么。在副作用调用点上的幂等键弥合了这道缝隙,而那是一种不同的保证,只有一把锁的调度器并不拥有它。
锁防的是重叠,不是重复,而锁的高度不对,还有第二种、更尖锐的表现。运行框架几乎总有会话级锁,它阻止两个 Run() 调用同时改动同一个会话,却对同一个智能体的两个不同会话触碰同一个外部资源什么都没说。两个编程会话向同一个分支推送相互冲突的提交。一个用户会话和一个被调度的滴答指向同一个外部状态,彼此都不知道对方存在。锁在运行框架里;冲突在资源处,而运行框架的锁够不着它。图 41.4 直接展示了这种错位。
绿色的每个会话都被单独加锁;红色的每个共享资源都未加锁。修法在资源处:每会话一个工作树(worktree),把冲突挪到一次合并里;或一个以被命名资源为键的资源级锁;或在外部系统上做乐观式调和。这种相撞并非假想;编程智能体的拉取请求里的合并冲突,如今频繁到足以构成一个带标注的数据集 (Ogenrwot and Businge 2026)。正确的选择取决于下游系统是否已有自己的并发控制可以倚靠。Git 的引用更新与数据库的行锁是真正的协调原语;它们存在的地方,运行框架可以让位于它们。
调度器本身爬的是同一条压力曲线。进程内与外部 cron 调度器,一旦某个滴答需要带外部副作用的多步编排,就让位给持久执行框架(Temporal、Inngest、Restate),因为概率性的模型行为让基础重试不够用,而那正是持久日志为之而建的情形。会话在滴答之间是保持持久还是临时,是一个有自身拐点的平行选择。持久会话累积上下文,于是一个监控智能体记得上周的基线,但事件无界增长,这让上下文管理成为必需。临时会话每个滴答都是全新的,当每个滴答彼此独立时正确。给无状态作业选持久,是无谓地引入无界存储;给需要记忆的作业选临时,是每个滴答都把记忆扔掉。
一个填满的窗口里什么得以存活
一个长时间运行的循环会遇到上下文窗口上限,于是运行框架必须回答两个不会自己回答的问题:什么被保留,以及谁来决定。自动压实把较早的回合总结成压缩形式,让会话越过硬上限继续跑,它的失效模式是结构性的:总结丢弃细节,而智能体对自己丢了什么毫无信号。Anthropic 的 cookbook 测出压实保留了三个高层事实中的三个,以及三个冷僻细节中的零个 (Anthropic 2026)。替代方案是外置化,智能体把它想保留的东西写到文件(progress.md、plan.md),并在压实之后重新加载。这用智能体对自身未来需求的盲目,换掉了总结的盲目。两者都不是机械的,这正是下面争议框的主题。
「直接用一个更大的窗口」是诱人的第三个选项,它挪走故障,而非消除它。更大的上下文窗口降低了压实频率,却不消除退化:即便检索完美,准确率也随输入长度增长而下降 (Du et al. 2025),而同样的退化在各种现实检索设定下也都会出现 (Hong et al. 2025)。「把一切都放进去」只是把故障从「上下文溢出」挪成「上下文已满而模型悄悄变差」。
长会话的上下文管理能否被做成机械的,尚无定论,而证据说它目前还不能。Lindenbauer 等表明,仅用占位符遮蔽旧工具输出,在五种设定中的四种里,以最低成本就与 LLM 总结在解决率上打平,且总结会导致轨迹延长:智能体比最优多坚持 13 到 15%,因为总结遮蔽了本该告诉它们停下的失败信号 (Lindenbauer et al. 2025)。确有帮助的压实工作,比如面向长跨度任务的学习式上下文压实 (Kang et al. 2025),仍把核心问题悬而未决。更大的窗口也不解决问题,因为即便在最简条件与完美检索下,准确率也随长度退化 (Du et al. 2025)。长会话质量倚赖三件运行时无法机械保证的事:模型在被要求时写出一份好总结,模型在重新加载时解读那份总结,以及运行时去强制执行一种外置化模式,而不是寄望于智能体自己维持一种。运行时能在正确的时刻触发压实并重新加载正确的文件。它无法让总结为真。
模型如何得知一个工具的存在
分发是工具如何运行;注册表是模型如何得知工具存在。一个静态扁平列表把每个工具都写进每条系统提示,大约在 30 到 50 个工具时就用尽了,因为函数调用准确率随目录增长而下降,而当无关工具在场时鲁棒性尤其受损 (Yan et al. 2025)。越过这个点,运行时就需要每轮选择。带安全色彩的故障最为尖锐:目录层一段恶意的工具描述,能通过系统提示把指令注入模型,完全绕过用户提示(Invariant Labs 的 mcp-injection-experiments)。注册表不是一张中立的查找表,这正是为什么目录准入是一个交给 第 56 章 的信任决定。注册表准入到目录里的东西,就是它准入到模型指令里的东西。
选择演化成一道阶梯,每一级提高目录上限,并添加一个运行时随后必须掌管的代价。静态扁平列表让位给动态工具 RAG,后者每轮只检索与当前回合相关的工具(把工具模式嵌入,再取 top-k 个匹配项),扩展到数千个工具,代价是一个有自身错误模式的检索层。带挂载和卸载的分层命名空间按阶段展示一个修剪过的子集,在理解问题时给只读工具、在修复时给写工具,这要求运行时跟踪阶段。模型驱动的发现给模型一个 list_available_tools 工具让它自己问,这提高了上限,却让发现质量成为一个运行时并不拥有的模型能力的函数。图 41.5 把这四级摆在各自获得的扩展上限与添加的代价旁边。
有状态工具留下的运行时负担
在这套设计里,工具按有状态性分成几类。无状态工具(一次网页抓取、一次给定沙箱 ID 的沙箱执行)不持有运行框架侧的状态,可平凡地扩展。有状态连接工具(MCP、gRPC 流)在一个运行框架为会话握着的连接里携带协商好的能力与游标,把它们与无状态调用一起通过同一个机制分发,会误处理其中一类或另一类。最难的变体是把两者混在一起的单个逻辑操作,因为它的部分失败空间是两者的乘积。
有状态的工具传输有一条很清晰的演化线索。最简单的设计按副本把连接存在内存里,以 (session_id, server_url) 为键,惰性打开。它一直管用,直到平台横向扩展,这时有状态性就和负载均衡器发生冲突:MCP 2026 路线图把有状态会话点名为首要的扩展瓶颈 (Model Context Protocol 2026)。具体的故障是重连风暴。当一个持有 N 个会话(每个会话各连着 M 个服务器)的副本死掉时,恢复是对幸存基础设施的 N 乘以 M 次同时重连,每次都重新协商能力并丢掉游标状态。机制决定了缓解办法:会话亲和把一个连接保持在一个副本上,带抖动的重连退避把重连在时间上错开,可恢复流让重连变得廉价。MCP 规范直接顺着这条曲线走,弃用 HTTP+SSE 转向 Streamable HTTP,在那里一次重连用 Mcp-Session-Id 加 Last-Event-ID 从一个游标重放,而不是重新开始 (Model Context Protocol 2025; Model Context Protocol 2025)。规范还加固了一个连接池本身触不到的相关隐患:当同一个用户的若干并发会话争着刷新一个单次使用的 OAuth 令牌时,输的一方递上一个已被消费的令牌并失败,于是 2025-06-18 修订版要求客户端必须实现 RFC 8707 的资源指示符,使一个令牌不能被对错误的服务器兑现 (Campbell et al. 2020; Model Context Protocol 2025)。2026 年的规范工作把这条演化线索走到了终点:2026-07-28 候选版本彻底移除了协议级会话,去掉 Mcp-Session-Id(SEP-2567)和 initialize 握手(SEP-2575),于是任何请求都可以打到任何副本;需要跨调用保存状态的服务器改为签发一个显式句柄,由模型作为普通参数传回 (Model Context Protocol 2026)。这条弧线从长连接走到可恢复流,再走到协议里不再有会话。
多智能体组合有一条平行的弧线,OpenAI 实验性的 Swarm 被 Agents SDK 取代,但组合本身属于 第 43 章,运行框架只掌管被组合的智能体是共享还是隔离会话与沙箱状态。那个共享的选择是它自己的权衡,带着一个被测量过的代价。共享会话与沙箱成本最低,代价是竞态与提示污染。完整的沙箱隔离是最费铺设工夫的,也是最强的隔离:Geng 和 Neubig 在 Commit0-Lite 上测出工作树隔离为 59.1%,对软的指令级隔离 56.1%,并显示软隔离在 PaperBench 上跌到单智能体基线之下 (Geng and Neubig 2026)。指令级约束是劝告性的;真正的隔离不依赖模型的遵从。
为长时间运行加固
循环本身很小。围绕它的机制才是这里一直在讲的,而最后一部分集中在少数几个运维选择里,这些选择决定了循环能否承受无人值守的长时间运行。
锁有两个来自分布式系统文献的常见陷阱。基于 Redis 的锁需要栅栏令牌才能在网络分区下安全(Kleppmann 对 Redlock 的批评)(Kleppmann 2016)。而在 PgBouncer 事务池化下,pg_advisory_lock 会在连接归还到池时释放,所以一个期望会话级锁的调用方必须用 pg_advisory_xact_lock 或一个专用的会话池,否则锁会在滴答执行到一半时蒸发。
给每个会话都加一个挂钟超时,不论它由什么触发,这个上限即便在词元计数出错或滞后时也仍然成立,因为挂钟时间运行框架能在本地测量,无需信任任何下游计量表。它之所以关键,证据很刺眼:一次 Claude Code 递归在五小时里消耗了大约 16.7 亿个词元 (anthropics/claude-code 2025);一份从业者的事后复盘则报告,一条 LangChain 流水线里的两个智能体经由智能体间通信(A2A,第 43 章)来回循环了十一天,产生了大约 $47,000 的账单,两个智能体都没有预算上限 (Waxell 2025)。
准入控制属于运行框架层。调用 Kubernetes API 的会话创建,请求的是一个沙箱,而不是创建一个,而危险的故障不是拒绝,而是一次挂起:Pod 处于 Pending,到客户端的流永远不开始,没有错误可以浮现,因为什么都没出错。在向客户端许诺一个会话之前先检查容量,把一个缓慢无声的「什么都没有」变成一个快速而清晰的「不行」,让客户端可以据此重试。
其余的控制遵循同样的机械推理。抖动防止许多智能体共享一个调度时同时触发。断路器在 N 次连续失败后停用一个智能体,使它不能无限消耗词元:在 2025 年 12 月的 AWS Cost Explorer 故障中,一个内部编码智能体删除并重建了生产环境;AWS 把根因归于配置错误的角色,媒体报道则归于智能体的自主行为,但无论按哪种说法,权限都过宽,也没有断路器起作用 (Singh 2025; Schuman 2026)。而启动时的自愈会重放那些结束从未被记录的滴答,这之所以安全,只因上面的幂等键吸收了重放。从运行框架看,沙箱化本身只负责很薄的一层:它为正确性掌管工作树隔离,为容量掌管准入控制,其余的归沙箱原语掌管,包括把高价值密钥挡在盒子外,靠受限令牌兑换或出网替换,让生成代码既读不到、也外泄不了它们(第 56 章)。
运行框架之所以重要,是因为智能体循环在这里变得可运维。它提供能力,靠的是让循环在有状态工具与被组合的智能体之间全速运行。它为效率付费,靠的是上下文管理与准入控制,让一个失控循环不至于烧光预算。而它赢得信任只在一处:人对一个运行中智能体的权威,恰好等于运行时暂停它的能力,而这种能力要么从一开始就设计进去,要么根本不会有。
运行框架对评估分数的影响,不亚于模型本身。一个基准数字,是一个模型在一个运行框架内部运行所产生的,而 Berkeley RDI 表明,顶尖的智能体基准可以通过利用运行框架本身来作弊,从 git log 里读答案,或对评分器做奖励欺骗,多个基准在智能体根本没解开任务的情况下就逼近 100% (Berkeley RDI 2025)。漏洞住在运行框架里,而非任务里。后果向上影响 第 47 章 与 第 52 章:一个分数是模型与其周围运行时的联合性质,所以一次运行框架的改动就是一次测量的改动,评估套件必须加固运行框架,而不只是留出一份私有答案册。一个留出集防的是记住答案;它对一个读评分器的智能体毫无办法。
延伸阅读
被引用的来源会出现在本书的参考文献页。下面这一项仅在正文里作为出处被提及,为想要原始材料的读者列在此处。
- Invariant Labs, “mcp-injection-experiments,” 2025. github.com该 GitHub 仓库提供代码示例,用于复现针对 AI 智能体的 MCP(模型上下文协议)工具投毒与提示注入攻击。
评论
登录后评论