具体问题与边界
工具调用不是普通函数调用。模型已经产生 ToolCall 时,参数可能非法;审批等待期间可能被取消;进程 启动后可能超时;结果写入前进程可能退出。Runtime 必须知道副作用尚未发生、正在发生还是已经发生, 才能决定 Pre Hook、审批、Post Hook、ToolResult 和 Rollout 的顺序。
本节讨论通用生命周期屏障,不把 Shell、Patch、MCP 各自的内部算法重新展开。
生命周期和提交点
| 阶段 | 状态所有者 | 可否安全拒绝 | 必须产出 |
|---|---|---|---|
| ToolCall accepted | Turn/History | 可 | 完整 Call 记录 |
| payload validation | Router/Handler | 可 | 参数错误 ToolResult |
| PreToolUse/Policy | Hook/Policy | 可 | allow、rewrite 或 deny |
| approval wait | Session waiter | 可取消 | 有作用域的 decision |
| execution | ExecutionEnvironment | 通常不可无损撤销 | stdout/patch/MCP result 或错误 |
| PostToolUse | Hook engine | 不能回滚 | 观察、additional context、block follow-up |
| result commit | Turn/Recorder | 结果已确定 | 同 call_id ToolResult + 事件 |
正常路径
- 先把模型产生的完整 ToolCall 记入 Turn,使后续失败也有可关联的 call_id。
- Router 按 payload variant 解析并校验;失败时不进入执行环境,直接构造 ToolResult。
- Pre Hook 可以按受限合同改写输入或阻断;Policy/Approval 再对最终具体动作做判定。
- ExecutionEnvironment 获得明确的 cwd、权限、取消 Token 和超时,执行真实副作用。
- 输出经过结构化编码与硬上限截断;Post Hook 观察真实结果,但不声称回滚。
- ToolResult 以原 call_id 提交 History/Rollout,下一次模型采样只读取已配对结果。
Call 先记录并不表示副作用已经执行。它建立的是协议事实;真正不可逆的提交点由具体工具决定,例如 子进程 spawn、Patch 第一次 replace 或远端 MCP 服务确认写入。
Python 风格伪代码
async def invoke_tool(call: ToolCall, step: StepSnapshot, turn: TurnSnapshot) -> ToolResult:
await turn.history.append(call)
try:
payload = step.tool_plan.parse(call)
payload = await hooks.run_pre(call.name, payload, turn.cancellation)
decision = policy.evaluate(payload, turn.permissions)
await require_approval_if_needed(decision, call.call_id, turn.cancellation)
raw = await step.tool_plan.execute(payload, turn.cancellation)
bounded = encode_and_truncate(raw)
post = await hooks.run_post(call.name, bounded, turn.cancellation)
result = ToolResult.success(call.call_id, bounded, post.additional_context)
except RecoverableToolError as error:
result = ToolResult.failure(call.call_id, encode_error(error))
except asyncio.CancelledError:
result = ToolResult.failure(call.call_id, "cancelled after acceptance")
await turn.history.append(result)
await turn.rollout.flush_pair(call, result)
return result
生产实现对不同工具有更细的事件和执行器,但“接受后的每个出口都形成配对结果”是可迁移合同。
失败、取消与部分副作用
| 故障点 | 副作用状态 | 模型应看见 | 恢复动作 |
|---|---|---|---|
| payload 非法 | 未发生 | 参数错误 Result | 允许模型修正参数 |
| Pre Hook/Policy 拒绝 | 未发生 | 明确拒绝 Result | 不自动升级 |
| Approval 被拒/取消 | 未发生 | 拒绝或取消 Result | 清理 waiter |
| 进程超时 | 可能已输出/写文件 | timeout + 有界输出 | kill、reap、报告残留 |
| Patch 中途失败 | 可能部分文件已改 | 已完成/未完成列表 | 不宣称事务回滚 |
| Post Hook 失败 | 副作用已发生 | 工具事实仍保留 | Hook 错误单独编码 |
| Result 写入失败 | 副作用已发生、历史未确认 | 不可盲目重跑工具 | 先恢复写入/重建配对 |
设计判断
通用 Router 应统一配对、Hook、事件和错误编码;具体执行器仍要拥有自己的提交点和清理逻辑。把所有 工具强塞进一个事务接口会制造虚假保证,尤其是 Shell 与远端 MCP 无法被本地 Rollback 撤销。
这种生命周期增加样板代码,但使故障测试可以断言“错误发生后剩下什么”。对纯计算、无副作用工具, 可以缩短 Policy/Approval 路径;仍应保留 call_id 配对和输出上限。
证据与 Mini Codex
生产锚点包括 Tool Router/parallel、Unified Exec ProcessManager、Apply Patch Handler、Hook dispatcher 与 Rollout recorder;6.13—6.20、6.25—6.40 已逐段证明调用链。
Mini Codex 覆盖 Hook rewrite、审批拒绝、超时 kill/reap、Patch 预验证和 ToolResult 配对:
cd examples/mini-codex
uv run pytest -q -k 'hook or approval or timeout or patch or paired'
它没有 PTY、跨平台进程组和真实远端副作用,故障矩阵只适用于已实现边界。
本节边界
本节证明的是副作用的时间顺序。是否允许执行、谁能强制限制资源,必须继续拆成策略、审批和沙箱三层。 详细源码映射由研究仓库中的配套索引维护。
评论
登录后即可评论