跳转到内容

从 Pi Runtime 到企业生产 Agent

把 Agent Loop 包成 HTTP API、部署多个副本,并不会自动得到生产级 Agent。HTTP 服务解决如何进入进程,副本解决如何扩容,但以下问题仍然存在:

  • Worker 在调用外部系统后、写回结果前崩溃,重试会不会重复扣款、重复发信或重复发布?
  • 一个审批等待两天,期间发布了新 prompt、新工具 schema,旧任务应该由哪个版本继续?
  • 用户、Agent、审批人和后台补偿任务分别以谁的身份操作,权限从哪里来?
  • 模型说“完成了”,业务系统如何证明真正的订单、工单或文档已经达到验收条件?
  • 一次执行跨越多个进程、模型调用和人工节点,怎样重建发生过什么,并判断新版是否更好?

业务 Run 是 durable owner;Worker 是可死亡、可重启、可替换的执行器;Agent 是受约束的决策核。

durable 指关键状态离开单进程内存后,系统仍能判断任务进行到哪里、哪些外部动作已经发生、下一步由谁接手。Temporal、OpenAI、OpenTelemetry、NIST 与 Google SRE 的官方资料用于说明机制,不限定具体产品选型。

Pi 源码快照:ccb9464114c2542aad444054f590f06b03a324ac(2026-08-28)。源码路径均相对 Pi 仓库根目录。

Pi 展示了一个 Agent turn 内部的模型/provider 契约、流式事件、工具参数与结果、取消、上下文、session 和产品宿主协议。

“一轮怎样跑”与“一个业务任务怎样活过机器故障、发布和人工等待”不是同一个问题。前者是 Agent Runtime,后者是 Run Platform。

在该快照中,可以把已经成立的能力分成三层:

Pi 源码证据 已经解决的问题 不能仅据此推出的结论
packages/agent/src/agent.ts 的 Agent、AgentOptions 在产品进程内持有 transcript、streaming state、steering/follow-up 队列和 AbortController;用 streamFn、beforeToolCall、afterToolCall 等 seam 注入策略 进程内 activeRun 不是跨 Worker 的业务 Run;AbortSignal 也不是 durable cancellation
packages/agent/src/agent-loop.ts 的 runAgentLoop()、executeToolCalls() 串起模型流、消息、工具执行、事件、停止条件以及串行/并行工具策略 工具完成事件不等于外部副作用已被幂等提交,也不等于有 lease 和故障接管
packages/agent/src/types.ts 的 AgentTool、AgentEvent 定义 schema 驱动的工具执行契约和可订阅的生命周期事件 runtime hook 与 event 是扩展 seam,不自动构成企业授权、审计账本或分布式事务
packages/coding-agent/src/core/sdk.ts 的 createAgentSession(),以及 agent-session.ts 的 _handleAgentEvent() 在产品层组装 model、resource、settings、session 与 Agent;在 message_end 等事件上把消息交给 SessionManager 保存 产品 session 的恢复语义不能替代业务状态机、审批、effect receipt 与领域验收
packages/coding-agent/src/core/session-manager.ts 的 SessionManager 用追加式 JSONL 树保存消息、分支、模型和压缩等会话事实,并可从已有 session 构建上下文 conversation durability 不等于企业业务 Run 的 durability

这组代码已经是一个很扎实的 product runtime:Agent Loop 小而完整,产品层通过事件与注入点补上 session、扩展、压缩和重试策略。生产系统最值得复用的不是“把这个类搬到服务器”,而是这种边界:让 Agent 专注决策协议,让宿主拥有产品策略。

新 harness 提供了重要方向,但不能提前写成完成态

Section titled “新 harness 提供了重要方向,但不能提前写成完成态”

同一 HEAD 里还有一组更接近恢复协议的代码:

  • packages/agent/src/harness/session/types.ts 已定义 OperationStartedRecord、StepAttemptRecord、ToolStartedRecord 等追加式 record;其中工具记录显式区分 replay: "never" | "safe"。
  • packages/agent/src/harness/session/jsonl/storage.ts 的 JsonlSessionStorage 实现先落盘后更新内存、串行追加、原子整文件发布和 torn-tail 修复。
  • packages/agent/src/harness/reducer.ts 的 validateRecordLog() 与 reduceLaneState() 用纯函数从 record/entry 重建 lane 状态;对应行为由 packages/agent/test/harness/reducer.test.ts、packages/agent/test/harness/session/jsonl-storage.test.ts 等测试约束。

这些是值得学习的 durable protocol 基础:先记录 intent,为结果预留稳定 id,再由 reducer 区分“已声明但未完成”和“不可能出现的损坏状态”。但边界也同样清楚:当前 packages/agent/src/harness/agent-harness.ts 在恢复已有记录时仍抛出 HarnessNotImplemented("create.restore"),prompt()、resume()、nextRun()、watch() 等运行路径也仍是 scaffold;packages/agent/test/harness/agent-harness-scaffold.test.ts 明确测试了这些未实现行为。

当前 harness 还不是完成的 durable execution:它的 runId 是 lane 中一次 operation 的 id,不能与租户级、业务级 Run 混为一谈。Pi 已展示从内存 Agent Loop 向记录协议与确定性恢复模型演进的基础,但现有实现尚不能证明跨 Worker 的 Run 所有权、lease、企业权限和生产恢复闭环已经成立。这些职责可以由 Pi、外部平台或业务服务承担。

Run Platform 解决“业务任务怎样活下去”

Section titled “Run Platform 解决“业务任务怎样活下去””

