源码对照(二):Codex 的工具、沙箱与扩展生态

专栏:Agent 工程 · 第 17 / 18 篇
AgentCodex沙箱MCP

上篇看了 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_imageupdate_plan(任务计划)、tool_search(工具太多时按需检索)、request_user_inputmcp(转发外部 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 修正案!)、ApplyPatchMcpToolCallNetworkAccessRequestPermissions——每一种都携带审批 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 + seccompcodex-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

图表(codex-tools-sandbox-mcp.md)

几个观察:

  • 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-tools-sandbox-mcp.md)

小结:安全放权是一套系统,不是一个开关

Codex 给出的答案是五层叠加:规则引擎(什么免审)→ 分级审批策略(什么要问)→ 结构化审批请求(让人类看得懂再点)→ OS 级沙箱(问少了也出不了圈)→ 网络管控(出圈也出不去网)。这五层与 dsh 的 approval/permission-presets/sandbox/seam 是同一张图的两种画法。下一篇我们把两个系统放在一起,提炼工业级 agent 的共同设计模式。

← 返回文章列表