AI 基建
0%
第六部分 · 编排 · 第 41 章

运行框架

作者Changkun Ou
阅读时长约 14 分钟

模型可以建议下一步做什么,却无法独自把建议变成有边界、可观察的工作。完成这件事的是模型周围的运行框架。它负责整理模型输入、记录进度、分发工具、等待审批、执行限制,并让人能够可靠地暂停或停止一次运行。

本章建立在一条边界之上:模型负责提出建议,运行框架负责排序并记录;策略与身份系统决定权限;工具和外部系统承载实际影响。运行框架可以执行一项决定,却不会授予权限;它可以协调多次尝试,却无法保证外部影响恰好发生一次。只有守住这些边界,才不会把一个方便的智能体循环误当成事务管理器或安全主体。

运行时契约

最小的运行框架只重复三项操作:询问模型、执行获准的工具调用、追加结果。生产运行时还必须回答这个循环没有回答的问题:

关注点 运行框架必须公开的契约
身份 这一步由哪位用户、哪个租户、哪份智能体定义和哪次运行产生?
状态 哪些内容已持久接收,哪些工作仍未完成?
权限 此刻究竟是哪项策略和哪次审批授权了这个动作?
外部影响 外部操作已经提交、拒绝、重复执行,还是结果未知?
控制 暂停、引导、取消、强制终止和分叉分别在哪里生效?
限制 还剩多少时间、成本、调用次数、字节数、变更次数和并发额度?
恢复 可重放哪些工作,哪些工作必须先核对外部结果?

可以把这份契约写成一个带版本的状态转换函数:

(St+1,Ct)=δv(St,et).(S_{t+1}, C_t) = \delta_v(S_t, e_t).

其中,e_t 是一项已接收事件,可以是用户命令、模型结果、工具结果、审批决定、计时器事件或恢复时的观察结果。C_t 是这次转换发出的命令集合。状态 S_t = (r, h, w, m, p, k, b, q) 包含:

  • r:运行身份、状态和智能体定义版本;
  • h:持久历史的头部;
  • w:工作区或沙箱引用;
  • m:应用记忆视图及其版本;
  • p:安全主体、策略快照和审批记录;
  • k:已经准入的工具目录版本;
  • b:剩余预算和已预留预算;
  • q:尚未完成的工作、尝试、租约和未解决影响。

归约器带有版本,因为恢复旧历史时,必须沿用产生这段历史时的语义。即使模型和工具的输出不确定,归约器处理已接收事件的结果也应该确定。由此得到四条不变量:

  1. 终态运行不会发出新命令。
  2. 每条外部操作命令都要记录行为者、范围、审批、稳定步骤 ID、尝试次数、幂等键和输入指纹。
  3. 预算只能通过明确授权的追加而增加。
  4. 同一条尝试血缘只接收一次完成结果;迟到或重复的结果会保留为证据,却不能悄悄推动状态前进两次。

这份事件历史是重启和审计的事实来源。检查点只是优化手段,不是第二份真相。它记录一个经过验证的历史位置,以及继续运行所需的版本、待处理操作 ID、截止时间、工作区引用和已知的外部影响结果。

运行是状态机,不是递归函数

下面的状态有意写得很明确。客户端收到 pausedwaiting_approval 时,不应该从输出流突然没有新词元来猜测发生了什么。同样,needs_reconciliation 是可见状态,而不是一个诱导系统盲目重试的普通失败。

状态 含义
created 已记录身份、定义和初始预算。
ready 准入成功,没有正在运行的子操作。
running 归约器正在处理一项已接收事件。
waiting_model 正在等待模型,界面可以明确显示“等待模型”。
waiting_tool 正在等待工具,具体调用和截止时间都可见。
waiting_approval 正在等待审批,尚未分发外部操作。
paused 不再调度新工作,可恢复状态仍然持久保存。
cancelling 已接收取消请求,正在停止活动子操作。
completed / failed 运行成功终止,或以已知失败终止。
needs_reconciliation 外部结果未知或相互矛盾,需要核对。
C 已创建 R 就绪 C->R X 运行中 R->X W 等待态 等模型 等工具 等审批 暂停 X->W K 取消中 X->K Q 待核对 X->Q D 完成 / 失败 X->D W->X K->D Q->X 已核对
图 41.1. “运行框架的状态机。等待和结果核对都是明确状态,不是输出流里的空白。”

