Agent 架构里最容易混淆的四种责任

写 Agent runtime 时,我经常会看到一个很顺手的设计:拿到一次 tool call,在同一个 handler 里读会话状态、检查权限、执行 shell,再把结果推给 UI。demo 阶段它很省事,到了重试、超时和恢复场景,问题就开始变得具体:到底谁拥有这次调用的状态?谁能证明副作用已经发生?失败后下一轮应该相信哪一份记录?

把 recovery-boundaries 章节和相邻的工具、patch、审批章节放在一起看,会得到一个很实用的拆法:把责任分成 owner、executor、governor、observer。它们可以由同一个类型中的不同方法承担,但不应该被揉成一份"工具结果"。

先找 owner:谁能把状态带过下一次 await

owner 持有的是跨 await、跨调用仍然有效的状态。它通常知道当前 session、turn、调用身份,以及下一步继续执行所需的上下文。这个角色决定哪份状态可以被更新、持久化或恢复。

这里有一个很容易被 UI 掩盖的错误:界面上出现了"工具已完成",不代表 owner 已经把完成事实写进自己的状态。一个事件可能已经发到前端,session 的 durable 记录却还没有落下;重新打开会话时,UI 看到过的那件事就没有地方可以重放。

所以我现在看 Agent 的状态问题,第一问不是"有没有 event",而是"这个 event 对应的权威 owner 是谁"。如果答案只能指向一段渲染代码,恢复边界通常还没有被定义。

executor 只负责碰世界

executor 是真正调用文件系统、shell、PTY、pipe 或远端 backend 的那一层。它拿到的是已经通过调度的 invocation,然后产生实际副作用或明确的错误。

这层不该顺便承担"用户是否批准""UI 要显示什么""下一轮上下文放什么"。把这些逻辑塞进执行函数,会让一次失败变得很难解释:进程可能已经启动,模型却只收到一个 Err;目录可能已经改变,TurnDiff 却没有对应的 patch;重试时又不知道上一次到底走到了哪一步。

源码里有一条很重要的边界:tool future 返回 Err 时,drain 分支调用的是 error_or_panic,不会自动补出一条 durable output。也就是说,失败调用可能没有可回放的工具产物。模型输出、机器状态、后续 prompt 需要分别核对,不能压成一个"调用结果"。

governor 决定这次能不能做

governor 处理审批、permission 和 sandbox。它回答的是"这次请求允许执行到哪里",不是"执行完以后发生了什么"。

如果权限决定隐藏在 executor 内部,审查日志里就只剩一句"handler 返回成功/失败",很难知道拒绝是因为用户没有批准、沙箱不允许,还是执行本身出错。更糟的是,同一个工具在不同 session 或不同模式下可能拿到不同权限,调用者却只看到了同一个工具名。

把 governor 单独拿出来,至少能给每次 invocation 留下三个可核对的事实:请求了什么权限,采用了什么 sandbox,最终是批准、拒绝还是绕过。它们不需要进入模型的自然语言回复,但应该存在于 runtime 能回查的记录里。

observer 看到的是投影,不是机器本身

observer 负责生成 item、diff、event 和 transcript。这些东西对 UI、日志和下一轮提示都很重要,但它们仍然是观察结果。

例如 TurnDiff 不是完整审计日志。普通 shell、外部进程、目录或权限变化,可能没有对应的 AppliedPatchDelta;patch move 和失败前缀也没有事务回滚。observer 没看到变化,只能说明这条投影没有记录,不能拿来证明机器没有改变。

这也是为什么"UI 显示完成"不能替代执行收据。一个可靠的收据至少要能指向执行者、权限决定和持久化 owner;observer 只负责把这些事实投影出来,不负责创造事实。

四种责任合在一起,故障会变成一句模糊的话

可以用一次 apply_patch 失败来检查边界:

text 复制代码
owner      保存 session/turn 与调用身份
governor   决定 permission、sandbox 与审批结果
executor   对文件执行 patch,返回成功或错误
observer   生成 item、diff、transcript,供界面和日志读取

如果 patch 已经改动了部分文件,executor 需要提供可核对的副作用收据;如果 governor 拒绝了请求,observer 应该能显示"拒绝"而不是笼统的失败;如果进程在返回前崩溃,owner 还要知道这次 invocation 是否有 durable 结果。四层各自回答一个问题,重试策略才有依据。

我给 Agent 平台留了一份很小的检查表:

  • 每个 action 的状态 owner 是谁,跨 await 后仍由谁持有;
  • executor 产生的副作用是否有独立收据,失败是否也会落记录;
  • governor 的 permission、sandbox 和审批结果能否按 invocation 回查;
  • observer 的 diff/event/transcript 哪些是投影,哪些字段指向权威状态;
  • 工具 future 失败、进程半途退出、网络超时后,下一轮从哪份事实继续。

这几问没有漂亮的统一答案。它们的价值在于把"工具调用成功"拆成可以逐项验证的责任。模型能看到的 ToolSpec、runtime 能执行的 handler、界面能展示的 event,分别属于不同边界;把它们混成一个对象,代码会更短,恢复时却只能靠猜。

完整的责任交接可以继续看 codex-internals 第 13 章

源码依据:codex-rs/core/src/session/turn.rs L419-L459、L1112-L1206、L1892-L1916;codex-rs/core/src/tools/router.rs L28-L78、L221-L243;对应章节为 13-recovery-boundaries14-tool-spec-registry15-apply-patch-turn-diff17-approval-permissions-sandbox

相关推荐
hust_wangyajun3 小时前
Function-Call / Skill / MCP-Server / ReAct 范式深度辨析
ai·llm·agent·mcp
程序员老刘3 小时前
Qwen 3.8 max干了20分钟没干完,免费模型3分17秒搞定,问题出在哪?
flutter·ai编程
2601_955760074 小时前
Claude API 多人协作中的版本管理方法
java·ai编程
Bigger4 小时前
Han:一个让 AI Agent 也能做出高级中国风页面的 CSS 设计系统
前端·ai编程·设计
杨超越luckly4 小时前
Agent应用指南:巨幕之下 · 中国 IMAX 影院205城的空间布局
人工智能·arcgis·html·agent·数据可视化
zhangfeng11334 小时前
AI编程范式:从Vibe Coding(氛围编程)向SDD规范驱动开发演进全解析
人工智能·驱动开发·ai编程
9i编程5 小时前
手敲重构学透 Multi-Agent 代码(下):从 AgentScope 1.0.8 升级到 2.0,API 变了什么、踩了哪些坑
人工智能·openai·ai编程
aqi005 小时前
15天学会AI应用开发(十九)使用LangGraph实现持久记忆功能
人工智能·python·大模型·ai编程·ai应用