第 19 讲:Tool Approvals 审批流与网络策略

Codex 源码解析 · 第 19 讲

Codex 源码解析系列 第 19 讲:Tool Approvals 审批流与网络策略 基于 OpenAI Codex 源码 · 2026-10-13 💡 本讲一句话: Codex 里每一次"越权动作"(跑命令、写 stdin、调 MCP、碰网络)都要过一条三级裁决链——Hooks 配置层先表态,Guardian 自动审查接着审,都不管才弹给用户;而"始终允许"不是魔法,是会话级缓存 + 按 host 的网络审批状态机。读完你会明白:AI Agent 的"权限弹窗"背后是一台怎样的确定性机器。 一、审批三级裁决链:Hooks → Guardian → User 第 17、18 讲我们看了命令怎么被沙箱卡住、补丁怎么先干跑再落盘。但还有一个问题没回答: 当策略判定"这个动作需要审批"时,到底谁来拍板? Codex 的答案在 core/src/tools/approvals.rs (873 行)里—— Session::request_approval 是所有审批动作的总入口,它把裁决权按固定优先级分给三个角色: ① Hooks(配置层) :用户/企业可以在 hooks 里写"这类命令直接放行/拒绝",零交互、确定性最高; ② Guardian(自动审查) :一个独立的 LLM 审查会话,按策略替用户做决定,适合无人值守场景; ③ User(弹窗) :最后防线,把动作和理由摆到人面前。三级是 串行短路 的——上一级给出明确裁决,下一级根本不会执行。 📄 codex-rs/core/src/tools/approvals.rs(第 493-558 行,中间省略网络审批的遥测分支) let resolution = match run_permission_request_hooks( // 第一问永远是 Hooks——配置层可以无条件放行/拒绝,零交互成本 self, // 会话实例:hooks 需要它读取当前 turn 的策略配置 ctx.review_context.turn(), // 传入当前 turn 上下文,hook 据此判断策略是否允许该动作 &permission_request_run_id, // 用 call_id 当 hook 运行 id:重试时加 :retry 后缀避免撞车 action.permission_request_payload(), // 把审批动作序列化成 hook 能理解的 payload(工具名+入参) ) .await // 异步跑完所有 permission_request hooks,拿到配置层裁决(或弃权) { Some(PermissionRequestDecision::Allow) => ApprovalResolution { // hook 放行 → 直接 Approved,后面两级根本不用跑 decision: ReviewDecision::Approved, // hook 放行 → 最终裁决=批准 source: ApprovalResolutionSource::Hook, // 记录来源=Hook(遥测里映射成 Config) }, Some(PermissionRequestDecision::Deny { message }) => ApprovalResolution { // hook 拒绝 → 带理由直接拒,同样跳过后面两级 decision: ReviewDecision::denied(message), // hook 拒绝 → 带上它给的拒绝理由 source: ApprovalResolutionSource::Hook, // 来源标记为 Hook(遥测里映射成 Config) }, None => self.request_reviewer_approval(action, &ctx).await, // hook 弃权(None)→ 进入第二级裁决链 }; // …(中间省略:网络审批的遥测记录与特殊裁决分支处理)… async fn request_reviewer_approval( // 第二级入口:Guardian 优先,用户兜底——两级串行、不并行 self: &Arc<Self>, // 会话实例:两级裁决都要用它查策略/发弹窗…

阅读全文:Codex 源码解析 第 19 讲