转换日志必须先于命令交付完成提交。这样,工作进程即使崩溃,也不会抹掉调用工具的原因。不过,这个顺序无法消除经典的不确定窗口:外部系统可能已经提交写入,工作进程却在记录回执前崩溃。因此,恢复逻辑必须理解外部影响,不能只会重放函数。

固定定义版本,明确适配协议

实例生命周期与定义版本是两个独立选择。共享运行器可以为每次运行解析不可变配置;刚刚分配的运行器也可能载入一份没有版本的提示词。每次运行都应该固定到一个智能体定义版本,其中包含指令、模型路由、工具模式、策略引用和运行框架归约器版本。每个检查点也要记录该版本。发布策略再把新运行路由到金丝雀版本或稳定版本,并明确现有运行是继续固定原版本、在安全边界迁移,还是停止。

不同提供商的协议不能互换。兼容适配器应该公布功能矩阵,覆盖工具调用、结构化输出、流式传输、取消、用量数据、续接标识和错误语义。某项能力无法表示时,就明确失败或声明降级,不能把“请求已接收”悄悄解释成功能对等。适配器和提供商版本也要与智能体定义一起固定,评估或事故调查才能复现真实调用路径。

控制动词各有含义

暂停、恢复、引导、取消、强制终止和分叉,是不同能力,不是同一种中断由弱到强的不同档位:

  • 暂停会在写明的安全点停止调度。活动调用可以完成、被取消,或继续列为未解决。
  • 恢复会在定义和策略版本兼容时继续已暂停状态。如果版本发生变化,迁移必须是一项明确的状态转换。
  • 引导会把用户命令追加到持久历史。契约必须说明它在下一次模型调用前生效,还是等当前工作完成后再生效。
  • 取消会记录 cancel_requested,拒绝新工作,并把请求传播给活动子操作。协作式代码必须主动观察取消;仅仅取消一次 RPC,并不能中断服务器上的任意代码 (gRPC Authors 2024)。
  • 强制终止会直接结束运行框架拥有的执行单元,例如进程组或沙箱。它是一条升级路径,不是回滚机制。
  • 分叉会从检查点创建新的运行命名空间,隔离可变状态,并使用新的幂等范围。已经提交的外部影响仍然是双方共享的现实。

取消有两个重要里程碑。请求已经持久记录,且不再调度新命令时,取消得到已确认;运行框架拥有的所有子操作都已停止或标为未解决时,系统才归于静止。运行框架停止等待以后,远程工作仍可能继续;取消也无法撤销已经提交的外部影响。对于结果未知的操作,运行时必须核对、补偿,或交给操作人员决定。Temporal 也区分优雅取消和强制终止,并指出远程活动必须通过协作式心跳才能收到取消通知 (Temporal Technologies 2026)。

审批也是一种中断,但还要满足完整性要求。审批界面必须展示确切工具、目标、参数或差异、执行身份、预期影响和成本。决定要绑定到唯一调用 ID 和不可变载荷哈希,并且有范围、会过期且只能使用一次。实际分发前必须再次执行授权与输入防护。如果暂停期间载荷或相关策略发生变化,就必须重新审批。可序列化的暂停与恢复流程已经展示了这种机制,包括由嵌套智能体发起的审批 (OpenAI 2026)。但审批流程不能取代执行授权。

持久工作、重复交付与外部影响