Run Platform 至少要拥有:

  • 持久化的 Run、Step、Attempt、Artifact 与 Audit;
  • durable workflow 或等价的状态机、队列与计时器;
  • lease、heartbeat、timeout、retry、recovery 和补偿;
  • tool gateway、身份、授权、凭据和 effect boundary;
  • 人工审批、暂停、恢复、驳回和接管;
  • 版本路由、历史兼容、回放验证与灰度;
  • trace、metric、log、eval、SLO 和事故审计。

Run Platform 也不应该成为订单、账户、工单或内容的最终事实源。业务系统仍拥有领域实体和不变量;Run 只拥有“为了达成某个业务目标而进行的一次受控执行”。

例如,退款 Agent 的 Run 可以记录它建议退款、获得审批并调用退款工具,但“退款是否真的成功”必须由支付系统的交易状态和回执证明,不能由 assistant message 中的一句“退款已完成”证明。

flowchart LR
    Trigger[API event schedule user]
    RunDB[(Business Run store)]
    WF[Durable workflow or state machine]
    Queue[Task queue]
    Worker[Replaceable Worker]
    Agent[Constrained Agent decision core]
    Gateway[Tool gateway]
    Systems[Business systems]
    Approval[Human approval]
    Evidence[(Artifact and Audit)]

    Trigger --> RunDB
    RunDB <--> WF
    WF --> Queue --> Worker
    Worker --> Agent
    Agent -->|proposed action| WF
    WF -->|policy gate| Approval
    WF -->|authorized command| Gateway --> Systems
    Systems -->|receipt and state| Gateway --> Evidence
    Evidence --> RunDB
    Approval --> WF
    Worker -. can die and be replaced .-> Queue

图中最重要的箭头不是 Agent 到工具,而是业务 Run 与 durable workflow 的双向关系:只要 Run 的事实、等待条件和已提交副作用仍在,任意 Worker 都可以重新领取下一步。

图为了表达职责同时画出了 Run store 与 workflow;实现时必须指定唯一的状态推进权威。若两者都存在,另一个应是幂等投影或通过 outbox 等协议同步,而不是在 Worker 中做无法恢复的双写。

最小持久化模型:Run、Step、Attempt、Artifact、Audit

Section titled “最小持久化模型:Run、Step、Attempt、Artifact、Audit”

下面不是要求立刻建五张表,而是先把语义拆清。可以按现有系统的数据库和工作流引擎映射,但不要把这些概念重新压回一个 status 字段或一段 transcript。

概念 回答的问题 最小关键字段 不应该承担的职责
Run 这次业务目标是什么,现在整体处于什么阶段? run_id、business_key、tenant_id、status、agent_version、policy_version、input_snapshot_ref、预算、deadline 不保存每次执行的瞬时锁;不冒充业务实体本身。
Step Run 的一个逻辑阶段是什么? step_id、run_id、kind、status、依赖、输入/输出引用、风险级别 不把每次重试覆盖成同一行日志。
Attempt 某 Step 被谁执行了第几次,结果如何? attempt_no、worker_id、lease_until、lease_token、heartbeat_at、开始/结束时间、错误类别、模型/工具版本 不等同于 Step;失败 Attempt 可以对应最终成功的 Step。
Artifact 哪些内容或回执可供后续使用和验收? 类型、URI/对象键、checksum、schema version、producer、敏感级别、retention 不把大文件和秘密直接塞进 workflow history 或 trace。
Audit 谁在何时基于什么授权做了什么? actor、subject、action、resource、decision、policy、request/response digest、correlation id 不只记录成功;拒绝、超时、人工修改和管理员恢复同样要记录。

人工审批可以作为独立的 Approval 实体,也可以是特殊 Step。关键不在表名,而在它必须拥有独立身份、版本和时间线:谁申请、谁有权批准、看到的 action payload 是哪个版本、批准是否过期、实际提交是否仍与所批内容一致。

transcript 适合承载模型可见的对话上下文;Run 还必须承载模型不应该决定或不应该看到的控制事实,例如:

  • 租户、业务键、权限主体和数据地域;
  • 已消耗预算、剩余重试、deadline 和 SLO 类别;
  • 已完成副作用的幂等键与业务回执;
  • 审批版本、审批人和驳回原因;
  • 当前代码、prompt、model、tool schema 和 policy 版本;
  • 最终验收规则及其机器可验证结果。

因此,恢复不能只做“加载聊天记录,再问模型继续”。恢复必须先重建 Run 的控制状态,再根据已提交事实决定是否、从哪里调用 Agent。

“生成退款建议”是一个 Step;第一次模型调用超时、第二次成功,是两个 Attempt。把二者分开有三个直接收益:

  1. 重试不会抹掉失败证据;
  2. Step 的业务状态不会和 Worker 的瞬时状态混在一起;
  3. 可以分别统计“最终成功率”和“首尝试成功率”。

这也对应 Temporal 官方文档中的区分:一个逻辑 Activity Execution 可以包含多个 Activity Task Execution,每次重试是一个新的 attempt;Activity Retry Policy 作用于失败的 task attempt,而不是假装前一次从未发生。参见 Detecting Activity failures 与 Retry Policies。这里借用的是语义,不要求你的实现必须使用 Temporal。

确定性工作流外壳与 Agent 决策核

Section titled “确定性工作流外壳与 Agent 决策核”

生产 Agent 最大的架构边界,是不要让模型接管本应由确定性系统拥有的控制平面。

