源码对照(二):Codex 的工具、沙箱与扩展生态
专栏:Agent 工程 · 第 17 / 18 篇上篇看了 Codex 的循环骨架,这篇拆它”放权给模型”的那套系统工程:工具怎么定义与分发、审批怎么分级、沙箱怎么落地,以及 MCP、code-mode、skills 这层扩展生态。
工具系统:统一路由 + 分工明确的 handlers
tools/ crate 定义 ToolSpec,直接序列化为 OpenAI Responses API 的工具 JSON;core 里的 ToolRouter 把模型输出的各种调用形态(FunctionCall / CustomToolCall / ToolSearchCall)归一成统一的 ToolCall 再分发:
// codex-rs/core/src/tools/router.rs:246-296(节选)
pub fn build_tool_call(item: ResponseItem) -> Result<Option<ToolCall>, FunctionCallError> {
match item {
ResponseItem::FunctionCall {
name, namespace, arguments, call_id, ..
} => {
let tool_name = ToolName::new(namespace, name).with_default_namespace();
Ok(Some(ToolCall {
tool_name, call_id,
payload: ToolPayload::Function { arguments },
...
}))
}
ResponseItem::CustomToolCall { name, input, call_id, .. } => Ok(Some(ToolCall {
tool_name: ToolName::new(namespace, name).with_default_namespace(),
call_id,
payload: ToolPayload::Custom { input },
...
})),
_ => Ok(None),
}
}
内置工具清单(core/src/tools/handlers/)值得细读,每个都对应一类真实需求:exec_command(shell,参数含 yield_time_ms/max_output_tokens——流式取输出的设计)、apply_patch(补丁式改文件)、view_image、update_plan(任务计划)、tool_search(工具太多时按需检索)、request_user_input、mcp(转发外部 MCP 工具)、multi_agents(子智能体)、get_context_remaining(模型自己看剩余上下文预算!)。分发之后还有一层 ToolOrchestrator 负责重试与沙箱降级编排——失败时按错误类型决定是否换更宽的沙箱策略重试。
审批:四级策略 + 规则引擎 + 审批缓存
审批的核心枚举精炼到可以直接背下来:
// codex-rs/protocol/src/protocol.rs:984-1007
pub enum AskForApproval {
/// 不可信项目:命令一律要审批,除非 execpolicy 规则显式放行
#[serde(rename = "untrusted")]
UnlessTrusted,
/// 模型自己决定何时请求审批
#[serde(alias = "on-failure")]
#[default]
OnRequest,
/// 细粒度控制:按类别的命令分别允许/自动拒绝
Granular(GranularApprovalConfig),
/// 从不询问:失败直接返回给模型,绝不升级给用户
Never,
}
和第 12 篇 dsh 的 ApprovalPolicy = 'ask' | 'never' 对照:Never 两边语义完全一致(fail-closed 的确定性拒绝);Codex 额外把”什么要审”做成了三层——untrusted(全审+白名单)、OnRequest(模型自主决定,默认)、Granular(按类别细控)。审批的内容也类型化了(core/src/tools/approvals.rs):ExecCommand(带建议的 execpolicy 修正案!)、ApplyPatch、McpToolCall、NetworkAccess、RequestPermissions——每一种都携带审批 UI 需要的全部结构化信息。两个锦上添花的设计:审批缓存(同一命令免重复审批)与 execpolicy 规则引擎(把”哪些命令可信”从人工点击变成可版本化的规则)。
沙箱:三平台统一接口
// codex-rs/sandboxing/src/manager.rs:41-58(节选)
pub enum SandboxType {
None,
MacosSeatbelt,
LinuxSeccomp,
WindowsRestrictedToken,
}
macOS 走 /usr/bin/sandbox-exec(Seatbelt 策略模板内嵌在二进制里);Linux 默认 bubblewrap + seccomp 的 codex-linux-sandbox 辅助进程(Landlock 是需显式开启的 legacy 路径);Windows 走受限令牌。沙箱模式三档:read-only(默认)/ workspace-write / danger-full-access——与 dsh 的沙箱模式逐词相同。网络是独立维度:network-proxy 用 MITM 代理管控出网,访问外网要过 NetworkAccess 审批。对照第 10 篇 dsh 的 SandboxProvider.confine(argv, policy):同一个抽象(包装 argv + 按调用携带策略),两套独立实现。
扩展生态:MCP、code-mode、skills、hooks
几个观察:
- MCP 是客户端:
codex-mcp管理外部 server 的连接、OAuth 登录、工具目录缓存,工具经统一路由与内置工具平权竞争; - code-mode 是个有意思的实验方向:把”模型逐个发 tool_call”变成”模型在运行时会话里写代码、代码里批量调工具”——多工具组合任务省 token 且原子性更好;
- skills 用 SKILL.md 声明,支持
@提及与从用户输入检测隐式调用——和 dsh 的 skill 机制同源(这套约定已是跨产品的通用语); - AGENTS.md:从项目根(默认以
.git为根标记)向下逐级拼接,项目级指令的通用约定; - hooks 覆盖 PreToolUse/PostToolUse/Stop/PreCompact/SessionStart——又与 dsh 的事件瀑布位一一对应。
审批与执行的时序
小结:安全放权是一套系统,不是一个开关
Codex 给出的答案是五层叠加:规则引擎(什么免审)→ 分级审批策略(什么要问)→ 结构化审批请求(让人类看得懂再点)→ OS 级沙箱(问少了也出不了圈)→ 网络管控(出圈也出不去网)。这五层与 dsh 的 approval/permission-presets/sandbox/seam 是同一张图的两种画法。下一篇我们把两个系统放在一起,提炼工业级 agent 的共同设计模式。