第22章 Agent Runtime¶
Agent Runtime 定义企业 Agent 的执行契约。一次任务通常是一条可暂停、可恢复、可审计的链路:模型规划步骤,Runtime 执行工具,前端展示进度,审计系统还原每次动作。没有 Runtime,长任务刷新页面后很难续跑,工具调用失败后也难以判断该重试、人工审批还是终止。本章以 Run、Step、Tool Call 三个对象为主线,展开 Run 六态、SSE 事件流、检查点、失败分类和 mini-platform 中的实现边界。用户通过 DataAgent 发起一个经营分析任务:先查上周销售数据,再定位异常 SKU,必要时生成一份说明并发起人工确认。前端只看到进度从“规划中”变成“执行中”,但后台已经经历了多轮模型判断、SQL 工具调用、结果校验和可能的审批等待。
如果平台只把这次交互当作聊天消息,很多问题无法处理。用户刷新页面后任务从哪里继续?SQL 工具超时后是重试还是终止?模型说“任务完成”时,工具队列是否真的清空?审批挂起 48 小时后,原来的上下文还能不能恢复?这些问题都属于 Runtime,Prompt 或 Planner 单独解决不了。Agent Runtime 的职责,是把一次用户任务变成一条可观察、可恢复、可审计的 Run。Planner 提议下一步,Registry 找到工具,Gateway 调模型,Console 展示进度;Runtime 负责把这些组件串成一条受控执行链。Agent Runtime 是很多企业试点走向生产时才真正意识到的缺口。聊天原型可以把用户输入、模型输出和工具结果放在同一个会话里;生产任务却会跨越多轮规划、多个工具、审批等待、外部系统超时和用户离开页面后的恢复。没有 Runtime,一次看似普通的经营分析就会变成一串无法复盘的异步动作。
Runtime 要回答的是“这次任务当前处于什么状态,下一步由谁负责,失败后如何恢复”。如果平台只关心“模型下一句说什么”,就无法管理长任务、工具副作用和人工审批。Run、Step 和 Tool Call 的分层让平台能分别管理任务生命周期、推理决策和副作用。用户刷新页面、服务重启、工具超时或人工审批延迟时,系统仍能回到同一个 run_id 上继续,而非重新生成一个近似任务。企业里最危险的做法是让每个 Agent 应用自己写执行循环。一个应用把工具失败当成重试,另一个应用直接终止;一个应用把审批写在前端,另一个应用写在后端;日志字段也各不相同。试点阶段看不出问题,审计、SLO 和事故复盘时会发现平台没有统一事实来源。
22.1 Runtime 的对象模型¶
22.1.1 Run / Step / Tool Call 的必要性¶
Runtime 最容易出错的地方,是把会话、推理轮次和工具调用混成一个对象。会话面向 UI,回答“用户在哪个聊天窗口里”;Run 面向任务,回答“一次可审计任务从哪里开始、到哪里结束”;Step 面向推理轮次,回答“Planner 第几次决定下一步”;Tool Call 面向副作用,回答“哪个工具被谁用什么参数执行,结果是什么”。财务分析、客服工单、合同审阅这类任务都可能跨多个系统。只记录会话 ID,审计员无法知道第 3 次工具调用是否经过审批;把每次工具调用都当成新任务,又无法在人审等待后恢复原任务。Run / Step / Tool Call 分层,是为了同时满足任务级 SLA、推理轨迹和副作用审计。
表22-1:Run、Step 与 Tool Call 的职责边界。来源:本书整理。
| 对象 | 代表什么 | 主要字段 | 典型用途 |
|---|---|---|---|
| Run | 一次可审计任务 | run_id, agent_id, input, context, state |
SLA、检查点、审批、审计 |
| Step | Planner 的一轮决策 | run_id, step_index, planner_output |
组织多轮规划和工具反馈 |
| Tool Call | 一次实际工具执行 | tool_call_id, tool, args, status, output |
回放、幂等、错误分类 |
这三个对象的关系很简单:一次 Run 包含多轮 Step;一轮 Step 可以产生零次、一次或多次 Tool Call。Planner 只提出 Tool Call 意图,执行动作必须由 Runtime 通过 Registry 完成。
22.1.2 /run 请求契约¶
一次 POST /agents/{agent_id}/run 对应一个 Run。长任务等待审批时仍使用同一个 run_id,不要新开聊天窗口或创建新的任务 ID。
{
"input": "上周华东区销售下滑的主要 SKU 是什么?",
"context": {
"user_id": "u-ops-001",
"tenant_id": "demo-retail",
"scope": ["sales_region:east"]
},
"options": {
"idempotency_key": "optional-client-key",
"max_steps": 20
}
}
context 要原样传给 Policy 和工具层。idempotency_key 用于客户端重试,避免重复执行发邮件、创建工单、写数据库这类有副作用的动作。max_steps 是死循环保护的一部分,防止模型在同一类工具调用中反复尝试。
22.1.3 Runtime 与相邻组件¶
Runtime 不替代 Planner、Tool Registry 或 Policy。它的职责是推进 Run 状态、执行已授权工具、推送事件、写检查点,并在失败时选择恢复路径。
表22-2:Runtime 与相邻组件的分工。来源:本书整理。
| 组件 | Runtime 做什么 | 组件自身负责什么 |
|---|---|---|
| Planner | 调用 next_step(),接收结构化决策 |
生成下一步计划,不直接执行工具 |
| Tool Registry | 按工具名和版本查找 handler | 管理工具 schema、版本和描述 |
| Policy | 在高风险动作前请求裁决 | 鉴权、审批策略、风险判断 |
| Memory | 读取或写入 Planner 可见上下文 | 长短期记忆、摘要、检索片段 |
| Console | 推送状态与审批事件 | 展示进度、接收人工审批回调 |
业界常把 Agent 能力拆成规划、记忆、工具使用等模块 (Wang et al. 2024)。Runtime 是连接这些模块的执行层,负责让它们按照同一条任务生命周期运行,避免各组件各自推进状态。把 Runtime 下放给每个业务 Agent 看似灵活,实际会很快失控。一个团队把工具超时当成失败,另一个团队把同样的超时重试三次;一个团队在前端断线后重跑任务,另一个团队从检查点恢复;安全团队想查同类高风险工具调用时,却发现每个 Agent 的日志字段都不同。Runtime 平台化的价值正在这里:它把状态、事件、错误码和恢复语义固定下来,让业务 Agent 只关心任务逻辑。
22.2 Run 六态状态机¶
22.2.1 状态定义¶
Run 六态是 Runtime 对外暴露的生命周期。编排框架内部可以有更多节点和子图,但 Console、SLA、告警、检查点和审计应以 Run 六态为准。
表22-3:Run 六态的含义与典型迁移。来源:本书整理。
| 状态 | 含义 | 典型迁移 |
|---|---|---|
pending |
Run 已创建,尚未开始规划 | start |
planning |
Planner 正在生成下一步决策 | plan_ready, plan_error |
executing |
Runtime 正在执行工具,或准备进入下一轮规划 | next_step, done, need_approval, exec_error |
waiting_human |
执行被有意暂停,等待人工审批或回调 | approved, rejected |
succeeded |
Planner 已结束,且没有未完成 Tool Call | 终态 |
failed |
不可恢复错误、审批拒绝、取消或重试耗尽 | 终态 |
waiting_human 表示 Runtime 明确进入暂停状态,不是任务卡死。合规场景里,审批等待时间通常要单独统计,不能简单算入模型或工具执行延迟。这点对长任务很关键。审批等待可能持续数小时甚至数天,但 Runtime 仍要保留原 Run 的身份、上下文和检查点。否则审批通过后重新创建任务,既会丢失之前的工具结果,也会让审计链断成两段。同一个 run_id 贯穿等待、审批、恢复和最终导出,业务责任和技术执行才能对齐。普通聊天后端只需要保存一段问答;Runtime 还要说明任务在哪个状态暂停、哪个工具已经执行、哪个审批仍在等待,以及恢复后从哪里继续。
22.2.2 迁移图¶
图22-1:Run 六态状态机。来源:本书自绘。Alt text:状态机包含 pending、planning、executing、waiting_human、succeeded、failed 六个节点,箭头表示从创建、规划、执行、人工等待到成功或失败的合法迁移。
图 22-1 的关键规则是:终态只能由 Runtime 触发。模型可以在文本中说“任务完成”,Planner 也可以返回结束意图,但 Runtime 还要确认工具队列为空、审批已完成、失败已处理,才能进入 succeeded。
22.2.3 与编排图状态的区别¶
第25章会讨论 Planner 和编排模式。编排图状态属于 Planner 内部实现,例如某个 LangGraph 节点、子图或路由分支。Run 六态属于平台契约,对前端、审计和告警可见。两者不应混用。如果把内部编排节点直接暴露给前端,用户会看到大量与业务无关的技术状态;如果把 Run 六态折叠进 Planner 内部,Runtime 又无法独立处理取消、审批、重试和恢复。因此,内部编排可以复杂,对外生命周期必须稳定。
22.3 执行循环与事件流¶
22.3.1 主循环¶
Runtime 的主循环可以先压缩成四步来看:创建 Run、调用 Planner、执行工具、依据结果继续规划或进入终态。先把这条最短路径看清楚,后面的重试、审批和回放机制才有锚点。
create run -> planning
while run is not terminal:
planner_output = planner.next_step(context)
if planner_output asks for tools:
validate policy and tool schema
emit action event
execute tool through registry
emit result event
update checkpoint
elif planner_output asks to finish and no tool is pending:
mark succeeded
elif approval is required:
mark waiting_human
else:
classify error and recover or fail
这段伪代码里有两个边界:Planner 只返回决策,不驱动状态机;工具调用必须先经过 schema 校验、权限判断和幂等控制,再进入执行器。主循环还要避免把“模型继续想一想”变成无限执行。每一轮 Step 都应消耗预算:模型调用次数、工具调用次数、总耗时和上下文长度都要计入同一个 Run。预算的作用是给失败设置边界。没有预算的 Runtime 很容易在工具参数反复修正、检索结果不满足条件或 Planner 犹豫不决时陷入循环。
22.3.2 端到端时序¶
图22-2:端到端 Run 时序。来源:本书自绘。Alt text:时序图展示客户端、Runtime、Planner、Tool Registry 和模型网关之间的调用顺序,从 /run 请求到状态迁移、工具调用、事件流推送和最终返回。
图 22-2 展示 Runtime 视角的执行链。ReAct 把推理和行动组织成交错轨迹 (Yao et al. 2023),工程上则需要把“行动”落成 Tool Call 记录,并把“观察”落成工具结果事件。OpenAI Agents SDK 的流式运行项也区分工具调用和工具返回事件 (OpenAI n.d.),前端与审计系统因此能看到执行过程,而非只看到一段最终回复。
22.3.3 SSE 事件¶
聊天模型的 token 流只能说明模型正在生成文本,不能证明系统执行了哪个工具。Agent SSE 应至少包含三类事件。
表22-4:Agent SSE 事件类型。来源:本书整理。
| 事件 | 含义 | 关键字段 |
|---|---|---|
state |
Run 状态变化或终态 | run_id, state, step_index, answer |
action |
即将执行工具 | tool_call_id, tool, version, args |
result |
工具执行结束 | tool_call_id, status, output, error |
approval_request |
进入人工等待 | approval_id, title, artifact_ref, requested_actions |
action 与 result 必须成对出现。只有 action 没有 result,审计时就无法证明副作用是否发生。进入 waiting_human 时,Runtime 还要推送审批事件,Console 再把人工回调传回 Runtime。
event: state
data: {"run_id":"run-8f3a","state":"planning","step_index":0}
event: action
data: {"run_id":"run-8f3a","tool_call_id":"tc-1","tool":"sql_executor","args":{"sql":"..."}}
event: result
data: {"run_id":"run-8f3a","tool_call_id":"tc-1","status":"succeeded","output":{"rows":[...]}}
event: state
data: {"run_id":"run-8f3a","state":"succeeded","answer":"华东区下滑 Top3 SKU 为..."}
SSE 使用 text/event-stream,事件格式和断线重连语义由 HTML Living Standard 定义 (WHATWG n.d.)。客户端断线后应带 Last-Event-ID 重连,服务端从事件日志或检查点继续推送,不能重新发起一次会产生副作用的 /run。
22.3.4 Trace 与 run_id¶
Runtime 还要把执行过程写入 Trace 系统。一次任务可能经过前端、Runtime、LLM Gateway、Tool Registry 和外部 SQL 服务。排查“慢在哪一步”时,需要用同一个 trace-id 串起这些服务的 span。run_id 与 trace-id 的用途不同。run_id 面向业务任务,用于 Console、审批、检查点和审计导出;trace-id 面向可观测性,用于性能分析、调用拓扑和告警。两者可以在 Observability 层建立映射,但不宜合并成一个字段,否则业务恢复语义会受到采样、过期和链路重建策略影响。分布式 Trace 可沿用 W3C traceparent 头和 OpenTelemetry 规范 (W3C 2021; OpenTelemetry n.d.)。排查事故时,这两个 ID 会一起出现。客服同事拿到的是 run_id,能定位用户看到的任务;SRE 拿到 trace-id,能定位慢调用和错误 span。两者映射清楚,团队才能从用户反馈走到系统调用,再从系统调用回到业务任务。
22.4 检查点与恢复¶
22.4.1 检查点保存内容¶
检查点是 Run 的可重启快照。进程崩溃、发布重启或节点迁移后,新进程读取检查点,应该能从最近的合法状态继续,不应要求用户重新提问。
表22-5:Runtime 检查点的必要字段。来源:本书整理。
| 类别 | 字段 | 为什么需要 |
|---|---|---|
| Runtime 状态 | run_id, state, step_index, history |
恢复状态机与迁移历史 |
| 请求上下文 | input, context, options |
保持权限、租户和任务语义 |
| 工具记录 | 未完成调用、已完成结果引用、错误码 | 避免重复副作用,重建 Planner 输入 |
| Memory 引用 | 会话 key、摘要、检索片段引用 | 防止 Planner 恢复后丢失上下文 |
| 事件位置 | 最后发送的 SSE event id | 支持断线后增量推送 |
只保存 state=executing 不够。比如经营分析任务已经拿到 SQL 结果,Pod 在下一轮规划前重启;如果检查点没有保存工具结果和 Memory 引用,Planner 恢复后可能重新选表或重算指标,导致恢复前后口径不一致。
22.4.2 何时写检查点¶
企业默认应在三类时机写入检查点:状态迁移成功后,Tool result 落盘后,进入或离开 waiting_human 时。写入过疏会放大崩溃窗口,写入过密会增加存储压力。高 QPS 短任务可以评估采样策略,但有副作用的工具调用结果不应跳过。存储上可以分两层。在线状态存 Redis 或其他低延迟 KV,TTL 对齐 Run 上限;审计归档存 PostgreSQL 或对象存储,采用追加写,便于回放和导出。本地开发可以用 SQLite 或文件目录模拟。
22.4.3 恢复流程¶
恢复流程也要受状态机约束。否则系统很容易在重试时重复执行副作用工具,或把已经进入人工审批的 Run 错误地拉回自动执行。
- 根据
run_id加载最近检查点,确认状态不是终态。 - 重放状态历史和 Tool Call 结果引用,重建 Planner 可见上下文。
- 如果状态是
waiting_human,等待 Console 回调,不自动继续执行。 - 如果有未完成工具调用,先查询幂等键对应的执行状态,再决定补推
result事件、重试或失败。 - 客户端断线重连时,根据
Last-Event-ID只推送未收到的事件。
LangGraph 等框架也会持久化图执行状态 (LangChain n.d.)。本书强调的区别是:Runtime 检查点以平台 run_id 为主键,服务的是 /run 契约和 Run 六态,不绑定某个编排框架内部节点名称。恢复流程最怕两件事:不知道工具是否执行过,又把同一动作重新执行了一次。因此,写操作工具必须与检查点一起设计幂等语义。创建工单、发邮件、写审批记录这类动作,执行前要带 idempotency_key,执行后要保存工具侧返回的业务 ID。恢复时先查询这个业务 ID 或幂等键,不要直接再次调用工具。还有一类恢复问题来自事件顺序。工具结果已经落盘,但 SSE 还没推到前端;或者前端已经收到 action,服务端在写 result 前重启。事件日志需要和检查点一起设计,至少保证同一个 tool_call_id 的 action、result 和状态迁移可以按顺序补发。否则用户界面会显示任务卡住,审计侧却能看到工具已经执行,两边对不上。
22.5 失败分类、超时与取消¶
22.5.1 错误码与恢复策略¶
Runtime 不能把所有失败都简单重试。模型超时、工具不可用、参数错误、上下文过长、策略拒绝和死循环的责任方不同,恢复路径也不同。
表22-6:Runtime 失败分类与恢复策略。来源:本书整理。
| 失败类型 | code |
默认处理 |
|---|---|---|
| 模型超时 | MODEL_TIMEOUT |
限次重试,必要时切换备用模型 |
| 工具不可用 | TOOL_UNAVAILABLE |
幂等场景重试,非幂等场景查询执行状态 |
| 工具参数错误 | TOOL_ARGUMENT_INVALID |
把 schema 错误反馈给 Planner,最多重试固定次数 |
| 上下文超长 | CONTEXT_OVERFLOW |
压缩历史,裁剪低优先级片段,或失败返回 |
| 死循环 | LOOP_DETECTED |
立即失败,记录重复参数摘要 |
| 策略拒绝 | POLICY_DENIED |
进入人工审批或失败 |
| 工具未注册 | TOOL_NOT_FOUND |
反馈 Planner 修正,仍失败则终止 |
参数错误通常不应立刻终止 Run。比如 Planner 生成 SQL 工具参数时漏掉 tenant_id,Registry 可以把 schema 错误写入 result 事件,再作为下一轮 Planner 输入。超过重试预算后,Runtime 才触发失败迁移。相反,策略拒绝和死循环不能盲目重试,否则会增加风险或拖垮共享资源。
22.5.2 三档超时¶
Runtime 至少需要三档超时:Run 总超时、Tool Call 超时和 LLM 请求超时。Run 超时决定整次任务是否进入失败或异步队列;Tool Call 超时决定是否按幂等键重试;LLM 超时交给 Gateway 的模型路由和重试策略处理。审批等待通常单独配置 approval_timeout_s,不直接等同于模型或工具超时。这三档超时不能写成一个全局配置。Run 总超时面向用户承诺和业务流程,可能是几分钟、几小时,甚至跨天;Tool Call 超时面向外部系统和幂等语义,通常要按工具类型配置;LLM 请求超时面向模型服务稳定性,更多由网关和模型路由处理。把它们合并以后,系统会出现两类问题:长任务被模型超时误杀,或者短工具因为 Run 总时长很长而迟迟不失败。超时结果也要进入状态和事件流。用户看到“任务失败”不够,前端和审计都需要知道是模型超时、工具超时、审批超时还是 Run 总超时。只有错误码清楚,后续评测才能区分模型能力问题、工具稳定性问题和流程设计问题。
22.5.3 取消语义¶
取消可以由用户、Console 或上游流程触发。取消后 Runtime 应停止未开始的工具队列,尽力取消进行中的调用,写入 failed + RUN_CANCELLED,并记录 cancelled_at。本书不把 cancelled 建成第七个 Runtime 状态,是为了保持 Run 六态稳定;如果未来单独建模取消态,状态机、SSE、检查点和前端展示必须同步更新。取消不是简单删除任务。已经执行的工具调用可能产生外部副作用,例如创建工单、发送邮件、写入审批记录或触发数据导出。Runtime 只能停止后续动作,并尽力把进行中的调用标记为取消请求;对于已经完成的动作,要通过补偿步骤或人工处理关闭风险。取消事件必须写入 trace,否则复盘时无法判断用户是在结果生成前取消,还是在外部动作执行后才取消。
22.6 Runtime 的代码边界¶
22.6.1 Runtime 的实现入口¶
Part V 各章共用 projects/multi-agent-workflow/ 演示 Run 六态、Registry 工具调用、Handoff 和 waiting_human。core/runtime/ 是平台模块,读代码可以从状态机、模型对象和主循环开始。
mini-platform/core/runtime/
├── state_machine.py
├── run_models.py
├── run_loop.py
├── handoff_tool.py
├── approval.py
├── checkpoint.py
└── stub_planner.py
projects/multi-agent-workflow/
├── run.py
└── README.md
运行方式如下。
cd mini-platform
python3 projects/multi-agent-workflow/run.py start
python3 projects/multi-agent-workflow/run.py approve
自动化验证可以运行:
读这段实现时,不建议先从演示入口看起。更可靠的顺序是先看 state_machine.py,确认六态和迁移;再看 run_models.py,理解 RunContext 和 ToolCallRecord;然后看 run_loop.py,把状态迁移、工具执行、检查点和审批恢复串起来。这样读代码时不会把样例业务步骤误认为 Runtime 的通用能力。
22.6.2 Runtime 示例范围与后续演进¶
本章示例覆盖状态机、RunLoop、Registry invoke、SSE 事件、检查点和人工审批。生产版本还需要补 HTTP /run 服务、OpenTelemetry span、三档超时、持久化事件日志、分布式锁、工具幂等和审计导出。本章示例已经覆盖 Run 六态、Tool Call 执行、检查点、SSE、人工审批和基础日志,但这些能力距离生产还有一段距离。Run 六态需要和 HTTP API、SSE、检查点保持一致;Tool Call 执行还要补权限、幂等、超时和熔断;检查点不能长期停留在文件或轻量存储,应进入在线状态存储和审计归档;SSE 要支持 Last-Event-ID 和事件日志,才能处理断线重连;人工审批要接 Console、Policy 和工单系统;可观测性也要从基础日志扩展到 trace-id、span、指标和告警。
早期 Runtime 不必一次实现所有生产能力,但不能缺少三个底线:状态机不能被模型文本绕过,Tool Call 必须有 action / result 成对记录,检查点必须能恢复 Planner 可见上下文。如果只能选一个验收场景,建议选择“工具执行后进程重启”。这个场景会同时检验状态机、Tool Call 记录、检查点、幂等和 SSE 重连:恢复后不应重复执行工具,前端应继续看到后续事件,最终状态应与未重启时一致。
22.7 Runtime 的资源隔离与并发控制¶
Runtime 进入生产环境后,最容易被低估的是资源隔离。开发环境里,一个 Run 通常由单个用户触发,工具调用也比较少;上线后,同一租户可能同时发起多轮对话、批量任务和后台重试,不同租户之间还会争用模型配额、工具连接池、数据库查询资源和消息队列。Runtime 如果只维护状态机,不维护资源边界,就会出现一个长任务拖慢整个平台的情况。资源隔离至少要覆盖四个层面。第一是租户和用户级别的并发限制,避免单个调用方占满执行队列。第二是工具级别的限流,例如 CRM、工单、数据库和文件解析服务的调用能力不同,不能使用同一套重试策略。第三是模型级别的预算控制,把上下文长度、输出 token、重试次数和并发请求纳入同一个预算。第四是 Run 级别的超时与取消,确保用户取消任务后,后台工具调用、流式输出和临时文件都能被清理。
并发控制还要考虑幂等。用户刷新页面、前端重连、审批回调重放、队列消费者重启,都可能导致同一个 Step 被提交多次。Runtime 需要为 Tool Call、审批事件和恢复动作设计幂等键,不能简单依赖“上一次没有报错”。如果一个扣减库存、发送邮件或创建工单的工具被重复调用,事后很难通过模型解释弥补。对 Runtime 来说,幂等是把 Agent 从演示系统推进到业务系统的必要条件,不是附加能力。
22.8 事件重放与状态修复¶
Runtime 的状态不应只存在于内存对象里。长任务、人工审批、工具回调和前端断线都会让一次 Run 跨越多个进程生命周期。更稳妥的做法是把关键状态变化记录成事件:Run 创建、Step 开始、工具请求、工具结果、模型输出、审批挂起、审批恢复、取消、失败和完成。检查点保存的是可恢复快照,事件日志保存的是状态为什么变成这样。两者缺一不可。事件重放可以服务两类场景。第一类是事故复盘,团队需要知道某次错误回答前后发生了什么,而非只看最终消息。第二类是状态修复,系统重启后可以从最近检查点加载上下文,再用后续事件补齐状态。如果发现事件和快照不一致,Runtime 应当进入修复流程,而非继续执行。修复流程可以是自动回滚到安全状态,也可以是把 Run 标记为需要人工处理。
这套机制会增加存储和实现成本,但它把 Runtime 的责任讲清楚了:Runtime 还涉及调用模型和工具的循环,也是 Agent 行为的账本。第38章的 Trace 可以在事件之上做诊断视图,第30章的 HITL 可以把审批事件接入恢复链路,第39章的评测可以从失败事件中抽样。没有事件重放,平台只能看到一次运行的表面结果,很难建立可信的运行治理。
22.9 Runtime 与队列系统的分工¶
长任务通常需要队列,但队列不能替代 Runtime。队列负责调度执行单元、控制并发和处理消费者失败;Runtime 负责保存任务语义、状态迁移、工具结果和用户可见进度。若把业务状态只放在队列消息里,消息过期、重试或死信后,平台就很难恢复用户看到的任务状态。若 Runtime 不知道队列执行到了哪一步,也无法给前端稳定反馈。比较清晰的分工是:Runtime 创建 Run 和 Step,队列只接收可执行 Step 的引用;Worker 执行后把结果写回 Runtime,由 Runtime 决定下一步状态。队列重试时使用 Step 的幂等键,Worker 不直接推进业务状态。这样即使 Worker 崩溃,Runtime 仍然能知道 Step 处于等待、执行中、失败、可重试或需要人工处理。
这套分工还方便后续扩展。低风险同步任务可以不进队列,高耗时解析、批量工具调用和报告生成可以异步执行,高风险写操作可以等待审批后再投递。Runtime 是统一语义层,队列是执行基础设施。把两者分开,系统才不会因为技术组件变化而破坏 Agent 的状态模型。Runtime 还要向前端暴露稳定进度,而非暴露内部队列细节。用户需要知道任务正在解析、等待工具、等待审批、生成报告还是失败恢复,不需要知道消息在哪个 topic 或由哪个 Worker 处理。前端状态越稳定,后端越容易演进。这个边界也是平台化的重要标志。稳定进度也能减少重复提交。用户看到任务处于可理解状态,就不需要刷新页面或重新发起同一请求;后端也能通过 run_id 和 idempotency_key 把重连、重试和恢复归并到同一次 Run。
这类体验细节最终会影响系统负载。越多重复提交和不明状态,Runtime 越难判断真实用户意图,也越容易触发重复工具调用。因此,Runtime 的 API 设计要把状态查询、事件订阅和取消操作作为基础能力。前端不应通过重复发起任务来确认进度,外部系统也不应通过重放请求来探测任务是否完成。稳定的控制接口能减少很多看似偶发的运行问题。这些接口也让客服和运维能够介入同一次 Run,而非要求用户重新描述问题。
Runtime 的状态机要服务真实故障。模型输出异常、工具超时、用户取消、审批挂起、外部系统部分成功,都会让任务进入不同恢复路径。若只有 running 和 failed 两个状态,平台无法区分可重试失败、业务拒绝、人工等待和系统取消。事件流也不是前端动画。SSE 事件承载的是可回放的运行记录:Planner 做了什么决定,工具何时开始和结束,审批为什么挂起,最终产物在哪里。前端用它展示进度,审计系统用它还原过程,评测系统也可以用它分析失败模式。把 Runtime 做成平台能力后,Agent 应用会更轻。应用负责定义任务和工具,Runtime 负责执行语义、状态、检查点、超时和事件。这个分工能让不同业务共享同一套恢复和审计能力,而非各自维护一套脆弱的执行脚本。
Runtime 上线后,平台要持续观察状态分布。大量 Run 停在 waiting_human,说明审批责任或通知链路有问题;大量 Run 因 max_steps 终止,说明 Planner 或工具反馈设计不合理;大量 Run 被用户取消,说明前端进度和预期管理不足。这些状态是平台运行质量的直接信号,不应被当作普通日志噪声。取消和超时也要做成显式语义。用户点击取消后,已经执行的工具是否需要补偿,排队中的任务是否移除,已生成 artifact 是否保留,都要由 Runtime 决定。工具超时后,系统不能只返回 failed,而要判断是否可重试、是否可能部分成功、是否需要人工确认。
检查点保存的内容要足够恢复,又不能无节制保存敏感信息。Planner 可见上下文、工具结果摘要、artifact 引用、审批状态和错误分类通常需要保存;大体量原始数据和敏感明细应放在受控存储中,通过引用恢复。恢复设计如果只追求方便,会把 Runtime 变成新的数据泄露面。Runtime 还要给开发者提供可调试入口。一次 Run 的状态机、事件流、工具调用和检查点应能在控制台里按时间线查看。没有这个入口,开发者只能在日志里拼接线索,业务团队也无法理解任务为何暂停或失败。当 Runtime 稳定后,企业可以把不同 Agent 的执行语义统一起来。客服、DataAgent、合同审阅和报告生成共享状态、事件、审批和恢复机制,业务差异体现在工具和策略上。这样的复用能降低每个新 Agent 的上线风险。
Run 对象还要承载租户和业务上下文。相同用户在不同租户、项目或角色下发起任务,权限和工具范围可能完全不同。Runtime 不能只记录 user_id,还要记录当时的组织、角色、策略版本和数据范围。这样审批、审计和回放才不会脱离上下文。事件顺序需要严格处理。SSE 推送可能因为网络抖动重连,前端可能收到重复事件,后台也可能重放历史事件。Runtime 应为每个事件分配递增序号,并让客户端按序应用。没有事件序号,前端时间线会偶发错乱,用户看到的状态也可能和后端不一致。工具执行的幂等键应由 Runtime 管理。模型不适合决定某次写操作是否重复,业务应用也不应各自实现幂等。Runtime 可以基于 run_id、tool_call_id、工具名和业务幂等字段生成键,并把键传给工具层。这样客户端重试、服务重启或事件重放都不会轻易造成重复副作用。
长任务还要有保活和清理策略。等待审批的 Run 可以保留更久,失败终止的 Run 可以按审计要求归档,用户取消的 Run 需要清理队列和临时 artifact。没有清理策略,Runtime 数据会持续膨胀;清理过早,又会破坏审计和恢复。Runtime 的接口稳定后,很多平台能力才能叠加。成本归因、SLO、HITL、Trace、评测样本回放和安全审计都依赖同一条 Run 记录。把 Runtime 做扎实,比在每个 Agent 中添加局部功能更能支撑长期演进。Runtime 的数据库设计也要支持审计查询。按 run_id 查单次任务,按用户查历史动作,按工具查调用记录,按状态查长时间挂起任务,都是常见需求。若早期只按聊天会话存一段 JSON,后续这些查询会很困难。对象模型在存储层也要保持清楚。
状态修复工具需要谨慎开放。生产环境里难免出现 Run 卡在中间态、事件重复、工具回调丢失等问题。平台可以提供管理员修复入口,但每次修复都要记录原因、操作者、修改前后状态和影响范围。手工修复没有记录,会破坏 Runtime 作为事实来源的可信度。Runtime 与队列的关系也要明确。同步请求适合启动任务和返回 run_id,真正执行应进入队列或后台 worker。队列可以承载重试、优先级和限流,Runtime 负责状态推进。把长任务绑在 HTTP 请求上,用户断线或网关超时后就很难恢复。多租户 Runtime 还要做资源隔离。一个租户大量提交长任务,不应占满所有 worker;高风险任务等待审批,也不应阻塞低风险只读任务。队列、并发和预算按租户和任务类型配置,才能支撑平台级运行。
22.10 Runtime 上线门禁与运行复盘¶
Runtime 上线前需要一组门禁,防止它只在演示链路里成立。第一项门禁是状态一致性。HTTP API、SSE 事件、检查点、数据库记录和前端展示必须使用同一组 Run 状态和错误码,不能出现后端显示 waiting_human、前端显示 running、审计记录显示 failed 的情况。第二项门禁是副作用控制。每个写工具都要有幂等键、超时、补偿说明和 Tool Call 记录;没有这些材料的工具只能留在沙箱或只读模式。第三项门禁是恢复验证。至少要跑进程重启、队列重放、审批超时、用户取消和工具部分成功这几类样例,确认 Runtime 能给出稳定状态。
运行复盘要看状态分布。大量 Run 卡在 waiting_human,可能说明审批人找不到、通知失败或责任配置不清;大量 Run 因工具超时失败,可能说明工具池容量或重试策略有问题;大量 Run 被用户取消,可能说明前端没有讲清进度或任务耗时超过预期;大量 Run 达到 max_steps,通常指向 Planner 循环或工具反馈不足。把这些状态当作普通错误日志,会错过平台改进机会。Runtime 是 Agent 平台的运行账本,状态分布就是它的健康信号。
门禁还要约束调试入口。控制台可以让工程师查看事件、检查点、工具调用和错误码,但不能随意修改 Run 状态。确实需要手工修复时,平台必须记录操作者、原因、修改前状态、修改后状态和影响范围。否则 Runtime 会失去事实来源地位。早期 Runtime 可以功能简单,但它的事实链必须可靠:Run 从哪里来,执行了什么,为什么停下,谁恢复过,最终产物在哪里,都要能回放。
22.11 租户级配额与恢复演练¶
Runtime 进入多租户环境后,还要把配额和恢复演练做成常规运行动作。配额不应只限制模型 token,还要覆盖并发 Run 数、后台 Worker、工具连接池、单租户排队长度、长任务保留时间和审批挂起数量。这样一个租户的批量报告、文件解析或异常重试,才不会挤占其他租户的交互式任务。配额策略也要允许分层:高优先级只读查询可以保留更低延迟,低优先级批处理可以排队,高风险写操作可以等待审批后再占用执行资源。
恢复演练要用真实状态组合。平台可以定期构造几类样例:工具执行后进程重启、审批回调重复送达、队列消息超时后重放、用户取消时工具已经部分成功、检查点与事件日志出现顺序差异。演练结果要记录到 Runtime 台账中,说明哪些状态可以自动修复,哪些状态需要人工介入,哪些工具缺少幂等或补偿能力。没有演练,团队只能在事故中第一次验证恢复路径;有了演练,Runtime 的状态机、事件日志、队列和工具契约会持续保持一致。
22.12 Runtime 并发控制与事件重放¶
Runtime进入生产后,最容易被低估的是并发控制。一个用户可能在同一任务里连续点击重试、刷新页面、补充条件或取消执行;多个工具可能并行返回结果;审批人可能在模型继续生成时改变状态;迟到的 token 或工具结果可能在 Run 已经结束后到达。若 Runtime 没有明确的事件顺序、幂等键和状态锁,前端看到的任务状态、后端记录的 Run 状态和工具实际执行状态会逐渐分裂。
并发控制要落实到事件模型。每个 Run 需要稳定的 run_id、event_id、seq、状态版本和幂等键。用户重试应创建可追踪的新尝试,取消应记录来源和确认结果,工具回调应检查当前 Run 状态,审批结果应和等待节点绑定。迟到事件不能直接写入最终答案,而要进入可审计的丢弃或补偿路径。这样第47章的前端 reducer、第38章的 Trace、第39章的 Eval 才能用同一段事件历史复原任务。
事件重放是 Runtime 可运维的基础。事故复盘时,团队应能从事件日志重建 Run 的状态变化:何时创建任务,何时选择工具,何时进入等待,何时失败或恢复,哪些事件被丢弃,哪些动作由人工确认。重放不一定重新执行工具,尤其不能重放写操作;它需要重放状态机和决策证据。只有事件重放成立,Runtime 才能支撑长任务、断线恢复、人工审批和多端协作,而不是只管理一次模型调用。
22.13 Runtime 运行账本与容量信号¶
Runtime 需要自己的运行账本。账本记录 Run 类型、状态分布、平均步骤数、工具调用数、等待时长、人工接管、取消原因、重试次数和失败阶段。没有这份账本,平台只能看到模型调用量和工具错误率,看不到任务为什么变慢、为什么挂起、为什么重复执行。Runtime 是 Agent 的任务骨架,它的运营指标应和模型、工具、前端、Trace 分开统计。
容量信号也应从 Runtime 发出。长任务积压、等待审批过多、同一工具调用排队、取消率升高、重试次数异常,都说明平台容量或流程存在问题。模型服务可能还很空闲,但 Runtime 已经因为人工审批、外部系统限流或工具超时出现堆积。若只看模型和 GPU 指标,团队会错过任务层拥塞。
运行账本要进入发布和复盘。新 Planner 策略上线后,步骤数是否增加;新工具接入后,等待和重试是否上升;新 UI 发布后,取消和重复提交是否变化;新审批规则上线后,挂起时长是否可接受。Runtime 账本把这些变化放在同一张任务视图里,帮助团队判断 Agent 能力扩展是否真正可运营。
22.14 Runtime 状态模型的兼容演进¶
Runtime 状态模型上线后,不宜频繁改动。Run 状态、事件类型、检查点字段、错误码和恢复动作都会被前端、Trace、Eval、HITL 和运维系统依赖。若状态模型变化没有兼容策略,旧任务可能无法回放,前端可能显示错误状态,评测样本也无法对齐历史结果。Runtime 是 Agent 平台的运行骨架,状态变更要按接口演进处理。
兼容演进需要版本字段。每个 Run 应记录 runtime_schema_version,事件也要能说明由哪个版本产生。新增状态时,平台要定义旧客户端如何显示;删除或合并状态时,要定义历史 Run 如何解释;新增错误码时,要说明 Planner、HITL 和前端是否需要新恢复动作。这样状态模型可以扩展,同时不破坏已经产生的运行证据。
早期平台可以先保持状态集合小而稳定,把复杂差异放进 reason code 和 metadata。只有当现有状态无法表达真实恢复动作时,再新增状态。这个纪律会让 Runtime 更容易被多个 Agent 复用,也能减少上线后“状态看起来成功,业务实际失败”的问题。
22.15 Runtime 变更发布与回放验收¶
Runtime 变更不能只看接口测试通过。状态机、事件顺序、检查点字段、队列重试和前端事件订阅相互耦合,任何一处变化都可能影响旧 Run 的恢复和新 Run 的可解释性。比较稳妥的发布方式,是先把变更拆成三类:只影响内部实现的变更、影响事件和检查点契约的变更、影响用户可见状态和恢复动作的变更。第一类可以通过单元测试和压测进入灰度;第二类需要用历史事件回放验证;第三类还要让前端、客服、运维和审计视图一起验收。
回放验收要使用真实失败样本,而非只跑成功路径。平台可以从 Trace 中抽取几类 Run:工具超时后恢复、审批挂起后继续、用户取消后清理、队列重复投递、模型输出异常、检查点缺字段。新版本 Runtime 在这些样本上应产生相同或更清楚的状态结论。若新版本改变了错误分类或恢复动作,发布记录要说明原因,并同步更新第39章的评测样本和第42章的 SLO 口径。这样 Runtime 的演进才有证据,不能依赖开发者对状态机的直觉。
灰度期间还要观察任务层指标。新 Runtime 版本上线后,如果平均 Step 数、等待时长、取消率、人工接管率或重复工具调用上升,即使接口没有报错,也说明状态推进或前端反馈可能发生了偏差。回滚策略也要提前定义:正在运行的 Run 继续用旧解释器,还是按新版本恢复;已经写入的新事件能否被旧版本读取;回滚后前端如何展示中间状态。Runtime 是整个平台的运行事实来源,发布纪律应接近数据库 schema 演进,不应按普通业务代码发布。
22.16 Runtime 运行争议的裁定材料¶
Runtime 上线后,很多争议不会表现为代码异常,而会表现为“任务到底有没有完成”。用户可能认为 Agent 已经给出答案,业务系统却没有执行动作;前端显示任务成功,工具调用实际返回部分失败;审批人点了同意,Runtime 却因为超时进入取消;模型输出了完成语句,但最后一个 Step 没有写入 artifact。此时裁定依据不能来自某个界面,也不能来自模型文本,而应回到 Runtime 运行事实。
裁定材料要包含一条 Run 的完整证据链。至少包括用户输入、Planner 决策、Step 序列、工具调用参数、工具返回、检查点、审批事件、取消事件、错误码、最终状态、用户可见消息和 artifact 写入记录。若争议涉及业务副作用,还要补充外部系统的确认结果,例如订单状态、工单状态、报告发布状态或权限变更记录。Runtime 的价值就在于把这些材料按时间顺序放在一起,让团队能判断任务是成功、失败、部分完成、等待恢复,还是需要人工重新裁定。
运行争议还要区分责任边界。Planner 选错工具,修复入口在策略和 Tool Registry;工具返回不一致,修复入口在工具契约和幂等设计;前端状态误导用户,修复入口在事件订阅和展示文案;审批超时导致任务取消,修复入口在 HITL 策略和通知链路;模型输出完成但 Runtime 未结束,修复入口在状态机和终态判定。把责任边界说清楚,复盘才不会落回“模型不稳定”这类无效结论。
早期平台可以为高风险 Run 建立争议包导出能力。争议包不需要暴露全部内部日志,但要提供可审核的时间线、关键输入输出、状态变化、证据引用和脱敏后的错误信息。客服、业务 owner、审计和平台团队看到同一份材料,才能对外解释一致。Runtime 因此承担执行和事实记录两类职责,支撑企业 Agent 发生争议时的裁定过程。
22.17 Runtime 资源隔离与并发控制¶
Agent Runtime 同时承载短问答、长报告、工具写操作、文件解析和评测批跑时,资源隔离会决定平台是否稳定。若所有 Run 使用同一个队列和同一组 worker,长任务会拖慢短任务,高成本任务会挤占普通查询,评测批跑也可能影响线上用户。Runtime 需要按任务类型、租户、风险等级和资源预算做并发控制。
并发控制包含限流,也包含排队、拒绝、异步转移和人工审批。平台要定义哪些任务可以排队,哪些任务需要立即拒绝,哪些任务可以转异步,哪些任务需要人工审批。每个 Run 应记录预估成本、队列、优先级、超时、取消策略和恢复方式。用户刷新页面或重复提交时,Runtime 也要通过幂等键识别同一任务,避免创建多个消耗资源的 Run。
早期可以建立四类队列:交互查询、异步报告、高风险审批任务和批量评测。每类队列有独立并发、超时和告警。Trace 记录队列等待时间和执行时间,成本治理章节再按业务线归因。这样 Runtime 的稳定性不再只依赖 worker 数量,而来自清楚的任务分级和资源承诺。
22.18 Runtime 运行证据的跨场景复用¶
Runtime进入生产后,新增能力不能只看功能是否可用,还要看运行证据能否被不同角色复用。平台需要把Run 状态、事件序列、资源配额、恢复动作、取消原因和 owner记录成稳定字段,并和发布单、Trace、评测样本以及事故记录关联起来。这样一次线上问题发生后,团队可以沿着同一组事实判断影响范围、责任归属和修复顺序,而不是在模型日志、业务日志和人工说明之间来回拼接。
这类证据还要服务相邻章节的能力。它和第25章 Planner、第38章 Trace 和第42章 SLO相连:上游能力提供输入假设,下游能力使用执行结果,治理能力负责保存证据和复审结论。若这些材料没有统一编号和版本,章节里讨论的工程能力在生产中会被拆散。业务 owner 只能看到用户投诉,平台 owner 只能看到系统错误,安全或合规团队只能看到事后说明,最后很难判断问题到底来自数据、模型、工具、流程还是组织责任。
生产环境中常见的风险包括不同场景各自定义状态、取消动作没有记录、恢复后缺少幂等证明、队列积压被误判为模型问题。这些问题在演示阶段不明显,因为演示通常只覆盖成功路径;上线后,用户会带来边界问题、重复请求、权限变化和长时间运行状态。平台团队应把失败样本纳入发布节奏,记录哪些样本需要阻断发布,哪些样本可以通过降级处理,哪些样本需要业务 owner 接受剩余风险。
Runtime 证据应成为平台公共资产,支撑排障、容量规划和发布复审。这份记录不需要复杂,但要包含时间、版本、owner、样本、处置动作和下次复查条件。没有这些字段,复盘会停留在口头经验;有了这些字段,平台才能把一次问题转成后续发布、评测和培训材料。
早期平台可以从少量高风险场景开始。先选择调用量高、业务影响大或涉及敏感数据的路径,要求每次变更都留下证据包,再逐步推广到普通场景。这样章节里的能力不会停留在概念层,而会成为可运行、可解释、可退回的工程系统。
22.19 Runtime 事件模型的兼容承诺¶
Runtime 事件模型一旦被前端、Trace、Eval、计费和合规共同使用,就不能随意改字段。事件字段的增加、删除、重命名和语义变化都会影响下游系统。平台需要给事件模型建立兼容承诺:哪些字段稳定,哪些字段实验中,哪些字段将在某个版本废弃,旧事件如何被读取,新旧字段如何共同存在。
兼容承诺可以降低平台演进成本。没有承诺时,每个上层系统都要猜测 Runtime 字段含义,升级后容易出现前端时间线断裂、评测样本无法回放、成本归因丢失或审计证据缺字段。有了承诺,Runtime 团队可以在发布前通知依赖方,给出迁移窗口和回放样本。
早期可以把事件模型当作公共 API。每次变更都要说明影响对象、迁移方式、回放样本和废弃时间。这样 Runtime 不再只是执行器,而是整个平台事实记录的来源。
本章小结¶
Runtime 把一次 Agent 交互变成可观察、可恢复、可审计的 Run。Run、Step 和 Tool Call 分别对应任务、推理轮次和实际工具执行,不能混用。六态状态机是平台对外契约,编排图状态只属于 Planner 的内部实现。succeeded 只能由 Runtime 判定,模型文本或 Planner 的结束意图不能直接决定终态。Agent SSE 也不能只有 token 流,必须用 state、action、result 和审批事件表达真实执行链。检查点要保存状态、上下文、工具结果、Memory 引用和事件位置。只有这些信息完整,Runtime 才能支持恢复、断线重连、审计回放和后续评测。
参考文献¶
Wang, L., Ma, C., Feng, X., et al. (2024). A survey on large language model based autonomous agents. Frontiers of Computer Science, 18(6), 186345. https://doi.org/10.1007/s11704-024-40231-1
Yao, S., Zhao, J., Yu, D., et al. (2023). ReAct: Synergizing reasoning and acting in language models. ICLR. https://arxiv.org/abs/2210.03629
OpenAI. (n.d.). Streaming. OpenAI Agents SDK. https://openai.github.io/openai-agents-python/streaming/
WHATWG. (n.d.). HTML Living Standard: Server-sent events. https://html.spec.whatwg.org/multipage/server-sent-events.html
OpenTelemetry. (n.d.). Tracing API. https://opentelemetry.io/docs/specs/otel/trace/api/
W3C. (2021). Trace Context. https://www.w3.org/TR/trace-context/
LangChain. (n.d.). Persistence. LangGraph. https://docs.langchain.com/oss/python/langgraph/persistence