问题 确定性 workflow / platform Agent 决策核
Run 状态与下一可运行 Step 按已提交事件和状态机推进 可以建议计划,不能直接改 durable state
重试、退避、timeout、deadline 根据错误类别和策略执行 可以解释失败或提出替代方案
lease、heartbeat、Worker 接管 平台维护所有权和过期规则 不感知或不决定分布式锁
身份、权限、凭据 gateway 绑定主体并强制校验 只能从被允许的能力集合中选择
高风险动作审批 平台创建并验证 approval 生成理由、影响摘要和候选 action
幂等和副作用提交 effect boundary 生成稳定 key、提交、查账 生成参数,但不能宣称提交成功
非结构化理解 提供输入与约束 提取意图、归纳证据、识别缺失信息
局部计划和工具选择 限制可选分支和预算 在合法分支中选择下一步
结果检查和局部修复 给出机器验收器和上限 解释失败、修改候选内容、请求人工介入
最终业务成功 读取领域状态并运行 acceptance check 可以提供结论,但不是最终裁决者

Temporal 的 Workflow Definition 明确要求 replay 路径中的 workflow code 保持确定性,并把 API、LLM、数据库等非确定性交互放到 workflow 外的 Activity。这是 durable workflow 的一种具体实现方式。更一般地说,即使你用数据库状态机与消息队列自建平台,也需要维持同样的边界:状态推进可重放,外部 I/O 有显式 effect boundary。

Agent 输出应是 Proposal,而不是 Commit

Section titled “Agent 输出应是 Proposal,而不是 Commit”

让模型直接返回 done、failed 或任意下一状态,会把控制平面交给非确定性输出。更安全的协议是让 Agent 返回有限的 proposal:

type Decision struct {
Kind string `json:"kind"` // call_tool, ask_human, finish_candidate
ToolName string `json:"tool_name,omitempty"`
Arguments json.RawMessage `json:"arguments,omitempty"`
Reason string `json:"reason"`
Confidence float64 `json:"confidence,omitempty"`
}

平台收到 proposal 后仍要执行 schema validation、policy check、预算检查、approval check 和 acceptance check。finish_candidate 表示“Agent 认为可能完成”,而不是 Run 已经 SUCCEEDED。

不要在 workflow replay 中重新问模型

Section titled “不要在 workflow replay 中重新问模型”

模型输出不是确定性的。replay 时重新调用同一个模型,即使 prompt 相同,也可能得到不同文本、工具参数和成本。因此要把一次模型调用视为外部 activity/step:其请求版本和最终结果作为事实记录;workflow replay 只消费已经记录的结果,不重新生成结果。

这并不要求保存所有 chain-of-thought。对恢复真正必要的是协议级事实:输入引用、模型与 prompt 版本、结构化输出、工具 proposal、token/cost、stop reason、错误类别和关联 trace。敏感内容可以独立存储并设置更严格的访问与保留策略。

Durable workflow、队列与 Worker 所有权

Section titled “Durable workflow、队列与 Worker 所有权”

“Worker 可替换”不等于进程里不能有 cache、连接池或当前 Attempt 的局部状态。它意味着:

  • 丢掉任意 Worker 后,不会丢掉 Run 的唯一真相;
  • 另一个 Worker 能在 lease/timeout 后安全接手;
  • 本地 cache 只能加速,不能决定业务正确性;
  • 进程退出不会让人工等待、计时器或重试计划消失。

Temporal 官方把 durable execution 描述为在 crash、网络失败或基础设施故障后继续执行;其官方文档首页给出了这一产品承诺。其他工作流引擎、云队列加数据库,或自建事件驱动状态机也可以实现相近目标,但需要自己明确事件持久化、定时器、可见性、重试和版本语义。

一个可靠的任务信封至少应包含:

run_id, step_id, attempt_no
tenant_id, capability_scope
agent_version, policy_version, tool_contract_version
deadline, retry_class
trace_context

消费者领取任务时,不应该仅凭消息体开始执行。它要在 Run store 中原子地验证:Step 仍可执行、当前 attempt 没有被完成、lease 能成功获取、版本可用、租户和 capability scope 匹配。消息可能重复、延迟或乱序;状态检查才是执行资格的最终判断。

Lease、heartbeat、timeout 各自解决什么

Section titled “Lease、heartbeat、timeout 各自解决什么”
机制 作用 不能证明什么
lease 在一段时间内声明某 Worker 拥有 Attempt 不能证明 Worker 仍健康,也不能证明外部副作用未发生
heartbeat 报告 Attempt 仍在推进,可附进度 checkpoint 不能替代业务回执,也不能无限延长 deadline
start-to-close timeout 限制一次 attempt 最长执行时间 超时不代表远端请求一定取消
schedule-to-close / run deadline 限制包含重试在内的总时间 不决定永久错误与瞬时错误分类
retry policy 为允许重试的错误安排下一 Attempt 不自动让非幂等副作用变安全

Temporal 的 Go Activity timeouts 与 heartbeats说明:heartbeat 可以携带进度,重试 attempt 能读取最后已送达的 heartbeat details;missed heartbeat 可触发失败检测与后续重试。官方文档也提醒,heartbeat 可能被 Worker 节流,且 Worker 崩溃前尚未送达的本地进度不会神奇地持久化。因此心跳是有损进度 checkpoint,不是事务提交记录。

lease 还要配合单调递增的 lease_token(也常称 fencing token)。Worker 每次 heartbeat、写 Artifact 或提交 Step 都携带该 token;新 Worker 接管后 token 前移,Run store 必须拒绝旧 Worker 的迟到提交。Tool Gateway 只有在提交前针对同一个权威状态原子校验 token 并占用 effect key 时,才能作出同样保证。否则旧 Worker 只是“被认为死亡”,它恢复网络后仍可能覆盖新 Attempt 的结果。

但 fencing token 也有边界:它能阻止网关接受一个已经失去所有权的新提交,却不能召回 lease 过期前已经发往第三方的网络请求。旧、新 Worker 仍可能分别观察到未知结果,所以 effect key、远端幂等或 reconciliation 依然不可省略。