可恢复的运行框架会把持久历史独立于提示词上下文保存。工作进程从队列消费命令,通过有界租约取得命令,并以稳定步骤 ID 和尝试次数写回结果。租约只能限制并发所有权,不能证明上一位持有者已经停止。对于正确性关键资源,应附加单调递增的栅栏令牌,并要求每条写入路径拒绝陈旧令牌。接收端不验证,令牌就只是元数据。

调度也采用同一套模型。给每次触发一个稳定的调度标识,例如 (schedule_id, scheduled_at),再通过唯一的持久记录认领它。即使如此,仍可能重复交付和重复执行。Kubernetes 文档说明,即使 Job 只要求成功完成一次,同一程序也可能启动两次 (Kubernetes Authors 2026)。调度器租约在有效期内可以防止重叠,却不能保证外部影响恰好一次。

外部操作能否安全重试,取决于接收端:

操作 重试规则
只读或已经证明幂等 使用有界指数退避并加入抖动。
由接收端幂等键约束的写入 重用同一幂等键;同一键对应不同输入指纹时必须拒绝。
写入结果未知 重试前先查询状态并核对;无法查询时,执行补偿或要求人工检查。
身份验证、授权、校验或业务规则失败 通常不可重试,应直接呈现失败。

错误类别、下次尝试时间、绝对截止时间和重试预算都要持久保存,避免重启重置策略。自动重试之所以有用,正是因为操作可能暂时失败;但同一活动也可能执行多次,所以必须按这个现实设计 (Temporal Technologies 2026)。

锁也应该放在它真正保护的资源上。会话锁只能串行化一段对话,保护不了被另一次运行触碰的分支、客户账户或部署。资源级并发控制可以采用条件写入、数据库约束、受保护分支、合并队列,或以实际资源为键的锁。Git 工作树只隔离本地文件,既不能串行化远程推送,也不能把 Git 变成事务系统。

工具是有类型、受授权的外部操作边界

只有名称和 JSON 输入模式还不够。运行框架需要一份契约,用它驱动准入、审批、执行、重试和结果处理:

ToolSpec {
  name, input_schema, output_schema, side_effect_class,
  auth_scopes, approval_rule, timeout, retry_class,
  idempotency, max_output_bytes, sandbox_profile
}

ToolResult {
  status, payload | artifact_ref, receipt,
  error_class, observed_at
}

四道门必须彼此独立:

  1. 目录准入决定一项工具定义和实现是否足够可信,可以注册。
  2. 逐轮暴露为模型选择最少且相关的候选工具。检索可以改善选择,不能授予权限。
  3. 执行授权在分发前立即核对当前安全主体、租户、动作、资源、参数、策略和已经绑定的审批。
  4. 结果处理验证结构,限制内联大小,把大输出保存为制品,记录回执,并把返回文本当作不可信数据。

工具名称、描述、模式和结果,都是不可信的模型输入。签名和审查可以降低工具投毒风险,但每一次具体调用仍然需要授权,子智能体发起的调用也不例外。MCP 同样用输入模式和可选输出模式定义工具,要求客户端验证结果,并建议让人能够拒绝敏感调用 (Model Context Protocol 2025)。它的授权指南还禁止把任意令牌转交给下游服务器,并要求令牌绑定到目标资源 (Model Context Protocol 2025; Campbell et al. 2020)。

不存在一条通用的工具数量阈值,可以断定扁平目录何时失效。应当使用真实模型、工具模式和任务分布测量选择质量。目录扩大后,检索、命名空间或模型驱动的发现可以减少逐轮暴露的工具,但每种方式都会增加新的故障:漏掉必要工具、暴露危险而无关的工具,或为发现额外消耗调用次数。

有状态工具:区分三类状态

“有状态工具”这个说法掩盖了三种不同责任:

  • 连接状态包括传输会话、协商出的能力、游标和背压。
  • 持久资源状态存在外部系统中,例如文件、工单、事务或远程作业。
  • 模型可见状态是后续轮次显式传回的句柄或摘要。

