Codex 工具循环精读
调研时间:2026-06-22 源码 commit:98845e4 本地 CLI:codex-cli 0.125.0
一句话定位
ToolOrchestrator 是 Codex 工具执行的状态机——它把「审批 - 沙箱 - 执行 - 失败升级重试」固化为确定性的代码路径,而非依赖模型即兴决策。exec policy 在此之前做第一道命令分类,sandbox 在此之后做最后一道 OS 级防线。
日常类比
把 ToolOrchestrator 想象成银行的「贷款审批 + 放款」流水线:
- exec policy(信用评分系统):自动给命令打分——白名单命令秒过,危险命令直接拒绝,灰色地带需要人工审批
- Approval(信贷经理审批):先走自动化风控规则(hooks),过不了找 AI 风控员(guardian),再过不了找真人(用户)
- Sandbox(保险箱):放款后,资金只能在限定范围内使用;即使贷款被批准,实际执行仍然受物理隔离约束
- 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)可以返回以下决策:
- accept — 仅本次批准
- acceptForSession — 本 session 内同类命令不再询问
- acceptWithExecpolicyAmendment — 批准并写入 exec policy 规则(永久学习)
- applyNetworkPolicyAmendment — 批准网络访问并记录
- decline — 拒绝执行
- cancel — 取消整个 turn
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 核对)
- Codex exec policy 是声明式规则文件(类 .gitignore 语法),支持 shell lowering 解析管道命令;Claude Code 使用 permission classifier 做动态分类(推测为 ML/规则混合)
- Codex 审批后自动写入 .rules 文件实现永久学习;Claude Code 的 permission 持久化机制不同(trusted commands list)
- Codex sandbox 是 OS 内核级(Seatbelt/bwrap/Landlock),失败后可 escalation;Claude Code 沙箱主要在应用层(进程隔离 + 文件系统限制)
- Codex ToolOrchestrator 是单一状态机;Claude Code 工具执行更分散在 QueryEngine 的各个阶段
- Codex 支持 Guardian(AI 自动审查员)做无人值守场景;Claude Code 的对应机制为 trust mode / auto-approve 配置
局限性
- exec policy 误匹配:shell lowering 处理复杂管道命令时可能拆分不准确(如 bash -c 的嵌套场景)[源码]
- escalation 安全隐患:sandbox escalation 允许在用户批准后无沙箱执行,若 Guardian 被绕过则等同于全权限 [源码]
- 规则膨胀:频繁批准会导致 default.rules 文件越来越长,规则冲突难以管理 [待验证]
- Windows sandbox 覆盖不完整:Windows Sandbox 需要特定 OS 版本和特性启用,fallback 行为不如 Linux/macOS 稳定 [源码]
- 并发 tool calls 的状态一致性:多个 tool 并发修改同一文件时,apply_patch 可能冲突 [待验证]
证据等级汇总
| 结论 | 来源 |
|---|---|
| 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/ 目录 |