Effect boundary:不要相信“Exactly Once”口号

Section titled “Effect boundary:不要相信“Exactly Once”口号”

考虑一个退款工具。外部支付系统已经接受退款,但 Worker 在把成功回执写回 Run store 前崩溃:

sequenceDiagram
    participant W1 as Worker A / Attempt 1
    participant P as Payment API
    participant R as Run Store
    participant Q as Queue
    participant W2 as Worker B / Attempt 2

    W1->>R: acquire lease(step=refund)
    W1->>P: POST refund(idempotency_key=run/step)
    P-->>W1: accepted, receipt=pay_789
    Note over W1: process crashes before recording receipt
    R-->>Q: lease expires / timeout schedules retry
    Q->>W2: deliver Attempt 2
    W2->>R: acquire new lease
    W2->>P: POST refund(same idempotency_key)
    P-->>W2: same operation / existing receipt
    W2->>R: commit receipt and Step success

如果第二次请求使用新随机键,或远端根本不支持幂等,系统可能发生重复退款。队列的“至少一次投递”和数据库的事务都无法跨越任意第三方 API 自动提供 exactly once。

Temporal 的 Activity Definition / Idempotency 对这个窗口有更直接的说明:Activity 可能被执行多次,甚至部分成功多次;Worker 在完成副作用后、通知服务端前崩溃会触发重试。官方建议写 Activity 保持幂等,并指出幂等键最终由被调用服务执行去重。换句话说,durable orchestration 记录“应该继续做什么”,外部系统仍要参与保证“同一逻辑动作不会重复生效”。

对有外部副作用的工具,建议固定为五段式:

  1. prepare:生成规范化 action、风险级别、资源目标和稳定 idempotency_key;
  2. authorize:基于当前主体、租户、资源和 action 做 policy check;
  3. approve:需要人工时持久化完整候选 action,等待批准;
  4. commit:通过 tool gateway 调用外部系统;
  5. reconcile:记录回执,并从领域系统查询最终状态进行验收。

稳定键通常来自业务语义,例如 tenant/run/step/effect_kind,而不是来自 Attempt。Attempt 会变化,但同一个逻辑副作用不能因为重试获得新身份。

外部系统能力 推荐处理 残余风险
支持原生幂等键和按键查询 每次重试使用同一 key;超时时先查询再决定是否重发 仍要核对远端幂等窗口、payload 冲突规则和保留时长
不支持幂等键,但支持读取业务状态 提交前后都读取;用业务唯一约束或 outbox/inbox 协调 读写之间仍有竞争,需要领域侧约束
既不支持幂等也无法可靠查询 不自动重试;进入人工 reconciliation 或补偿流程 无法工程化消除重复,只能降低和处置风险

所谓补偿也不是“撤销历史”。发一封更正邮件、创建反向交易、恢复旧文档版本,都是新的、需要审计的副作用。

Tool Gateway:把能力、身份和凭据挡在 Agent 外面

Section titled “Tool Gateway:把能力、身份和凭据挡在 Agent 外面”

Agent 看到的是有限的 capability,而不是数据库 root 密码、云账号密钥或用户完整 OAuth token。Tool Gateway 至少完成:

  • 将 tenant + actor + delegated subject + run 绑定为执行身份;
  • 按工具、动作、资源、字段和风险级别做授权;
  • 注入短期凭据,避免把秘密放进 prompt、transcript、trace 或 Artifact;
  • 校验输入 schema、大小、域名、路径、SQL/命令范围和输出 schema;
  • 执行 rate limit、预算、并发、超时、隔离和 egress policy;
  • 写入追加式、可关联的审计事件与外部回执摘要;若业务要求防篡改,再增加签名、WORM 存储或独立审计域。

OpenAI 的生产 Agent 实务指南也明确把 guardrails 视为分层防御,并指出它们要与认证、授权、访问控制和标准软件安全措施一起使用。换言之,模型 guardrail 不能替代企业 IAM。

身份也不能只写一个 user_id。至少要区分发起人、被代理主体、审批人和实际执行的 Worker/service identity,并把它们与目标租户、资源和 action 一起授权。NIST 的 SP 800-207 Zero Trust Architecture强调不能因网络位置而隐式信任访问者;面向云原生应用的 SP 800-207A进一步讨论了应用与服务身份的细粒度策略。这些原则落到 Agent 系统,就是“模型提出动作,网关依据当前身份与资源再次裁决”。

OpenAI Agents SDK 的Guardrails 官方文档区分 input、output 与 tool guardrails,并特别说明并行 input guardrail 可能在拦截前已经消耗 token 或执行工具;需要避免副作用时,应使用 blocking 模式或 tool 执行前 guardrail。

还要注意覆盖范围:当前 SDK 文档明确说 tool guardrail 只覆盖 function_tool 管线,handoff、hosted tool 和若干 built-in execution tool 并不统一经过它。它适合做 runtime 内的局部防线,不能替代所有工具必经的企业 Tool Gateway。

这一事实可以推广成一个简单原则:

可能影响外部世界的检查,必须位于 effect boundary 的同步阻塞路径;异步扫描适合补充发现,不适合承担最后一道授权。

风险分级比“所有工具都审批”更实用

Section titled “风险分级比“所有工具都审批”更实用”

一个可落地的起点是:

级别 示例 默认策略
L0 只读低敏 查询公开知识、读取已授权工单摘要 自动执行,保留审计
L1 可撤销低风险写 创建草稿、添加内部标签 规则校验后自动执行,可回滚
L2 重要业务写 发客户邮件、修改订单、发布内容 细粒度 policy,早期阶段人工批准
L3 高风险或不可逆 付款、删除、权限变更、法律承诺 强制人工审批,必要时双人复核