协议支持时,恢复流程应该重新连接、重新发现能力,并从游标或显式句柄继续。否则就建立新连接,但不能假装持久资源状态也随旧连接消失。协议本身会改变这些机制。例如,MCP 在不同日期的规范版本中修改过传输和授权 (Model Context Protocol 2025; Model Context Protocol 2025)。因此要固定协议版本,并依据规范声明的保证设计系统,而不是假定每条连接都是持久会话。

上下文是视图,不是记录

权威历史记录包含已接收命令、模型与工具结果、审批决定、回执、来源链接和未解决义务。工作上下文是这份历史的有损投影:

Kt=P(Ht,Bt,Tt),K_t = P(H_{\le t}, B_t, T_t),

其中,P 从权威历史 H 中选择材料,在上下文预算 B 内服务当前任务状态 T。提示词上下文、持久执行历史和 第 40 章 介绍的个性化记忆,是三类不同存储。

压实可以总结早期轮次;遮蔽可以用带类型的占位符替换庞大观察结果;大输出可以变成制品引用;检索可以为下一项决定恢复证据。每种方式都会丢失信息,因此投影视图应该说明省略了什么,并保留指回来源事件的链接。压实工作上下文不会删除权威证据。它尤其要保留活动约束、待处理审批、未完成计划、影响回执、失败,以及所有摘要的来源血缘。

更长的上下文并没有消除这个设计问题。受控研究发现,即使检索结果正确,性能仍可能随输入长度增加而下降 (Du et al. 2025);在软件智能体任务中,简单遮蔽观察结果也可能达到复杂摘要方法的效果 (Lindenbauer et al. 2025)。选择必须由具体任务的实证决定。

沙箱由多道独立边界构成

“在容器里运行”并不是隔离契约。每个维度都要单独说明:

维度 最低限度要回答的问题
进程 代码能否向宿主或同级进程发送信号、检查其状态或逃逸过去?
文件系统 哪些路径被挂载、可写、持久保存或共享?
网络 出站连接是否默认拒绝,允许哪些目标和协议?
凭据 是否彻底排除长期密钥,并只为已经核对的动作签发能力?
资源限制 CPU、内存、进程数、磁盘、输出和网络上限分别是多少?
生命周期 暂停、重启、取消、强制终止、到期和删除之后,哪些状态仍然存在?

工作树不是安全边界。它可以减少多个编程任务之间的文件冲突,却无法隔离内核、进程、网络、宿主凭据或容器运行时套接字。不可信代码需要与威胁模型相匹配的沙箱,例如加固容器、用户态内核或微型虚拟机,还要配合非 root 身份、受限挂载、移除多余权限、系统调用策略和默认拒绝的出站网络 (Souppaya et al. 2017)。

不要挂载长期密钥。凭据代理可以先验证具体动作,再签发短期、范围狭窄并绑定受众和资源的凭据。代理还必须对日志脱敏,并支持轮换和撤销。这些控制可以降低凭据被盗的概率和影响范围,却不能让一个已经授权的动作自动变得安全。同样,结束本地沙箱也不会撤销已经使用其凭据提交的外部影响。

预算与准入控制必须协同

限制是一套账本,不是警告计数器。启动模型或工具调用前,先预留其允许的最坏消耗;完成后核对实际用量并释放余额。无法完成预留的工作必须拒绝。挂钟时间与空闲时间、模型调用、输入和输出词元、金额、工具调用、外部变更、字节数、子运行、并发操作和沙箱资源,都要有彼此独立的上限。

子运行从父运行获得子预算,而不是领取一份全新额度。因此,父子两级的预留始终受同一个单调上限约束。系统要保存绝对截止时间,避免重启凭空增加挂钟时间,并把剩余期限传给下游。本地超时只限制运行框架等待多久;远程工作或计费仍可能继续,必须另行取消或核对。

