Agent Framework 的价值,不在于把模型、工具和 Prompt 包进更多类,而在于把影响正确性、恢复和副作用安全的控制语义,从隐含约定变成可验证契约。
引言:每一步都“合理”,为什么系统还是重复发布了?
设想一个多仓库源码研究与文章发布 Agent。它接收研究问题,读取五个代码仓库和官方文档,形成证据包,生成草稿,等待人工批准,然后发布文章并保存回执。
一次运行中,五个仓库研究任务并行执行。三个分支把结果写进同一个 research_result 字段,最后完成的分支覆盖了前面已经核验的证据。编辑 Agent 没有发现覆盖,仍然生成了草稿。人工批准了 revision 7,发布工具也成功返回;但 checkpoint 只保存到“准备发布”。进程恢复后,Agent 根据旧 checkpoint 再次调用发布工具。
这不是某个真实生产事故,而是本文用于检验设计的受控案例。它故意把三类常被混在一起的问题放到同一条链上:
- 并行分支怎样合并;
- 谁拥有继续推进的控制权;
- 外部副作用怎样确认和恢复。
图 1|一次“每一步都合理”的执行,仍可能因为缺少运行时契约而覆盖证据并重复发布。
导读图|六类稳定制品通过显式引用链形成运行时契约。
案例最终使用的制品链是:
research_plan
-> source_records
-> evidence_pack {
EvidenceBundle(repo_id, source_revision, artifact_hash)
EvidenceSet(required bundle manifest)
}
-> article_draft(revision, evidence_manifest_hash, content_hash)
-> review_decision(draft_revision, content_hash, destination)
-> publish_receipt(effect_key, external_ref)
三篇系列文章共享的稳定逻辑制品只有六类:research_plan、source_records、evidence_pack、article_draft、review_decision 和 publish_receipt。Framework 文中的 EvidenceBundle 是 evidence_pack 内部的仓库级不可变对象,EvidenceSet 是该 evidence pack 对 required bundle manifest 的闭合视图;二者不增加新的跨篇制品类别。
开场中的 research_result 是故障设计里的不安全共享字段,后续方案不再使用它。每个 EvidenceBundle 都不可变;草稿引用一个已经闭合的证据清单;审批和发布则绑定草稿内容与目标,而不是绑定一句自然语言“可以发布”。
模型可能在每一步都生成看似合理的文本。再写一段 Prompt,也无法定义并发 reducer、批准 revision 或发布幂等键。此时,缺失的不是“更聪明的 Agent”,而是运行时契约。
本文所说的 运行时契约,是框架对执行、状态、副作用、控制权、恢复和治理作出的显式约定。这些约定至少应当可检查、可测试、可恢复或可约束。最小框架边界 则是为当前风险提供必要契约的最小职责集合,不以组件最多为目标。
全文使用五类陈述口径:官方源码和文档可直接核验的是“公开事实”;跨样本有限归纳是“样本观察”;需要在具体项目验证的是“工程建议”;本文研究 Agent 的选择是“案例决策”;没有受控实验支持的阈值和收益标作“待验证”。
入口问题只有一句:
当前失败究竟需要模型更聪明,还是需要运行时把某个控制语义变成契约?
1. 先诊断:这真的是 Framework 问题吗?
失效问题
一个常见但必须在项目中具体核验的工程反应,是把 Agent 失败直接解释成“框架能力不够”:不会分解就增加 planner,工具错误就重画图,资料遗漏就增加 Agent,结果不可验收就引入消息总线。
这些升级没有对准责任边界。
| 观察到的失败 | 优先检查 | 何时进入 Framework 设计 |
|---|---|---|
| 不会分解或推理 | 模型、Prompt、任务定义 | 需要显式步骤上限、终止或转移语义 |
| 工具返回错误事实 | 工具、数据源、参数和验证器 | 需要权限、超时、重试、幂等或回执 |
| 资料已存在但模型没看见 | Context Engineering | 需要 Runtime 在特定状态触发组装 |
| Agent 自报完成但交付不合格 | 业务 validator、Guardrail、Evaluation | 运行中需要将业务验收结果作为迁移条件;离线 Evaluation 验证该契约 |
| 中断后重复执行、并行覆盖、owner 不明 | Runtime | 立即进入执行、状态、控制与恢复契约 |
可核验事实与样本观察
[公开事实] helloagents 的固定源码展示了一个薄 Agent 可以组合模型调用、消息、工具注册、生命周期 hook 和 session store,具体位置为 sources/helloagents/hello_agents/core/agent.py:152-204、sources/helloagents/hello_agents/core/lifecycle.py:15-35,113-127、sources/helloagents/hello_agents/core/session_store.py:70-119。helloagents 公开事实只到这里;它不能证明薄框架已经满足生产可靠性。OpenAI Agents SDK 和 LangGraph 则进一步显式表示 next step、interruption、checkpoint 和 resume。openai-runtimelanggraph-runtime
[样本观察] 这些实现不构成“框架越重越好”的证据。它们说明,复杂度的分水岭不在于是否叫 Agent,而在于系统是否必须回答下面的问题:
- 当前执行处于哪一步?
- 哪个状态是业务权威?
- 谁有权继续推进?
- 哪个副作用已经发生?
- 恢复时应继续、等待、重试还是终止?
路线与代价
如果问题只在模型输出或工具数据,增加 Runtime 状态会扩大故障面;如果问题已经涉及副作用、并发和恢复,继续依赖 Prompt 又会把关键规则留在不可检查的文本中。诊断的代价是先保存一次可复现 trace,而不是立即重构。
案例决策
[案例决策] 对本文案例,第一版可以继续使用单循环,但要保存 event、artifact ref 和工具回执。只有当并行合并、人工等待和发布恢复成为实际失败,才增加相应契约。
章节图|先定位失败责任,再决定是否增加 Framework 契约。
待验证
薄循环在目标任务规模下能维持多久、引入显式状态后的维护成本是否更低,需要用目标项目的失败和消融验证。工程上的保守默认是从薄循环和清晰日志开始;出现不可整体重试的副作用、跨进程等待、并行写入或局部恢复时,再进入 Framework 升级。
2. 选择执行语义:循环、状态机、图和事件解决不同失败
失效问题
执行语义经常被画成一条成熟度阶梯:循环之后是状态机,状态机之后是图,图之后是多 Agent。这个图形很整齐,却会误导设计。
这些机制回答的是不同问题,而且可以在同一个系统中组合。
| 执行语义 | 最适合表达 | 新增责任 |
|---|---|---|
| 单循环 | 短路径、局部状态、可整体重试 | step 上限、超时、终止条件 |
| 显式状态机 | 有限阶段和稳定迁移规则 | 状态枚举、迁移不变量、版本 |
| 图 | 分支、汇合、回路和局部恢复 | reducer、并发写、checkpoint |
| 事件驱动 | 长等待、回调、跨进程继续 | 投递、去重、顺序、消费者恢复 |
| 消息总线/多 Agent | 角色隔离、并行专业化 | 路由、ownership、背压、失败传播 |
图 2|循环、状态机、图、事件与多 Agent 围绕不同失效并列选择,不构成共同成熟度轴线。
可核验事实与样本观察
[公开事实] OpenAI Agents SDK 在处理模型响应后,不是只返回一个布尔值,而是得到 NextStepHandoff、NextStepFinalOutput、NextStepRunAgain 或 NextStepInterruption,源码见 sources/openai-agents/src/agents/run_internal/run_steps.py:117-178 与 sources/openai-agents/src/agents/run.py:1068-1116,1128-1195。openai-runtime 该固定版本显式区分了这些结果。
[工程建议] Runtime 应把“下一步是什么”表示成有限、可观测的数据,而不是让控制流散落在回调中。
[公开事实] LangGraph 的 Pregel loop 将 task、write、checkpoint 与 interrupt 放入图循环,源码见 sources/langgraph/libs/langgraph/langgraph/pregel/_loop.py:652-724,850-931;checkpoint 基础类型见 sources/langgraph/libs/checkpoint/langgraph/checkpoint/base/__init__.py:92-119。langgraph-runtime
[样本观察] 图本身不替应用决定两个分支如何更新同一份证据。若两个节点都能覆盖 research_result,增加边并不会让合并正确。
[公开事实] MetaGPT 的 Role 使用 think/act 循环,环境负责路由消息并并发运行角色,源码见 sources/metagpt/metagpt/roles/role.py:399-427 与 sources/metagpt/metagpt/environment/base_env.py:175-210。metagpt
[样本观察] 这些机制支持把角色执行与路由分开观察,但不能推出消息到达者自动拥有全局控制权。
路线与代价
单循环的代价最小,但复杂分支会落入特殊判断;状态机让迁移清晰,却不表达任意并行拓扑;图能表达依赖,却引入 reducer 与 checkpoint;事件适合长等待,却增加投递和幂等责任;多 Agent 提供上下文与权限隔离,也增加 owner 和失败传播问题。
案例决策
[案例决策] 本文案例采用组合语义:
- 仓库研究使用 fan-out/fan-in 图,每个分支只能写自己的
EvidenceBundle; - 草稿、评审和发布使用有限状态机;
- 人工评审表现为可持久化 interruption;
- 发布回执通过事件进入状态机。
它不是从“循环升级到图”,而是让不同失败使用不同表达。
章节图|循环、状态机、图、事件和 interruption 按失败组合,而非线性升级。
待验证
这些语义在目标项目中的开发成本、运行成本和错误率没有统一结论。保守默认是选择能完整表达当前不变量的最简单语义;只有当前结构无法在不增加隐含分支和特殊回调的前提下表达真实失败时才更换或组合机制。
3. 拆开消息、运行上下文、业务状态、事件和 Checkpoint
失效问题
许多恢复问题不是“没有保存”,而是保存对象的职责混乱。团队把聊天历史当任务状态,把自然语言摘要当发布记录,再把 checkpoint 当作外部系统已经提交的证明。
可核验事实与样本观察
[公开事实] LlamaIndex 为 Agent input/output、tool call 和 tool result 定义了不同 WorkflowEvent,源码见 sources/llamaindex/llama-index-core/llama_index/core/agent/workflow/workflow_events.py:24-113 与 sources/llamaindex/llama-index-core/llama_index/core/agent/workflow/base_agent.py:520-659。llama-events LangGraph checkpoint 包含 channel values、versions seen 和 pending sends 等执行信息。OpenAI Agents SDK 的 RunState 可以序列化,并保留 trace 关联,源码见 sources/openai-agents/src/agents/run_state.py:257-350,1430-1437。openai-state
[样本观察] 这些实现支持的是对象可分离,不证明某个框架已经替应用定义好业务权威。文章采用下面的边界:
| 对象 | 生命周期 | 回答的权威问题 | 典型内容 | 不负责 |
|---|---|---|---|---|
| Message | 模型交互 | 模型看见或生成了什么 | user/assistant/tool items | 证明业务副作用 |
| Session | 一段交互连续性 | 会话如何延续 | messages、session metadata | 跨 run 业务真相 |
| Run | 一次执行尝试 | 这次执行由谁推进 | run identity、attempt、owner | 长期 Memory |
| RunContext | run 内稳定元数据 | 以谁的身份、权限和策略运行 | principal、scope、deadline、policy snapshot | 模型可见上下文内容 |
| Command/Intent | 瞬时请求 | 希望系统做什么 | PublishRequested | 证明已经发生 |
| DomainEvent | 事实历史 | 已经发生了什么 | ReviewApproved、PublishConfirmed | 保存全部聊天 |
| BusinessState | 当前业务状态 | 当前草稿、审批与发布状态 | event projection 或权威事务记录 | 引擎恢复位置 |
| Checkpoint | run 内恢复点 | Runtime 从哪里继续 | cursor、pending steps、owner epoch | 外部提交证明 |
| Store | 跨 run 持久对象 | 哪些制品和记录可查询 | artifacts、events、receipts | 自动决定注入模型 |
| Interruption | 可恢复等待 | 等谁、等什么、怎样恢复 | approval request、resume token、revision | 表示任务成功 |
| Memory | 跨输入边界 | 哪些过去信息有资格复用 | 受治理事实与经验 | run state |
路线与代价
业务状态可以由 append-only DomainEvent 投影,也可以由独立事务存储维护。前者便于回放,后者读取直接;无论选择哪条路线,都必须说明事件、投影和 checkpoint 的一致性边界。若不能在同一事务中更新,应使用 outbox、重放或 reconcile,而不是假设三者永远同步。
WorkflowEvent 只描述运行时内部调度;DomainEvent 描述业务事实;AuditRecord 保存决策证据。三者可以相互引用,不能因为都叫 event 就混为一层。
案例决策
[案例决策]
PublishRequested是 Command;DraftCreated(revision=7, content_hash=...)是 DomainEvent;ReviewApproved(revision=7, content_hash=..., destination=...)是 DomainEvent;PublishConfirmed(revision=7, receipt_id=...)是 DomainEvent;- 模型说“文章发布成功”只是 Message 与 AuditRecord;
- checkpoint 保存
event_cursor和待处理步骤,不复制外部事实作为自己的权威。
如果 checkpoint 已保存“调用发布工具”,但没有 PublishConfirmed,外部状态是未知,不是“未发生”。如果 Store 中已经存在外部回执而 checkpoint 落后,恢复逻辑先对账并补写 DomainEvent,再推进。
WaitForApproval 会持久化一个 ApprovalRequest(run_id, draft_revision, content_hash, destination, resume_token)。人工动作产生 ReviewApproved 或 ReviewRejected,之后 Runtime 才能 resume。
章节图|Message、Run、业务事实与 Checkpoint 由不同权威问题划分边界。
待验证
event sourcing 与独立事务状态哪一种更适合目标系统,需要按查询、审计和运维成本验证。保守默认是结构化权威状态加 append-only 事件;当恢复、审计或并行合并需要回答历史版本与因果关系时,再扩展完整事件投影。
4. 工具调用不是副作用协议
失效问题
Tool Calling 通常能承载工具名、参数和返回值,但这不等于应用已经定义副作用的权限、提交、超时、重试和恢复语义。最危险的状态不是明确失败,而是请求已经发出、外部可能成功、本地没有收到确认。
可核验事实与样本观察
[公开事实] OpenAI Agents SDK 的 Agent.as_tool 支持 needs_approval,源码见 sources/openai-agents/src/agents/agent.py:576-625,说明审批可以成为工具执行前的显式环节。openai-agent-tool helloagents 的 ToolRegistry 在 sources/helloagents/hello_agents/tools/registry.py:132-155 负责 schema 和调用边界,但业务幂等、回执与补偿仍需应用定义。helloagents
[样本观察] 这是协议边界分析,不代表所有 Tool Calling SDK 都缺少相关扩展。关键是项目必须能从自己的接口中回答副作用状态,而不能只依赖模型看到的一段错误文本。
路线与代价
工具不宜只分成“读”和“写”。读取也可能计费、改变 cursor、创建查询任务或消耗一次性资源。更稳妥的是按 effect class 决定协议强度:
| Effect class | 示例 | 最小契约 |
|---|---|---|
PureRead | 固定本地文件读取 | 权限、版本、timeout、错误分类 |
ObservedRead | 随时间变化的 API 查询 | 加查询时间、结果版本、可重复性说明 |
ResourceCreatingRead | 创建异步搜索任务 | 加幂等键、资源 ID、reconcile |
ExternalMutation | 写草稿、更新工单 | 加授权、request hash、回执、冲突语义 |
IrreversibleMutation | 正式发布、发送资金 | 加强审批、幂等、人工处置或补偿策略 |
外部效果与本地记录必须拆成两个维度:
EffectStatus =
NotStarted
| StartedUnknown
| Committed(external_ref)
| NotCommitted
| Conflict
| RequiresHumanResolution
LocalRecording =
Unrecorded
| DomainEventRecorded(event_id)
| CheckpointLinked(checkpoint_id)
外部已经 committed、本地尚未记录,是一致性缺口,不会让外部动作退回。不能保证同一事务时,恢复必须通过 effect key 对账。
ToolRuntime 采用一套一致接口:
prepare(call, run_context)
-> PreparedCall | Denied | NeedsApproval
execute(prepared_call)
-> ToolAttempt
reconcile(effect_key)
-> EffectStatus
classify(attempt_or_status)
-> Retry | DoNotRetry | ReconcileFirst | HumanResolution
PreparedCall 绑定 run_id、principal、resource scope、authorization ID、approval object、request hash、effect key 和 deadline。相同 effect key 只能对应相同业务动作与 request hash;不同 payload 复用同一 key 必须返回 conflict。
案例决策
[案例决策] 发布 effect key 由 article_id + approved_revision + content_hash + destination 生成。审批对象同时绑定 draft、revision、content hash、destination、批准人、有效期与 scope。
发布超时后进入 StartedUnknown。Runtime 只能先 reconcile(effect_key):
- 已提交:保存
ToolReceipt,追加PublishConfirmed,再关联 checkpoint; - 明确未提交:若审批仍有效且策略允许,创建新 attempt;
- 仍未知:进入
RequiresHumanResolution,不能自动 retry。
章节图|工具请求通过 prepare、execute、reconcile 与 classify 才形成可恢复的副作用协议。
待验证
不同工具的 effect class、reconcile 能力、审批强度和人工处置 SLA 必须由目标系统验证。保守默认不是“所有读都重试”,而是只在权限、输入版本、时效和重复安全性都仍成立时自动重试。
5. 多 Agent 的核心不是角色数量,而是控制权
失效问题
把“研究员”“编辑”“发布员”写进三个 system prompt,不会自动形成正确协作。多 Agent 设计首先要回答:谁可以改变哪类状态,以及完成子任务后控制权去哪里。
可核验事实与样本观察
[公开事实] OpenAI Agents SDK 明确区分两种模式:agent-as-tool 完成受限任务后把结果返回调用方;handoff 则把当前控制转移给目标 Agent。openai-agent-toolopenai-handoff LlamaIndex 的 multi-agent workflow 使用 handoff allowlist 和 next_agent 表达可转移范围,源码见 sources/llamaindex/llama-index-core/llama_index/core/agent/workflow/multi_agent_workflow.py:73-92。llama-handoff
| 模式 | 控制权 | 适合 |
|---|---|---|
| agent-as-tool | 调用方始终保留 | 专家问答、局部分析、格式转换 |
| handoff | 交给目标 Agent | 后续任务应由新角色独立推进 |
| fan-out/fan-in | 编排器保留,分支只提交结果 | 多仓库并行研究 |
| 异步协作 | durable owner 与事件协议决定 | 长任务、跨进程、人机混合 |
[样本观察] 这些模式可以说明控制转移路线,但不能自动处理过期 worker、晚到消息或两个执行者同时恢复。为此,运行时还需要一个最小 ControlLease:
ControlLease {
run_id
owner_id
owner_kind
epoch
expires_at
}
只有当前 owner 与当前 epoch 能推进受保护状态。handoff 原子地使旧 owner 失去推进权;lease 过期后要经过显式 recovery 才能产生新 epoch;晚到消息不能凭内容重新取得控制。
路线与代价
agent-as-tool 和 fan-out/fan-in 保留中心 owner,推理边界清楚,但中心编排器可能成为瓶颈。handoff 赋予目标 Agent 更完整的自主权,却增加权限、恢复和回收问题。异步协作适合长等待,也必须承担 lease、重复投递与背压。
案例决策
[案例决策] 本文案例不让仓库专家直接改总稿。每个专家只能写仓库级 EvidenceBundle,编排器等待所有必需分支或明确降级后执行合并。编辑 Agent 拥有草稿 revision;人工评审期间 Runtime 产生 WaitForApproval;批准后发布步骤获得唯一写权限。
合并契约被固定为:
EvidenceBundle(repo_id, source_revision, artifact_hash) is immutable
duplicate delivery is idempotent
conflicting source revision produces Conflict
required repo set closes before DraftCreated
late bundle creates a new EvidenceSet and a new Draft revision
人工批准只产生 ReviewApproved 业务事实,不自动获得发布工具权限。真正执行发布的 worker 还必须持有当前 ControlLease,并通过 ToolRuntime.prepare 的授权检查。
章节图|多 Agent 协作的核心是制品写权限、当前 owner 与 ControlLease。
待验证
中心编排器的吞吐边界、lease 时长和迟到分支策略都需要项目验证。工程上的保守默认是优先 agent-as-tool 或 fan-out/fan-in;只有目标角色确实需要独立上下文、权限、生命周期或异步 owner 时,才使用 handoff 或更松耦合协作。
6. 恢复不是把历史消息重新发送给模型
失效问题
一个可恢复 Runtime 必须保存“继续执行所需的最小权威状态”,而不是假设模型重读聊天后会自行理解发生了什么。
可核验事实与样本观察
[公开事实] OpenAI Agents SDK 可以将 interruption 后的 RunState 序列化并恢复;LangGraph 通过 checkpoint、writes 与 interrupt/resume 继续图执行。openai-statelanggraph-runtime
[样本观察] 这些固定版本提供了恢复载体;它们不能自动保证应用业务事件、外部副作用和 checkpoint 的一致性。
恢复前至少回答:
- 恢复的是哪个 run 和业务 revision?
- checkpoint 之前哪些业务事件已经提交?
- 哪些工具调用结果明确,哪些状态未知?
- 人工批准是否仍对当前草稿有效?
- 当前控制权属于谁?
- 哪些模型步骤允许重放?
- 哪些副作用必须先 reconcile?
先统一几个容易混淆的动作:
| 动作 | 本文语义 |
|---|---|
| retry | 同一业务动作的新 attempt,必须复用并核验 effect key |
| reconcile | 只查询外部效果,不发起新的副作用 |
| resume | 恢复同一个 run 的控制状态 |
| replay | 重做内部步骤,前提是输入、策略、权限与时效仍有效 |
| compensate | 对已经 committed 的效果执行业务补偿,不是普通 retry |
路线与代价
轻量恢复可以只保存状态快照;需要并发恢复、事件回放或跨进程 owner 时,则要保存 cursor、版本和 lease。状态越完整,迁移和兼容成本越高;状态过薄,又无法判断恢复是否合法。
本文最小 checkpoint 是:
Checkpoint {
run_id
checkpoint_id
schema_version
runtime_version
business_revision
event_cursor
pending_steps
owner_epoch
state_hash
created_at
}
Runtime.resume 不只接收 run_id:
resume(
run_id,
expected_checkpoint_id,
resume_input,
owner_token
) -> RunResult | StaleResume | OwnershipConflict | ReconcileRequired
两个 worker 同时恢复同一 run 时,只有成功取得新 owner epoch 的 worker 可以继续。旧 checkpoint、旧 resume token 和旧 owner 的写入都应被拒绝。
案例决策
[案例决策] 案例采用如下顺序:
load checkpoint
-> load authoritative business events
-> reconcile unknown tool effects
-> verify approval matches current draft revision
-> rebuild RunContext
-> compute NextStep
批准对象至少包含:
ApprovalRequest {
approval_id
run_id
draft_id
draft_revision
content_hash
destination
approval_scope
requested_at
expires_at
}
草稿在等待期间从 revision 7 变为 revision 8,或发布目标从 staging 变为 production,旧批准自动失效。发布前必须同时校验 revision、content hash、destination、scope、有效期和撤销状态。
若业务事件、状态投影和 checkpoint 不能使用同一事务,案例使用 outbox/reconciliation:外部回执先保存到 Store,PublishConfirmed 可重放生成,checkpoint 最终引用该 event。EffectStatus=StartedUnknown 永远不会直接转为 retry。
章节图|恢复先重建权威事实,再在 retry、reconcile、resume、replay 与 compensate 之间选择。
待验证
checkpoint 粒度、owner lease 时长和自动 replay 范围必须由故障恢复实验决定。保守默认是只自动 replay 输入与策略版本未变、权限仍有效、没有外部副作用且结果可安全丢弃的内部步骤。
7. Context、Guardrail、Trace 与 Runtime 各负其责
失效问题
Framework 很容易膨胀成“所有 Agent 问题的总层”。更稳定的边界是:
- Context Engineering 决定模型本轮看见什么;
- Runtime 决定何时组装上下文、调用模型和推进状态;
- 运行中的业务 validator/Guardrail 对输入、输出、工具参数或状态迁移执行拒绝、修复或升级;
- Trace 记录模型、工具和状态决策的证据;
- 离线 Evaluation 判断这些契约在固定任务上是否成立,不直接推进生产 run;
- Memory 决定哪些过去信息值得跨输入边界保存和治理。
可核验事实与样本观察
[公开事实] OpenAI Agents SDK 的 run loop 在模型调用、响应处理和 next step 推进之间路由 guardrail、interruption 与工具结果,源码见 sources/openai-agents/src/agents/run.py:897-929,1068-1116。openai-runtime LlamaIndex 的 typed tool events 则让工具调用与结果成为可观测对象,而不是一段拼接文本。
[样本观察] 只有 trace 中的对象拥有稳定关联,团队才能区分“上下文没进来”“模型选错了”“工具状态未知”“业务事件没写入”和“checkpoint 落后”。
路线与代价
最小 AuditRecord 需要因果链,而不是一袋日志:
trace_id
run_id
step_id
attempt_id
parent_id / causation_id
business_revision
context_ref
model_response_ref
authorization_id
receipt_id
domain_event_id
checkpoint_id
external_ref
error_class
ContextAssemblyManifest 属于 Context Engineering;Runtime 只保存:
ContextRef {
manifest_id
policy_version
input_hash
assembled_at
}
Framework 不拥有常驻、检索、压缩和预算策略。类似地,Runtime 可以读取经过治理的 Memory reference,却不定义 Memory 写入门和遗忘规则。
保存完整模型输入输出有助于调试,也增加隐私、存储和访问控制责任。只保存摘要则可能无法重放。项目应根据风险决定 trace 级别,并为敏感字段定义脱敏和保留期。
案例决策
[案例决策] 一次发布可以沿以下链路追踪:
ContextRef
-> model decision
-> PreparedCall.authorization_id
-> ToolAttempt
-> ToolReceipt.external_ref
-> PublishConfirmed.domain_event_id
-> Checkpoint.event_cursor
-> Draft.business_revision
模型 trace 中出现“已发布”,仍不能替代外部回执和 PublishConfirmed。
章节图|Context、Runtime、Guardrail、Trace、Evaluation 与 Memory 相连但不合并权威。
待验证
trace 保留范围、脱敏策略和采样比例需要按故障定位与合规成本验证。保守默认是完整记录状态迁移和副作用因果链,不默认持久化所有模型内部文本。
8. 用失败消融决定复杂度升级
失效问题
框架复杂度若按组件数量升级,就会把“拥有图、事件和多 Agent”误当成成熟。更可操作的方法,是按失败维度选择候选机制,并写清它不自动解决什么。
可核验事实与样本观察
[样本观察] 五个深描样本分别暴露了有限 next step、图循环、typed events、角色循环和消息路由等机制。它们没有提供统一证据证明“更多机制”会带来更高可靠性。公开源码能证明机制存在,不能替当前项目决定成本和收益。
路线与代价
| 失败维度 | 候选机制 | 机制不自动解决的问题 |
|---|---|---|
| 多步继续、终止、step limit | 单循环 Runtime | 业务并发和外部副作用 |
| 稳定阶段迁移与非法迁移 | 状态机 | 分支汇合和跨进程等待 |
| 分支、汇合、回路、局部重试 | 图 | reducer、版本冲突和 owner |
| 长等待、回调、跨进程继续 | 事件与持久化 interruption | 业务幂等和效果对账 |
| 独立上下文、权限、并行专业化 | 多 Agent | 控制权、背压和失败传播 |
| 多租户、配额、部署和运营 | 平台层 | 单任务正确性 |
这些候选机制不构成共同轴线。一个系统可以只采用其中一项,也可以在不同子流程组合多项。增加一种机制同时会增加新的运行状态、迁移、观测和故障,因此停止升级是正式设计结果。
案例决策
[案例决策] 案例在单进程内先验证分支隔离、批准 revision 与发布幂等。没有多租户、跨区域和大规模队列证据,就不引入平台层。
每次机制变化记录:
- 它解决哪个已知失败;
- 新增哪些状态和故障;
- 哪些不变量可确定性验证;
- 哪些收益仍是
[待验证]; - 当前机制的停止条件。
章节图|每一级复杂度都经过同一固定任务验收,达标即停止升级。
待验证
状态机、图、事件和多 Agent 在目标任务中的开发成本、恢复时延与失败分布需要受控运行才能判断。本文只给出升级依据,不给出通用阈值。
9. 把八个决策串成运行时闭环
八个决策最终形成一条闭环:
acquire control lease
-> load checkpoint and authoritative state
-> reconcile unknown effects
-> assemble context
-> call model
-> interpret response as NextStep
-> authorize tool effects
-> execute prepared calls
-> record receipts, LocalRecording and DomainEvents
-> update projection
-> checkpoint event cursor and pending work
-> continue / handoff / wait / complete / fail
图 3|Runtime 组织执行,NextStep 表示控制结果,ToolRuntime 约束外部效果;业务事实与 checkpoint 通过 ID 关联。
其中有四条不能交换的顺序:
- 高风险副作用先授权,再执行。
- 恢复时先对账外部结果,再决定是否重试。
- 只有当前 owner epoch 可以推进受保护状态。
- DomainEvent 与 checkpoint 必须关联,但不能互相冒充。
章节图|授权、对账、控制权与事件关联构成四条不可交换顺序。
一个运行时步骤的伪代码可以保持很小:
step(run_state, run_context):
assert control_lease.is_current(run_state.owner_epoch)
if run_state.pending_effect is StartedUnknown:
effect = tool_runtime.reconcile(run_state.pending_effect.effect_key)
return apply_effect_or_wait(run_state, effect)
context_ref, model_input = context_provider.assemble(run_state)
response = model.generate(model_input)
next = interpret(response)
if next is InvokeTools:
prepared = tool_runtime.prepare(next.calls, run_context)
if prepared is NeedsApproval:
return persist_approval_request(run_state, prepared)
if prepared is Denied:
return transition(run_state, Fail(prepared.reason))
attempt = tool_runtime.execute(prepared)
effect = tool_runtime.classify(attempt)
return apply_effect_or_wait(run_state, effect)
if next is WaitForApproval or next is WaitForEvent:
return persist_interruption(run_state, next, context_ref)
return transition(run_state, next)
apply_effect_or_wait 只有在外部状态得到足够确认后才追加 DomainEvent;随后更新业务投影,并让 checkpoint 引用新的 event cursor。外部状态仍未知时,它返回 WaitForEvent 或 Fail(RequiresHumanResolution),不会直接执行 retry。
这里没有规定必须使用哪一个 SDK。重要的是,interpret、prepare、execute、reconcile、apply 和 checkpoint 的职责不再藏在一个无法检查的循环里。
10. Schema 是决策结果:推导最小 Runtime 制品
前文已经把失效拆成执行、状态、合并、副作用、控制权、恢复和治理。到这里才有理由定义接口。下面不是行业标准,而是本文案例的最小契约骨架。
NextStep
NextStep =
Continue
| InvokeTools(tool_calls)
| Handoff(target, payload)
| WaitForApproval(approval_request)
| WaitForEvent(subscription)
| ReconcileEffect(effect_key)
| Complete(output)
| Fail(error)
NextStep 只表示 Runtime 控制转移。InvokeTools 仍是计划,必须经过 ToolRuntime.prepare;EffectStatus 与 retry 决策由工具协议单独表示。简单项目可以删掉 Handoff、等待和 reconcile 变体。
Runtime
Runtime.run(input, run_context) -> RunResult
Runtime.step(run_state, run_context) -> NextStep
Runtime.resume(
run_id,
expected_checkpoint_id,
resume_input,
owner_token
) -> RunResult | StaleResume | OwnershipConflict | ReconcileRequired
Runtime.checkpoint(run_state, event_cursor) -> Checkpoint
Runtime 负责执行与恢复,不负责定义文章草稿、评审决定或证据包的业务内容。
RunContext {
run_identity
principal
tenant_and_resource_scope
runtime_dependencies
policy_snapshot
cancellation_and_deadline
owner_epoch
}
Checkpoint {
run_id
checkpoint_id
schema_version
runtime_version
business_revision
event_cursor
pending_steps
owner_epoch
state_hash
created_at
}
ControlLease {
run_id
owner_id
owner_kind
epoch
expires_at
}
RunContext 不包含模型本轮可见全文;那属于 Context Engineering 生成的 ContextRef。业务内容和 revision 仍属于 BusinessState 与 DomainEvent。
ToolRuntime
ToolRuntime.prepare(call, run_context)
-> PreparedCall | Denied | NeedsApproval
ToolRuntime.execute(prepared_call) -> ToolAttempt
ToolRuntime.reconcile(effect_key) -> EffectStatus
ToolRuntime.classify(attempt_or_status) -> RetryDecision
tool_receipt:
receipt_id: ...
tool: publish_article
run_id: ...
attempt_id: ...
authorization_id: ...
tenant: ...
resource_scope: ...
approved_revision: r7
request_hash: ...
effect_key: article-42:r7:<content-hash>:docs-site
external_ref: ...
effect_status: committed | not_committed | started_unknown | conflict | requires_human_resolution
observed_at: ...
local_recording:
receipt_id: ...
status: unrecorded | domain_event_recorded | checkpoint_linked
domain_event_id: ...
checkpoint_id: ...
同一 effect key 只能绑定同一业务动作和 request hash。ToolAttempt 表示一次请求尝试;ToolReceipt 只表示可验证的外部观察,不声明本地已经记账;LocalRecording 记录该 receipt 是否已经转换为 DomainEvent 并与 checkpoint 关联。两条状态轴不能因为都出现 committed 或 recorded 就合并。
审批与上下文引用
ApprovalRequest {
approval_id
run_id
draft_id
draft_revision
content_hash
destination
approval_scope
requested_at
expires_at
}
ContextRef {
manifest_id
policy_version
input_hash
assembled_at
}
ApprovalRequest 防止批准被移植到另一份内容或目标;ContextRef 让 Runtime 能追踪模型输入,但不拥有上下文选择规则。这里的 input_hash 指向 Context manifest 中 provider adapter 最终序列化的模型输入;组装层 hash、adapter version 与序列化策略仍由 manifest 保存。
运行时契约矩阵
| 契约 | 最小不变量 | 防止的案例失败 |
|---|---|---|
| 执行 | 每一步产生有限且可解释的 NextStep | 隐式循环无法解释停留 |
| 状态 | Command、DomainEvent、BusinessState、Checkpoint 分离 | 摘要覆盖权威状态 |
| 合并 | bundle 不可变、重复幂等、冲突显式、required set 闭合 | 证据覆盖 |
| 副作用 | 请求绑定授权、scope、hash、effect key,可 reconcile;外部效果与本地记录分轴 | 重复发布或已提交但未记账 |
| 控制权 | 只有当前 owner epoch 能推进,handoff 撤销旧 owner | 多 Agent 漂移 |
| 审批 | revision、content hash、destination、scope 与有效期一致 | 旧批准误用 |
| 恢复 | 先对账 receipt/event,再从 expected checkpoint 恢复 | 恢复重放 |
| 治理 | 因果链连接 context、decision、authorization、receipt、event、checkpoint | 无法归因 |
从字段回到失败
| 制品字段或方法 | 对应失败 | 可验证断言 |
|---|---|---|
EvidenceBundle.repo_id/source_revision/artifact_hash | 证据覆盖 | 重复到达不改变结果,revision 冲突显式失败 |
ControlLease.epoch | owner 不明 | 旧 owner 与过期 worker 不能推进 |
ApprovalRequest.content_hash/destination | 旧批准误用 | 内容或目标变化后发布被拒 |
PreparedCall.authorization_id/request_hash | 参数在批准后变化 | execute 只接受完整授权对象 |
ToolReceipt.external_ref/effect_status / LocalRecording.status | timeout 后重复发布或外部提交未记账 | StartedUnknown 先 reconcile;committed receipt 必须再绑定 DomainEvent 与 checkpoint |
Checkpoint.event_cursor/owner_epoch | checkpoint 落后或并发恢复 | 已有回执时补事件,不再次 execute |
ContextRef 与 AuditRecord 因果 ID | 无法归因 | 每次状态迁移可回到模型输入和工具结果 |
这些接口不是行业标准,也不是完整框架。删减规则很简单:
如果一个类型、字段或方法不能回到真实失败、责任边界或确定性验证,就先删除。
章节图|Runtime Schema 从失败、责任与可验证断言反推,而不是先验模板。
结语:把“大家默认如此”变成系统可以证明的事
回到源码研究 Agent,真正需要框架处理的不是“如何写一篇更好的文章”。它需要保证五个仓库的证据不会互相覆盖,批准只对指定草稿 revision 有效,发布动作可以查证且不会在恢复时重复。
一个项目可以从五个动作开始:
- 保存一个可复现的运行时失败。
- 写出失败背后的隐含控制语义。
- 用最小类型、状态或不变量把它显式化。
- 为合并、副作用、控制权和恢复添加确定性测试。
- 当前机制达到验收后停止升级。
Framework 的边界因此不是“模型外面的全部代码”。它只拥有执行契约。Context、Memory、Evaluation 和业务领域仍有各自的权威。
Framework 的成熟度,不在于运行时有多少节点,而在于关键控制语义是否已经从“大家默认如此”变成可验证契约。
附录 A:源码深描样本矩阵
外部资料访问截止日为 2026-09-06;目录与规格文件使用已经确认的 2026-09-07 版本标识。
| 项目 | 固定提交 | 直接观察到的机制 | 工程推断 | 不可外推 |
|---|---|---|---|---|
| helloagents | 5432566d01ea1c2095c4a717fe2a010aa1c3b0bd | Agent、hook、ToolRegistry、SessionStore 分别存在 | 薄运行骨架可以先于复杂图存在 | 生产可靠性 |
| OpenAI Agents SDK | 863b96cfe99b5388910ff5b8cd85329003330132 | 有限 NextStep、handoff、as-tool、可序列化 RunState | 控制结果与恢复载体可显式化 | 应用恢复一致性已解决 |
| LangGraph | d56666f7fbf0d380ad84cdf0cbe5aa48ab0cc086 | graph loop 处理 writes、checkpoint、interrupt | 图运行必须面对写入与恢复语义 | 图自动解决业务冲突 |
| LlamaIndex | 47b85c8ec229f725aa680ed3d613d2c02359480f | typed events、tool call/result、handoff allowlist | 调用、结果和转移可分离观测 | typed event 自动正确 |
| MetaGPT | 11cdf466d042aece04fc6cfd13b28e1a70341b1f | Role ReAct 与 Environment 路由分别存在 | 角色执行和路由可以分工 | 消息路由等于控制权 |
附录 B:离线运行时验证清单
| 层 | 最小断言 |
|---|---|
| 终止 | step limit、timeout、Complete 和 Fail 都有确定结果 |
| 状态 | Command、DomainEvent、BusinessState 和 Checkpoint 不会互相覆盖;非法迁移被拒绝 |
| 合并 | 重复 bundle 幂等;输入顺序不改变结果;revision 冲突显式;必需分支缺失阻断 |
| 晚到结果 | 草稿生成后的 late bundle 只能产生新 revision,不能静默修改已批准内容 |
| 副作用 | 相同 effect key 只能绑定相同 request hash;StartedUnknown 不能直接 retry;外部 EffectStatus 与本地 LocalRecording 分开检查 |
| 审批 | revision、content hash、destination、scope 或有效期变化后旧批准失效 |
| 恢复 | checkpoint 落后于 receipt 时补写 DomainEvent,不再次 execute |
| 控制权 | lease 过期或 handoff 后旧 owner 无法推进;并发 resume 只有一个成功 |
| 事件 | 重复和乱序投递不产生重复副作用或非法状态;消费者从 cursor 恢复 |
| 失败传播 | 必需分支失败阻断;可选分支失败生成明确降级事件;取消后晚到结果被隔离 |
| Trace | 每个迁移能回到 ContextRef、decision、authorization、receipt、event 与 checkpoint |
这些检查只验证契约自洽,不证明模型能力或系统性能提升。
附录 C:容易混淆的边界
| 概念 | Framework 依赖的交界面 | Framework 不拥有 |
|---|---|---|
| Workflow/业务领域 | Commands、DomainEvents、BusinessState schema | 文章内容、证据语义和业务验收定义 |
| Context Engineering | ContextRef、assembly outcome | 常驻、检索、预算、压缩和重注入策略 |
| Memory | 经过治理的 memory object reference | 写入门、更新、遗忘与跨 session 治理 |
| Evaluation | validator/grader 结果与回归门信号 | suite、grader 校准和 GatePolicy 生命周期 |
| Store | artifact、event、receipt 引用 | Store 中所有对象的长期治理 |
| Trace | AuditRecord 因果链 | 业务状态或外部提交证明 |
| 平台 | identity、quota、deployment、operations API | 单任务正确性 |
参考资料
参考资料
helloagents 固定源码,重点见
hello_agents/core/agent.py、lifecycle.py、session_store.py与tools/registry.py。 ↩ ↩OpenAI Agents SDK 固定源码,重点见
src/agents/run_internal/run_steps.py与src/agents/run.py。 ↩ ↩ ↩LangGraph 固定源码,重点见
libs/langgraph/langgraph/pregel/_loop.py与 checkpoint base。 ↩ ↩ ↩MetaGPT 固定源码,重点见
metagpt/roles/role.py与metagpt/environment/base_env.py。 ↩