风险级别应由工具和 action 决定,不能让模型自己给自己的操作降级。

Human-in-the-loop 是 durable state,不是弹窗

Section titled “Human-in-the-loop 是 durable state,不是弹窗”

审批人需要看到并批准一个明确对象,而不是一句“Agent 想执行某操作”。最少应冻结:

  • Run、Step、tool call 与 action version;
  • 目标资源、关键参数、影响范围和风险解释;
  • Agent、prompt、model、tool contract 和 policy 版本;
  • 相关 Artifact 的不可变引用或 digest;
  • 过期时间、所需角色、是否允许自批、是否要求双人复核。

审批通过后,commit 前还要检查“待提交 action 是否与所批版本一致”。如果 Agent 在等待期间改了收件人、金额或文件内容,旧审批必须失效或重新申请。

OpenAI Agents SDK 的 Human-in-the-loop 文档展示了一个具体机制:工具调用产生 interruption,运行状态可序列化,审批后从 RunState 恢复;官方还建议给可能长期等待的任务同时保存 Agent 定义或 SDK 的版本标记。这证明 SDK 可以提供一个 pause/resume 边界,但企业系统仍要自行拥有审批身份、SLA、通知、越权防护和业务 Run 状态。

除高风险工具外,至少在这些条件触发人工介入:

  • Attempt、工具调用、token、成本或墙钟时间超过预算;
  • 连续得到相同错误,或模型在两个方案之间循环;
  • 输入证据冲突、缺失或来自低可信源;
  • acceptance check 失败,但继续修复会扩大影响;
  • 模型/策略判定不确定,且错误代价高;
  • 需要使用 Agent 当前未获授权的 capability。

人工接管不是 Run 失败。它是一个显式状态:等待谁、需要什么信息、何时超时、超时后升级给谁,都应可查询和审计。

生产 Run 的“代码版本”远远不够,至少要能定位:

  • workflow definition / state machine version;
  • Agent instructions 或 prompt bundle version;
  • model/provider 及关键推理参数;
  • tool list、schema、执行语义和 gateway policy version;
  • evaluator / acceptance rule version;
  • Artifact schema 与数据快照版本。

版本字段不是为了把所有历史实现永久在线,而是让系统能做三个决定:旧 Run 继续走旧版本、在显式迁移点升级,或停止并要求人工处理。

正确的 replay 用已记录事件重建控制状态;它不应重新调用 LLM、重发邮件或重新扣款。对外部调用,replay 消费的是已经记录的结果或 failure;真正的 retry 则创建新的 Attempt,并经过幂等和授权边界。

这一区分决定了“故障恢复测试”不能只重启进程看是否又跑起来,还要断言:

  • replay 没有产生新的外部调用;
  • retry 使用同一逻辑 effect key;
  • 已审批 action 没有被新版工具偷偷改变;
  • 新旧 workflow 代码对历史事件的解释一致,或存在明确迁移。

Temporal 的 Worker Versioning提供了一个具体例子:可以把 workflow pin 到启动时的 Worker Deployment Version,或选择 auto-upgrade 并自行维持 replay safety;官方还提供 ramping 与 rollback。可迁移的原则是:运行版本和流量路由都属于 durable 语义,具体部署模型则可以替换。

对产品无关的平台,可以采用以下任一组合:

  • pinned:Run 或某阶段固定在创建时版本,旧 Worker 排空后下线;
  • compatible replay:新版代码能解释旧事件,并由 replay test 保证;
  • migration checkpoint:只在显式安全点转换状态并升级版本;
  • manual quarantine:无法安全迁移的少量旧 Run 进入人工队列。

最危险的做法是只更新 Deployment,然后让所有未完成 Run 自然落到新版代码上,却没有任何版本字段或历史回放测试。

Observability:从一次请求上升到完整 Run

Section titled “Observability:从一次请求上升到完整 Run”

推荐的层次是:

business_run
└── workflow_step
├── invoke_agent
│ ├── model_inference
│ ├── plan_or_decision
│ └── execute_tool
├── policy_check
├── approval_wait
└── acceptance_check

OpenTelemetry 的 GenAI agent semantic conventions 目前标记为 Development,定义了 invoke_agent、invoke_workflow、plan、execute_tool 等 span 语义;见官方仓库的 Agent and framework spans。因此可以沿用这些名字建立互操作性,但在标准稳定前应隔离字段映射,避免把仍在演进的属性变成内部数据库主键。

默认不要把 prompt 和工具结果全塞进 trace

Section titled “默认不要把 prompt 和工具结果全塞进 trace”

OpenTelemetry 的 GenAI spans content guidance明确指出 instructions、input 和 output 可能敏感且体积很大,instrumentation 不应默认采集;生产环境可以把内容放到有独立访问控制的外部存储,只在 span 中保留引用。

因此建议分三层记录:

  1. 默认 telemetry:Run/Step/Attempt id、版本、耗时、token、成本、状态、错误类型、tool name,不含原文;
  2. 受控诊断 Artifact:脱敏后的 prompt/response、tool arguments/result,按租户与事件授权访问;
  3. 高敏原始数据:仅在业务确有需要时保存,单独加密、审计和设置 retention。

Metric 必须同时看业务正确性与系统可靠性

Section titled “Metric 必须同时看业务正确性与系统可靠性”

只看 token、延迟和 HTTP 500,会错过“技术成功、业务错误”。至少分四组:

