犀牛鸟 2026 研究笔记 estelledc.github.io

Codex 工具循环精读

调研时间:2026-06-22 源码 commit:98845e4 本地 CLI:codex-cli 0.125.0

一句话定位

ToolOrchestrator 是 Codex 工具执行的状态机——它把「审批 - 沙箱 - 执行 - 失败升级重试」固化为确定性的代码路径,而非依赖模型即兴决策。exec policy 在此之前做第一道命令分类,sandbox 在此之后做最后一道 OS 级防线。


日常类比

把 ToolOrchestrator 想象成银行的「贷款审批 + 放款」流水线:

  1. exec policy(信用评分系统):自动给命令打分——白名单命令秒过,危险命令直接拒绝,灰色地带需要人工审批
  2. Approval(信贷经理审批):先走自动化风控规则(hooks),过不了找 AI 风控员(guardian),再过不了找真人(用户)
  3. Sandbox(保险箱):放款后,资金只能在限定范围内使用;即使贷款被批准,实际执行仍然受物理隔离约束
  4. Escalation(申诉):保险箱锁死了正常操作?自动发起二次审批,请求更高权限

类比边界:贷款是一次性决策,而 ToolOrchestrator 在同一 turn 内可能执行几十次,每次独立走完整流水线。


架构总览

flowchart TD
    MC[模型返回 tool_call] --> TR[ToolRouter 分发]
    TR --> TO["ToolOrchestrator.run()"]

    subgraph orchestrator["ToolOrchestrator 三阶段"]
        direction TB
        EP[ExecPolicy 决策] -->|Skip| EXEC1[首次执行]
        EP -->|Forbidden| REJ[直接拒绝]
        EP -->|NeedsApproval| APR[审批流程]
        APR -->|Approved| EXEC1
        APR -->|Denied| REJ

        EXEC1 -->|成功| OK[返回结果]
        EXEC1 -->|Sandbox Denied| ESC{可升级?}
        ESC -->|Yes| REAPPR[二次审批]
        ESC -->|No| FAIL[返回 sandbox 错误]
        REAPPR -->|Approved| EXEC2[无沙箱重试]
        REAPPR -->|Denied| FAIL
        EXEC2 --> OK
    end

    TO --> orchestrator

ExecPolicy:命令分类引擎

三层配置叠加

exec policy 规则从三个层级加载,后者覆盖前者:

层级 路径 说明
内置 codex-rs/execpolicy/builtin/ Codex 自带的安全规则(不可修改)
用户 ~/.codex/config/rules/ 用户全局偏好
项目 .codex/rules/ 或项目根 项目特定规则

[源码] ExecPolicyManager::load(config_stack) 按优先级合并。

决策流程

命令字符串
  |
  +-- shell lowering(处理 bash -c, pipe, &&)
  |   -> 拆分为多段命令
  |
  +-- Policy::check_multiple 逐段匹配 .rules 文件
  |   +-- 命中 Allow 规则 -> Skip { bypass_sandbox: true }
  |   +-- 命中 Prompt 规则 -> NeedsApproval
  |   +-- 命中 Forbidden 规则 -> Forbidden
  |
  +-- 未匹配 -> render_decision_for_unmatched_command
      +-- 已知安全命令(ls, cat, grep...) -> Skip
      +-- 已知危险命令(rm -rf, sudo...) -> NeedsApproval / Forbidden
      +-- 其他 -> 根据 approval_policy + sandbox 策略决定

关键代码

文件:codex-rs/core/src/exec_policy.rs

// 决策核心(简化)
match evaluation.decision {
    Decision::Forbidden => ExecApprovalRequirement::Forbidden { reason },
    Decision::Prompt => {
        match prompt_is_rejected_by_policy(approval_policy, prompt_is_rule) {
            Some(reason) => Forbidden { reason },
            None => NeedsApproval { reason, proposed_amendment },
        }
    }
    Decision::Allow => Skip {
        bypass_sandbox: all_segments_explicitly_matched,
        proposed_execpolicy_amendment: ...,
    },
}

[源码] 用户批准命令后,append_amendment_and_update 将新规则持久化到 default.rules 并热更新内存策略——学习效应:同一命令不会反复弹审批。


Approval:三级审批优先级

当 exec policy 返回 NeedsApproval 时,ToolOrchestrator 启动 request_approval:

flowchart LR
    subgraph priority["审批优先级(命中即停)"]
        direction TB
        H[Permission Request Hooks] -->|"Allow/Deny"| DONE[立即采纳]
        H -->|无匹配| G[Guardian]
        G -->|"Approve/Reject"| DONE
        G -->|未启用| U[User]
        U -->|"Accept/Decline/Cancel"| DONE
    end

三级审批详解