准入控制把身份与策略检查,同原子地预留配额或容量结合起来。调度前查询容量只能提供参考,因为真正调度前容量仍会变化。有界队列、租户级并发、优先级与公平性、启动期限、背压和明确的 pending 状态,可以让资源紧张变得可见。Kubernetes 资源配额限制命名空间的总资源消耗;Pod 请求无法调度时则会保持 Pending (Kubernetes Authors 2026; Kubernetes Authors 2026)。

断路器解决的是另一个问题。它用关闭、打开和试探状态,防止调用方持续请求一个故障依赖。它不能代替运行预算,也不能把“退出码为零”当作任务成功。结果判断应该依赖结构化状态、回执、后置条件检查和明确的目标验证。

把运行框架作为整体评估

运行框架变更本身就是实验处理,不是可以忽略的背景噪声。比较方案时,应固定模型、任务集、工具实现、策略、采样配置和资源范围。记录确切的智能体定义、运行框架与适配器版本、实际请求、工具模式、沙箱配置和环境。除了任务质量,还要报告这些保护机制是否奏效:

  • 任务成功率和无效工具调用率;
  • 工具选择精确率和召回率;
  • 审批绕过率和未授权影响率;
  • 取消确认延迟和静止延迟;
  • 取消后的影响数量和重复影响率;
  • 恢复偏差和未解决影响率;
  • 压实后的上下文关键内容丢失率;
  • 隔离逃逸率;
  • 相对于最小基线的新增延迟和新增成本。

接着要在边界注入故障:让工作进程在外部提交与日志回执之间崩溃;重复交付一条命令;让租约在工作进程暂停时到期;在审批后撤销授权;挂起子进程;填满队列;投毒工具描述;在关键约束即将使用前压实上下文;在外部影响发生后创建分叉。干净的正常路径轨迹无法检验这份契约。

下层约束的传导

归因于智能体的分数,其实是模型、运行框架、工具和环境的共同性质。基准基础设施可能泄漏答案,也可能暴露评分器,让智能体学会利用它 (Wang et al. 2026)。因此,第 47 章第 52 章 在比较模型时,必须报告并固定运行框架版本。提供商配置文件有参考价值,但当适配器会插入默认值或省略不支持的字段时,实际序列化的请求和观察到的响应,才是更可靠的复现材料。

争议所在

有两个问题仍无定论。第一,没有任何上下文投影策略能可靠预知未来步骤需要哪项细节。压实、遮蔽、检索和更大窗口,只是在交换不同错误。第二,可移植的运行框架能够统一常见操作,却无法凭空制造提供商能力,也无法让不同提供商拥有完全相同的错误和取消语义。这两项都应该作为明确的产品选择接受测量,不能藏在一个通用的“智能体”接口背后。

运行契约核对表

把一项长时间运行交给运行框架之前,应当确认它能回答:

  • 哪一个不可变定义和归约器版本负责这次运行?
  • 最后接收的是哪项事件,还有哪些工作或外部影响没有解决?
  • 哪一份确切载荷和策略授权下一项外部操作?
  • 何时确认取消,运行框架拥有的工作又在何时完全静止?
  • 哪些重试类别安全,由谁执行幂等约束?
  • 父预算和子预算分别还剩多少?
  • 进程、文件系统、网络、凭据、资源和生命周期边界如何约束执行?
  • 恢复测试和故障注入测试能否证明以上主张?

运行框架让智能体具备可运维性,而不只是能运行。下一章 第 42 章 会进一步考验这份契约:图形界面会带来更长的动作链、更弱的状态观察、更高的延迟,以及执行前更难识别的外部影响。

延伸阅读

正文引用的来源都收录在本书的参考文献页。下方制品提供了一个精简的工具描述投毒复现实例。

  • Invariant Labs, “mcp-injection-experiments,” 2025. github.com
    该 GitHub 仓库提供代码示例,用于复现针对 AI 智能体的 MCP(模型上下文协议)工具投毒与提示注入攻击。

评论

登录后评论