维度 示例
业务结果 Run acceptance rate、正确完成率、错误完成率、人工接受/修改/接管率
副作用安全 duplicate effect rate、reconciliation mismatch、越权拦截、审批后 payload 漂移
durable reliability Worker crash recovery rate、stuck Run、lease timeout、retry exhaustion、恢复耗时
效率 P50/P95 完成时间、模型/工具调用数、token、成本、队列等待、人工等待

“错误完成率”尤其重要:Run 标记 SUCCEEDED,但业务验收失败,比显式报错更危险。

Evals 与 SLO:模型质量和平台可靠性分开验

Section titled “Evals 与 SLO:模型质量和平台可靠性分开验”
  1. Decision eval:给定同一业务输入,是否选对工具、参数、是否正确请求审批或拒绝;
  2. Trajectory eval:整条 trace 是否出现无效循环、错误 handoff、越权 proposal 或不必要工具;
  3. Outcome eval:最终领域状态是否满足业务 acceptance,且副作用、成本和人工介入在允许范围内。

OpenAI 的 Evaluate agent workflows建议先用 trace grading 定位 workflow-level 问题,再把已经明确的“好”固化为 dataset 与重复 eval run。这里最值得迁移的不是某个评分 API,而是从真实轨迹发现失败,再把失败沉淀成回归集。

离线 eval 集至少包含:正常样本、边界样本、历史事故、对抗输入、权限差异、工具超时、重复消息和旧版本 Run。LLM-as-judge 可以辅助评价开放式结果,但高风险 outcome 仍应优先使用领域规则、真实回执和人工标注。

SLO 应从用户真正感知的服务结果出发,而不是从现有 metric 里挑一个好看的数字;可参照 Google SRE 的官方章节 Service Level Objectives。以下阈值仅用于说明设计方法,不是行业统一标准:

  • 平台 SLO:99.9% 的可重试 Run 不因单 Worker 崩溃永久丢失;P95 在 lease/heartbeat timeout 后 2 分钟内重新调度;重复副作用低于业务定义阈值;
  • 产品 SLO:已支持场景的正确完成率达到目标;错误完成率低于上限;L2/L3 动作审批覆盖率为 100%;成本和完成时间在预算内。

平台 SLO 绿色不能证明模型做对了;eval 分数变高也不能证明 crash recovery、权限和幂等正确。两者要在发布门禁里相遇。

一个更稳健的发布顺序是:

flowchart LR
    Offline[Offline replay and eval]
    Shadow[Shadow: no external effects]
    Draft[Draft only]
    Approve[Human-approved writes]
    Canary[1 to 5 percent canary]
    Expand[Expand by tool and tenant]

    Offline --> Shadow --> Draft --> Approve --> Canary --> Expand
    Canary -. regression or incident .-> Approve
    Approve -. policy failure .-> Draft

不要一开始就开放所有工具。先从单 Agent、少量明确工具、只读或草稿开始;用真实 Run 建立 eval baseline,再开放可撤销写操作;高风险动作长期保留人工批准。

故障注入要打在 effect boundary 两侧

Section titled “故障注入要打在 effect boundary 两侧”

至少覆盖这些实验:

  • Worker 在模型返回前、返回后分别被 kill;
  • tool commit 前 kill、远端成功后但本地记账前 kill;
  • heartbeat 丢失、队列重复投递、消息延迟和乱序;
  • 数据库写入超时但结果未知;
  • approval 等待期间部署新 Agent/tool/policy 版本;
  • 外部 API 返回 429、5xx、永久业务错误和畸形成功响应;
  • Artifact 写成功但 Run 更新失败,或反过来;
  • trace exporter 故障,验证 observability 失败不会阻断核心业务,也不会丢失必要 Audit。

每个实验都要断言最终领域状态和审计证据,不能只断言 Worker 进程恢复、Run 最终变绿。

以“审核合同后发送客户邮件”为例。这里先声明案例假设:企业自有邮件网关维护以 effect key 为唯一键的 durable ledger,并能查询供应商已经接受的 message id;这不是所有邮件供应商的通用保证。

时间 事件 durable 事实 系统动作
10:00:00 创建 Run 输入合同 Artifact、tenant、actor、版本被冻结 workflow 安排 analyze Step
10:00:03 Attempt 1 调用模型 记录 model/prompt version 与 trace id Agent 提议 send_email,风险 L2
10:00:08 policy 要求审批 Approval 保存收件人、主题、正文 digest、附件 ref Run 进入 WAITING_APPROVAL,Worker 释放
次日 09:12 审批通过 actor、role、action version、decision 写 Audit workflow 创建 commit Step;审批只代表业务同意
09:13 Worker A 准备提交 重新读取当前身份与 policy,验证 action digest 未变,获取短期凭据 gateway 用稳定 effect key 调用供应商;供应商返回 message id,Worker 随后被 kill
09:15 lease 过期 Attempt 1 标记 timeout,Step 仍未确认 Worker B 获得 Attempt 2
09:15:02 Worker B reconciliation 再次授权后,从网关 effect ledger 按 key 查到相同 message id 不重发,写入回执 Artifact
09:15:04 acceptance check 网关确认供应商已接受、收件人与正文 digest 匹配 若本场景以 accepted 为验收口径,Step、Run 才进入 SUCCEEDED

这条时间线体现了完整边界:Agent 决策、人工批准、外部提交和最终验收是四个不同事实。

accepted、sent 与 delivered 也不是同一状态。如果业务承诺的是送达,Run 还要等待供应商回调或后续查询,不能拿 message id 代替送达事实。

先定义模块边界,不先选工作流产品

Section titled “先定义模块边界,不先选工作流产品”

一个最小 Go 服务可以从这些 package 边界开始:

internal/run Run, Step, Attempt state and repository
internal/workflow deterministic transition rules and scheduling ports
internal/agent model invocation and constrained Decision protocol
internal/tool capability catalog and gateway client
internal/policy authorization, risk classification and approval rules
internal/artifact immutable payload references and retention
internal/audit append-only business security events
internal/eval fixtures, graders and acceptance checks

如果采用 Temporal,internal/workflow 可以由 Temporal Workflow 承载,模型与外部工具放入 Activities;如果采用数据库状态机加队列,这个 package 则实现同样的 transition contract。领域 package 不应该 import 某个具体模型 SDK。

type DecisionKernel interface {
Decide(ctx context.Context, in DecisionInput) (Decision, error)
}
type EffectOwner struct {
RunID string
StepID string
AttemptID string
LeaseToken int64
}
type ReconcileState string
const (
EffectFound ReconcileState = "found"
EffectAbsent ReconcileState = "confirmed_absent"
EffectPending ReconcileState = "pending"
EffectUnsupported ReconcileState = "unsupported"
)
type ReconcileResult struct {
State ReconcileState
Receipt *Receipt
}
type ToolGateway interface {
Prepare(ctx context.Context, req ToolRequest) (PreparedAction, error)
Commit(ctx context.Context, action PreparedAction, authz Grant, owner EffectOwner) (Receipt, error)
Reconcile(ctx context.Context, action PreparedAction) (ReconcileResult, error)
}
type RunRepository interface {
Load(ctx context.Context, runID string) (Run, error)
LoadFrozenAction(ctx context.Context, stepID string) (ActionEnvelope, error)
AcquireAttempt(ctx context.Context, stepID string, expectedVersion int64, lease time.Duration) (Attempt, error)
Heartbeat(ctx context.Context, attemptID string, leaseToken int64, progress Progress) error
CommitStep(ctx context.Context, attemptID string, leaseToken int64, result StepResult) error
}

这些 interface 的价值是暴露所有权:DecisionKernel 不提交业务副作用;ToolGateway 不决定 Run 状态;RunRepository 不调用模型。不要为了“未来多厂商”再抽象一层万能 ProviderManager,先让一个端到端 Run 可恢复、可审计。

EffectOwner 不是让 Gateway “相信 Worker 传来的 token”。最小正确实现可以让 lease row 与 effect ledger 由同一个权威服务/数据库拥有:Gateway 用 run_id + step_id + lease_token 做一次条件写,原子验证当前 token 并把 effect_key + payload_digest 标成 CLAIMED。若两者分属独立系统,就需要等价的线性化 claim 协议;一次“先查询 token,再写网关数据库”的普通双调用仍有竞争窗口。做不到时,不应声称已经 fencing,只能依靠稳定幂等键与 reconciliation 降低风险。

Decision Step 与 Commit Step 必须分开

Section titled “Decision Step 与 Commit Step 必须分开”

Decision Step 调用 DecisionKernel,经 Prepare() 规范化出包含 effect key、payload digest 和 tool contract version 的 PreparedAction,然后把它持久化。需要审批时,Approval 绑定这份不可变 action;后续 commit 重试只能加载它,不能重新问模型再生成一份“差不多”的动作。

下面只展示 Commit Step。它每次真正提交前都重新执行当前授权;昨天的审批是对固定业务动作的同意,不是永不过期的 IAM grant。attempt.MustReconcile 由 AcquireAttempt() 在领取 lease 的同一原子读写中,从 durable Step/effect 状态计算出来;不能相信可能重复、延迟或乱序的 queue task 自带布尔值:

func ExecuteCommitStep(ctx context.Context, task Task) error {
attempt, err := runs.AcquireAttempt(ctx, task.StepID, task.RunVersion, 30*time.Second)
if err != nil {
return err
}
envelope, err := runs.LoadFrozenAction(ctx, task.StepID)
if err != nil {
return err
}
if !envelope.ActionMatchesDigest() {
return nonRetryable(ErrActionDrift)
}
// 审批后仍按当前身份、资源和 policy 重新授权,并签发短期 grant。
grant, err := policy.Authorize(ctx, envelope.Action, task.Subjects)
if err != nil {
return err
}
if !envelope.ApprovalSatisfies(grant) {
return approvals.Pause(ctx, attempt, envelope.Action)
}
owner := EffectOwner{
RunID: task.RunID, StepID: task.StepID,
AttemptID: attempt.ID, LeaseToken: attempt.LeaseToken,
}
var receipt Receipt
if attempt.MustReconcile {
known, err := tools.Reconcile(ctx, envelope.Action)
if err != nil {
return err
}
switch known.State {
case EffectFound:
if known.Receipt == nil {
return requireManualReconciliation(envelope.Action)
}
receipt = *known.Receipt
case EffectPending:
return retryLater(known)
case EffectUnsupported:
return requireManualReconciliation(envelope.Action)
case EffectAbsent:
// 只有远端能够可靠证明“未发生”时才允许继续提交。
default:
return requireManualReconciliation(envelope.Action)
}
}
if receipt.ID == "" {
receipt, err = tools.Commit(ctx, envelope.Action, grant, owner)
if unknownOutcome(err) {
return EffectOutcomeUnknown{EffectKey: envelope.Action.EffectKey}
}
}
if err != nil {
return err
}
if err := acceptance.Check(ctx, envelope.Action, receipt); err != nil {
return err
}
return runs.CommitStep(ctx, attempt.ID, attempt.LeaseToken, StepResult{Receipt: receipt})
}