级别 触发条件 说明
Hooks 项目配置了 permission request hooks 外部进程/脚本做自动决策,最高优先 [源码]
Guardian approvals_reviewer = “auto_review” 或 strict_auto_review AI 子 agent 做风险评估 [源码]
User 以上都未启用或未决策 通过 App Server 弹出审批请求给人类 [源码][文档]

审批决策类型

用户(或 Guardian)可以返回以下决策:


Sandbox Escalation:失败后的升级链

当首次执行被 sandbox 拒绝(SandboxErr::Denied),ToolOrchestrator 按以下决策链判断能否重试:

sandbox denied
  |
  +-- 网络策略拦截 且 无审批上下文? -> 直接失败
  +-- tool.escalate_on_failure() == false? -> 直接失败
  +-- tool.wants_no_sandbox_approval() == false? -> 检查特殊场景,否则失败
  +-- unsandboxed_execution_allowed == false 且 无网络审批? -> 直接失败
  |
  +-- 通过所有检查 -> 决定二次审批方式:
      +-- 已批准过 且 非 strict_auto_review 且 无网络审批
      |   -> 跳过二次审批,直接重试
      +-- 否则 -> 带 retry_reason 重新走 request_approval
          -> 批准后以 SandboxType::None 或重新选择沙箱执行

[源码] 这种设计保证了:安全性默认最大化(先 sandbox),但用户体验不至于被误报卡死(可升级)。


工具注册与路由

ToolRouter

ToolRouter 在每个 turn 开始时构建,持有完整的工具注册表和模型可见的工具规格(JSON Schema)。

工具分发流程

model output: FunctionCall { name: "shell", arguments: {...} }
  -> ToolRouter::build_tool_call -> ToolCall { tool_name, call_id, payload }
  -> registry.dispatch_any -> 按 tool_name 分发到对应 Handler
  -> Handler 执行 -> 返回 ToolOutput

内置工具清单

类别 工具 说明
代码执行 shell Shell 命令执行(走 exec policy + sandbox)
代码执行 unified_exec 新版统一执行接口(含 write_stdin)
文件编辑 apply_patch 结构化补丁应用
多 Agent spawn_agent 创建子 agent
多 Agent wait_agent 等待子 agent
多 Agent send_message / followup_task 向子 agent 发消息
多 Agent interrupt_agent / list_agents 管理子 agent
外部工具 MCP tool call 调用外部 MCP server 工具
元工具 tool_search 搜索可用工具
元工具 plan 生成执行计划
元工具 get_context_remaining 查询剩余上下文窗口
元工具 new_context_window 请求新的上下文窗口
交互 request_user_input 向用户提问
交互 request_permissions 请求额外权限
系统 current_time / sleep / view_image 辅助工具

[源码] 工具注册在 tools/registry.rs,每个 handler 实现 ToolExecutor trait。


Turn 内并行执行

Codex 在单个 turn 内支持并发执行多个 tool calls:

文件:codex-rs/core/src/session/turn.rs

// try_run_sampling_request 内部(简化)
let mut in_flight: FuturesOrdered<_> = FuturesOrdered::new();

while let Some(event) = stream.next().await {
    match event {
        OutputItemDone(FunctionCall { .. }) => {
            // 立即 spawn 异步 tool 执行
            let handle = tokio::spawn(dispatch_tool_call(...));
            in_flight.push_back(handle);
        }
        Completed { .. } => break,
    }
}
// 等待所有 in-flight tool calls 完成
drain_in_flight(&mut in_flight).await;

[源码] 模型在一次响应中返回多个 tool calls 时(如同时读 3 个文件),它们并发执行而非顺序等待。


与 LangGraph 的对比

维度 Codex ToolOrchestrator LangGraph Tool Node
编排方式 线性状态机(approval - sandbox - execute - retry) 图节点,可配置前后置条件
并行粒度 Turn 内多 tool 并发 Send API 分支并行
安全决策 exec policy + 三级审批 + OS sandbox 应用层自行实现
规则学习 用户批准后自动写入 .rules 文件 无内置机制
失败恢复 sandbox escalation 自动重试 需要在图中显式建模 fallback edge

与 Claude Code 的差异(待 Agent A 核对)


局限性


证据等级汇总

结论 来源
ToolOrchestrator 三阶段(approval - sandbox - execute - retry) [源码] orchestrator.rs
ExecPolicy 三层配置合并 [源码] exec_policy.rs
三级审批优先级:hooks - guardian - user [源码] orchestrator.rs request_approval
sandbox escalation 五步决策链 [源码] orchestrator.rs run 方法
用户批准后自动写入 .rules 持久化 [源码] exec_policy.rs append_amendment
Turn 内 FuturesOrdered 并发 tool calls [源码] session/turn.rs
25+ 内置工具 handler [源码] tools/handlers/ 目录