这是边界示例,不是可直接复制的完整实现。EffectOutcomeUnknown 必须由 workflow 归类并持久化,使下一个 AcquireAttempt() 返回 MustReconcile = true;不能只把它当普通 Go error 丢回队列。Gateway 应先按上述原子协议用 owner token 占用 effect ledger,再按稳定 effect key 调外部系统;即便如此,已发出的远端请求仍只能靠幂等与 reconciliation 收敛。真实代码还要处理 heartbeat goroutine 的退出、乐观锁、context cancellation、panic recovery、Artifact 写入顺序和 Audit,但不要让这些细节重新渗入 DecisionKernel。

Go 与 Pi/TypeScript 类比的失效边界

Section titled “Go 与 Pi/TypeScript 类比的失效边界”
从 Pi 学到的概念 Go 中可映射为 不能机械翻译的地方
Agent Loop 与事件 显式状态机、typed event、context.Context 一个 goroutine 加 channel 不会自动提供 durable replay
schema 驱动工具 struct/JSON Schema validator + gateway Go struct 类型安全不等于外部 JSON 已授权或已运行时校验
AbortSignal / cancellation context.Context 取消是协作信号,远端副作用可能已经提交
session/transcript message store / Artifact 对话历史不是 Run、approval、effect receipt 的替代品
stream/provider seam 小型 client interface provider 抽象不应该吞掉重试、幂等和业务验收
  • 只选一个业务场景和一个 Agent;
  • 3–5 个有明确 schema 的工具;
  • Run、Step、Attempt 和 Audit 入库;
  • Worker 通过队列领取,支持 deadline 和有限重试;
  • 只读或只产出草稿,不直接写关键业务系统。

验收:任意 Run 能回答“输入是什么、谁执行、用了哪个版本、为什么结束、最终业务结果是什么”。

  • Tool Gateway、稳定 effect key、外部回执 Artifact;
  • lease、heartbeat、retry classification 和 reconciliation;
  • 人工审批可跨进程暂停恢复;
  • kill Worker 的故障注入成为持续测试。

验收:在“远端成功、本地记账前崩溃”的窗口中,系统不产生重复副作用,并能重建最终结果。

  • trace 与 eval dataset;
  • model/prompt/tool/policy 全版本化;
  • shadow、draft、人工提交、canary、按租户与工具逐级灰度;
  • 产品 SLO、平台 SLO 和自动回滚门禁;
  • 根据真实失败扩大 guardrail,而不是预先发明万能治理框架。

验收:每次升级都能说明它改善了哪些 eval/SLO、引入了哪些风险、如何回滚,以及哪些旧 Run 仍由旧版本处理。

Pod 自动重启只能恢复进程数量;如果 Run、审批、幂等键和已完成副作用仍只在内存中,业务任务仍会丢失或重复。

聊天记录没有可靠表达 lease、Attempt、授权主体、审批版本、业务回执和 acceptance。把这些控制事实塞进 system prompt 也不会让它们变成事务状态。

模型可以提出“我需要发送邮件”或“这个错误可能是瞬时的”,但最终 capability、授权、错误分类、重试次数和预算必须由平台策略裁决。

HTTP 200 可能只表示接收请求,不表示业务完成。必须保存业务回执并运行领域 acceptance;异步任务还需要轮询、回调或事件完成 Step。

权限拒绝、schema 错误、业务不变量失败和明确无效输入通常不是瞬时错误。Temporal 官方 Retry Policies也区分 non-retryable error,并建议把可能失败的外部操作放在 Activity 级别重试,而不是盲目重启整个 workflow。

最终文本看似正确,不代表工具选择、权限、成本和副作用路径正确。生产 eval 必须同时检查 trajectory 与 outcome。

NIST AI RMF 1.0 Core把风险管理组织为 Govern、Map、Measure、Manage 四个函数,并强调它们不是按顺序执行的一次性 checklist,治理贯穿其他环节。映射到生产 Agent:

AI RMF 函数 在 Agent 平台中的具体问题
Govern 谁拥有风险、哪些工具禁止自动化、谁能审批、事故如何升级、Audit 保存多久?
Map 这个 Agent 服务谁、接触什么数据、影响哪些业务实体、错误会伤害谁?
Measure eval、错误完成率、越权拦截、重复副作用、人工接管、成本和恢复指标表现怎样?
Manage 是否继续发布、降级到草稿/只读、回滚版本、暂停工具、补偿或通知受影响用户?

框架给出治理语言,不替你决定具体阈值。阈值必须来自业务影响、合规要求和可验证的运行数据。

在把一个 Pi 风格的 Agent Runtime 接入真实业务前,应能对下面的问题给出可验证答案:

  • Run 是否有独立于 Worker 和 transcript 的 durable identity?
  • Step 与 Attempt 是否分离,失败重试是否保留完整证据?
  • replay 是否只重建控制状态,不重放 LLM 与外部副作用?
  • 每个写工具是否有 effect key、授权、回执与 unknown-outcome 处理?
  • lease、heartbeat、timeout 和 retry 的职责是否明确?
  • Worker 被 kill 后,另一个 Worker 是否能安全接手?
  • Agent 是否只生成受 schema 约束的 proposal,而不是直接提交 durable state?
  • 高风险 action 是否冻结内容后审批,commit 前是否验证未漂移?
  • prompt、model、tool、policy、workflow 和 evaluator 是否可定位版本?
  • trace 是否能串起 Run、Step、Agent、tool、approval 和 acceptance,同时默认保护敏感内容?
  • eval 是否覆盖 decision、trajectory 和 outcome?
  • SLO 是否同时包含平台可靠性与业务正确性?
  • 灰度和故障注入是否覆盖“外部成功、本地未知”这一最危险窗口?

如果这些问题没有答案,系统可能已经是一个好用的 Agent demo,却还不是一个可靠的企业生产 Agent。