Codex CLI 源码深度技术解析:OpenAI 开源编码 Agent 的 Rust 架构全貌

从 SQ/EQ 事件循环、三平台沙箱到 Skills/Hooks 扩展体系的实现剖析

Posted by 爱折腾的工程师 on Sunday, August 23, 2026

一、项目背景:OpenAI 的官方开源编码 Agent

如果说 2025 年是 AI 编码 Agent 的爆发年,那么 OpenAI 的 Codex CLI 就是这场竞赛中最值得细读的开源样本之一:它由 OpenAI 官方维护、以 Apache-2.0 协议开源,是一个运行在本地计算机上的编码 Agent——模型推理在云端,但读文件、改代码、跑命令全部发生在你自己的机器上,通过 ChatGPT 订阅(Plus/Pro/Business/Enterprise)或 API Key 认证。

这个项目有一条颇具代表性的演进路线:早期版本是 TypeScript 写的 Node.js CLI,如今已经完整重写为 Rust。仓库根目录下的 codex-cli/ 如今只剩一个纯粹的 npm 安装壳——bin/codex.js 只做一件事:根据平台和架构选择对应的原生二进制包(如 @openai/codex-darwin-arm64)并拉起真正的 Rust 可执行文件。所有 Agent 逻辑都沉淀在 codex-rs/ 这个庞大的 Cargo workspace 里。

为什么要重写?从源码里能读出几个直接答案:启动速度与分发体积(单一静态二进制,musl 静态编译 Linux 包)、类型系统对协议层的强约束(下文会看到 Rust 类型如何直接生成 TypeScript 定义)、以及并发安全(Agent 的异步事件循环正是 Rust + tokio 的主场)。

项目概貌

维度 数据
核心语言 Rust(workspace edition 2024)+ TypeScript(SDK / 协议类型)
Workspace 规模 codex-rs/ 下 90+ crates,含 138 个 workspace 成员
模型接口 OpenAI Responses API(SSE 流式)
终端 UI ratatui 0.30 + crossterm(OpenAI 维护 fork)
持久化 SQLite(sqlx,61 个迁移 SQL)+ rollout 会话文件
沙箱 macOS Seatbelt / Linux bubblewrap+seccomp / Windows RestrictedToken+WFP
规则引擎 Starlark(execpolicy)
脚本运行时 内嵌 V8(code-mode,跑 TypeScript 工具)
构建体系 Bazel + Cargo 双构建,CI 校验 lockfile 漂移
可观测 OpenTelemetry(OTLP)+ Sentry

本文基于当前 main 分支源码快照分析,涉及文件路径均相对 codex-rs/。文中观点仅代表个人技术解读。

本文导读

  • 第二节先用一张分层架构图建立全局认知:五种前端形态如何共享同一套 Rust 内核;
  • 第三、四节是主执行链路:SQ/EQ 事件循环、工具系统、纵深防御的沙箱体系、扩展机制与提示词工程;
  • 第五、六节看多端接入形态、工程治理实践与完整技术栈;
  • 适合关注 AI Coding Agent、终端工程、权限治理或 Rust 工程化的读者。

二、架构概览:一套内核,五种前端

Codex CLI 分层架构总览

整个 codex-rs workspace 可以归纳为六层:

接入层是最令人印象深刻的设计——同一个 Agent 内核对外的五种形态,全部是可插拔的薄壳:

  • TUItui/ crate,ratatui 驱动的终端交互界面,codex 默认入口;
  • execexec/ crate,非交互一次性执行,输出 JSONL 事件流,是脚本/CI 与 SDK 的底层驱动;
  • app-serverapp-server/ crate,JSON-RPC 2.0 服务,VS Code 扩展等富客户端的标准接口;
  • MCP Servermcp-server/ crate,把 Codex 本身暴露为两个 MCP 工具(codex / codex-reply),供其他 Agent 调用;
  • TS SDKsdk/typescript/@openai/codex-sdkCodex → Thread → Turn 三级 API,底层 spawn codex exec

协议层protocol/ crate)定义了所有跨层数据结构:Op 提交与 Event 事件的双队列(下文详述)、Thread/Turn/Item 会话语汇、权限与沙箱策略类型。这一层通过 ts-rs 直接生成 TypeScript 类型,保证 Rust 与 TS 客户端的 wire format 永不漂移。

核心层core/ crate)是 Agent 的大脑:Session 状态机、Turn 循环、工具路由、上下文组装与压缩、Hooks 运行时、MCP 管理器。值得注意的是,AGENTS.md 里专门写了一条治理红线:

resist adding code to codex-core! —— 新功能优先考虑其他 crate,必要时为新概念开一个新 crate,而不是往最大的 codex-core 里继续堆代码。

能力层把每个扩展点做成独立 crate:apply-patch(补丁应用)、skills(技能发现与注入)、hooks(生命周期钩子)、pluginscodex-mcp(MCP 客户端连接管理)、code-mode(内嵌 V8 执行 TS 工具逻辑)。

安全层是本文重点之一:sandboxing(跨平台沙箱管理)、linux-sandbox / windows-sandbox-rs(平台辅助二进制)、network-proxy(网络管控)、execpolicy(Starlark 规则)、guardian(LLM 自动审批)。

基础层则是 state(SQLite)、rollout(会话持久化/resume)、otelconfig、30 多个 utils/* 小 crate(绝对路径、流解析、模糊匹配、模板等)——小 crate 策略让依赖关系始终单向、可测。

三、核心机制解析

3.1 SQ/EQ:事件驱动的 Turn 循环

Codex 的内核并发模型非常干净,protocol/src/protocol.rs 开头注释一锤定音:

Uses a SQ (Submission Queue) / EQ (Event Queue) pattern to asynchronously communicate between user and agent.

一次 Turn 的完整生命周期

用户侧的一切动作被建模为 Op(提交进 SQ),Agent 的一切产出被建模为 Event(广播进 EQ)。Submission 结构体除了携带 op 载荷,还带 trace(W3C trace context,跨异步交接传播链路追踪)以及 parent_turn_id / root_turn_id——后者是多 Agent 编排的基础,子 Agent 的提交可以追溯到直接发起者和顶层因果根:

// codex-rs/protocol/src/protocol.rs
pub struct Submission {
    pub id: String,                    // 关联 Event 的唯一 ID
    pub op: Op,                        // 载荷
    pub trace: Option<W3cTraceContext>,// 跨异步交接的链路追踪
    pub parent_turn_id: Option<String>,// 直接发起本提交的父 turn(Agent 间通信)
    pub root_turn_id: Option<String>,  // 因果链上的顶层 turn
}

Op 枚举覆盖了打断(Interrupt)、后台终端清理、实时语音会话(RealtimeConversationStart/Audio/Text/...)、以及最核心的 TurnInput。而执行侧的 Session 有一条硬约束:

// codex-rs/core/src/session/session.rs
/// A session has at most 1 running task at a time,
/// and can be interrupted by user input.
pub(crate) struct Session { ... }

一次 Turn 在 core/src/session/turn.rs 里的完整流程是:

  1. 上下文组装:运行 SessionStart 等 hooks、注入 skills 目录与显式技能内容、读取 AGENTS.md 项目说明、拼装环境上下文(时间、OS、git 状态、目录结构);
  2. 模型请求:向 Responses API 发起 SSE 流式请求,支持按模型路由与重试(responses_retry.rs);
  3. 流式解析AssistantTextStreamParser 增量解析响应,delta(正文/推理摘要)实时流入 EQ 供前端渲染,同时识别 function_call 响应项;
  4. 工具分发ToolRouter(由 tools/spec_plan.rs 按模型能力构建)把调用路由到注册表中的处理器,支持并行工具调用(tools/parallel.rs);
  5. 审批闸门:经策略矩阵裁决(详见 3.3),放行则在沙箱内执行,结果作为工具输出回填上下文,回到第 2 步循环,直到模型不再产生工具调用;
  6. 收尾:发出 TurnCompleted,附带 turn diff 汇总(turn_diff_tracker.rs)与 token 用量,全量写入 rollout 文件支持 resume

两条容易被忽略的精妙设计:

  • 上下文治理成文。AGENTS.md 里"Model visible context"一节规定:历史只增不改(保证前缀缓存命中)、注入片段必须有硬上限(单项不超过 10K token)、所有注入必须实现为 core/context 里的 ContextualUserFragment 结构体。逼近预算时,compact.rs 会触发 auto-compact 自动压缩历史(还支持服务端远程压缩的 v2 协议)。
  • 可转向(steering)。Turn 运行中用户的新输入不是排队等下一轮,而是作为 SteerSubmission 参与当前任务的改向,配合 Interrupt 构成完整的中断语义。

3.2 工具系统与 apply_patch

工具子系统的骨架在 core/src/tools/registry.rs(注册表)→ router.rs(按名字空间路由,工具名形如 namespace.tool)→ orchestrator.rs(编排)→ handlers/(各工具实现)。所有需要审批的动作统一收敛为一个枚举:

// codex-rs/core/src/tools/approvals.rs
pub enum ApprovalAction {
    ExecCommand,        // shell 命令
    Execve,             // 直接 execve
    ApplyPatch,         // 文件补丁
    McpToolCall,        // MCP 工具调用
    NetworkAccess,      // 网络访问提权
    RequestPermissions, // 权限申请
}

文件修改走的是自研的 apply_patch 而非通用 shell:模型输出一种类似 git diff 的 V4A 补丁格式(heredoc 包裹),apply-patch/ crate 提供流式解析器StreamingPatchParser,边生成边解析)与确定性应用逻辑(derive_new_contents_from_chunks),并能把应用结果反向渲染成 unified diff 展示。两个细节值得玩味:

  • 单二进制自调用:补丁还可以作为独立命令执行——Codex 主程序通过 arg0 分发,以 --codex-run-as-apply-patch 特殊参数自我调用进入 apply_patch 模式,省去多带一个二进制;
  • 换行符策略可配置:NormalizeToLf(默认,行为兼容历史)或 PreserveLineEndings(保留文件既有换行风格),这类边角正确性正是生产级 Agent 与 demo 的分水岭。

3.3 纵深防御:沙箱、网络与审批

安全体系是 Codex 源码里最"豪华"的部分,四层递进、全链路 fail-closed:

纵深防御安全栈

第一层:三平台 OS 级沙箱sandboxing/ crate 的 SandboxManager 是统一入口,SandboxType 枚举三种实现:macOS 走 /usr/bin/sandbox-exec + SBPL 策略(deny default 起步、仿 Chrome 白名单,策略文件 include_str! 编译进二进制,运行时按可写根/只读例外/代理端口动态拼接);Linux 默认走独立辅助二进制 codex-linux-sandbox——bubblewrap 做文件系统隔离(user namespace)+ no_new_privs + seccomp 网络过滤(Landlock 仅作 legacy 回退),沙箱内网络命名空间的流量经 UNIX socket 桥回宿主机代理;Windows 则创建专用沙箱用户,用 CreateRestrictedTokenDISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED + capability SID)降权执行,文件系统靠 ACL/DACL,网络靠 WFP(Windows Filtering Platform)过滤器硬断直连。

第二层:网络管控不靠 iptablesnetwork-proxy/ 的思路是把沙箱内的出网流量全部赶到 127.0.0.1 的环回代理上:沙箱内只放行 loopback,通过注入 HTTP_PROXY/HTTPS_PROXY/ALL_PROXY 环境变量强制进程走代理;代理基于 rama 实现 HTTP CONNECT / SOCKS5,按域名白名单逐连接裁决(deny 优先),TargetCheckedTcpConnector 还会拦截非公网 IP 直连,可选 TLS MITM 模式提供凭据代理。这套设计跨平台一致,不依赖宿主机 root 权限改防火墙。

第三层:审批策略矩阵AskForApproval(untrusted / on-request / granular / never)与 SandboxMode(read-only 默认 / workspace-write / danger-full-access)两个正交维度,在 core/src/exec_policy.rs 中合成 Allow / Prompt / Forbidden 三态裁决。核心规则很克制:沙箱兜底成立时尽量不打扰用户——on-request 策略下,受控沙箱内的普通命令直接放行,只有请求沙箱提权或命中危险模式才升级为 Prompt;而危险命令或无沙箱保护时,连 never 也强制 Forbidden。

第四层:规则引擎 + LLM 自动审批execpolicy/ 用 Starlark 写命令规则(prefix_rule 支持 allow/prompt/forbidden 三态与 justification 说明,host_executable 锁定可执行文件绝对路径防同名劫持,match/not_match 示例在加载期即被校验——相当于策略自带单测)。而 guardian/ 则是一个 LLM 安全评审子代理:收到审批请求时重建精简 transcript,独立会话输出严格 JSON 的风险评估(风险等级/授权范围/结论/理由),90 秒超时或解析异常一律 fail-closed 拒绝,同一 turn 连续拒绝达到阈值还会熔断中断。

3.4 可扩展性体系:MCP、Skills、Hooks

MCP 双向打通。作为客户端,codex-mcp/ 基于 rmcp 管理外部 MCP server 连接(mcp_connection_manager.rs 统一处理工具变更与调用,支持 elicitation 交互);作为服务端,mcp-server/ 仅暴露两个工具——codex(新开会话)与 codex-reply(按 threadId 续会话),审批请求通过 MCP elicitation 回传给宿主 Agent,等于让任何 MCP 宿主都能"雇佣"Codex 干活。

Skills 采用渐进式披露。技能就是一个带 SKILL.md(YAML frontmatter 写 name/description)的目录,可附 references/scripts/assets/。发现路径覆盖四层:项目 .codex/skills/、用户 ~/.agents/skills/(与业界 $SKILL 约定对齐)、系统内置(include_dir! 嵌入二进制、按指纹增量安装,预置 skill-creator、review-agent 等六个)、管理员层。注入策略分两级:目录(名称+描述+使用规则)常驻系统提示,正文则只在被 $SkillName 显式点名或任务匹配时按 turn 注入并截断——先读目录再按需加载全文,天然控制上下文膨胀。

Hooks 覆盖 11 个生命周期事件PreToolUse / PermissionRequest / PostToolUse / PreCompact / PostCompact / SessionStart / SessionEnd / UserPromptSubmit / SubagentStart / SubagentStop / Stop,支持命令行式与 MCP 式两种 runner,每个事件的输入/输出都有生成的 JSON Schema。企业可以在不改 Codex 代码的前提下插拔自己的合规检查。

3.5 提示词与上下文工程

提示词全部是编译期内嵌的:prompts/templates/ 下按用途分组(compact 压缩、goals 目标管理、permissions 策略说明、realtime 语音、review 评审),经 include_str! 进二进制,需要变量的模板(如 compact 的 token 预算)用轻量 Template 运行时渲染。基础系统提示词维护在 models-manager/prompt.md,并按模型套用专属 instructions 模板({{ personality }} 占位符渲染),用户可用 config 覆盖——提示词即代码,纳入版本控制与代码评审。

四、多前端形态与 SDK

  • TUItui/,约 1500 个文件):chatwidget.rs 是编排中枢——消费 app-server 事件、维护已提交 transcript 与流式活动单元、驱动审批弹窗与 transcript 回放 overlay。配色规范固化在 tui/styles.md(cyan=用户交互、green=成功、red=错误、magenta=品牌色)。质量手段相当硬核:600+ 张 insta 快照测试锁定每一次 UI 渲染回归。
  • execcodex exec "..." 一次性执行,--json 输出 JSONL 事件流,--output-schema 约束最终消息结构,支持 resume 与 ephemeral 会话。它还是 SDK 的底层通道。
  • app-server:JSON-RPC 2.0(wire 上省略 "jsonrpc" 头),支持 stdio / WebSocket / Unix socket 三种传输;initialize 握手后以 thread/start → turn/start → item/* → turn/completed 驱动会话,thread/fork 支持从既有对话分叉。入站过载时返回 -32001 要求客户端退避重试——工程细节拉满。类型契约用 ts-rs 从 Rust 导出(just write-app-server-schema 生成 JSON Schema fixtures),Rust 改一个字段,TS 侧类型与 schema 同步更新。
  • TS SDKCodex → startThread() → thread.run() / runStreamed()ThreadEvent 覆盖 thread.started / turn.started|completed|failed / item.*,item 类型包括 agent_message、reasoning、command_execution、file_change、mcp_tool_call、web_search、todo_list 等。

五、实现亮点与工程实践

抛开功能,这个仓库本身就是一份大型 Rust 工程的参考实现:

  1. 类型全链路同步serde + schemars + ts-rs 三件套,Rust 单一事实源同时产出 JSON Schema(config、hooks、app-server API)与 TypeScript 类型,前后端协议永不漂移;
  2. Bazel + Cargo 双构建:日常开发用 just(fmt/test/fix/bench),CI 用 Bazel 保证可复现构建,MODULE.bazel.lockCargo.lock 联动,CI 校验漂移;
  3. 激进的 lint 文化:workspace 级 clippy denyunwrap_used / expect_usedcodex-core 的 lib.rs 甚至 deny print_stdout/print_stderr——库代码禁止直接碰标准输出,一切用户可见输出必须走 TUI 或 tracing 抽象;
  4. 规模预算成文:模块目标 <500 行、超 800 行必须拆分;单次变更复杂逻辑 <500 行、机械变更 <800 行;写进 AGENTS.md 并在评审中强制执行;
  5. 测试分层:Agent 逻辑改动强制要求集成测试(core/suite,用 wiremock 挂载 SSE mock 断言出站请求体);TUI 用 insta 快照;性能用 divan 基准(just bench);
  6. 单二进制多路复用:通过 arg0 分发,同一个 codex 二进制按调用名切换为 TUI、codex-linux-sandbox 沙箱执行器或 apply_patch 独立命令;
  7. 上游 fork 自持:crossterm、tungstenite 等以 openai-oss-forks 维护 fork 并锁定 rev,需要的补丁不必等上游;
  8. 前沿运行时试验田code-mode 系列 crate 内嵌 V8(v8 = "=150.4.0")在本地执行 TypeScript 工具逻辑,配合 tree-sitter(bash/PowerShell 语法解析做命令理解)构成传统 shell 之外的另一种工具执行路径。

六、技术栈说明

类别 选型 用途
异步运行时 tokio + futures + async-channel SQ/EQ、工具并发
终端 UI ratatui 0.30 + crossterm(fork) TUI 渲染与事件
序列化/类型 serde / serde_json / ts-rs / schemars 协议、schema、TS 代码生成
模型接口 reqwest + eventsource-stream Responses API SSE 流
数据库 sqlx + bundled SQLite 会话状态(61 个迁移)
规则引擎 starlark execpolicy 命令策略
沙箱 landlock / seccompiler / 系统调用 三平台 OS 沙箱
MCP rmcp 3.1.3 MCP 客户端与服务端
代码解析 tree-sitter(bash/PowerShell) shell 命令结构化理解
脚本运行时 v8 code-mode 执行 TS 工具
可观测 tracing + OpenTelemetry(OTLP)+ Sentry 遥测与错误上报
测试 wiremock / insta / divan / pretty_assertions 集成/快照/基准测试
构建 Bazel + Cargo + just + pnpm workspace 多语言统一构建

七、总结与展望

把 Codex CLI 的源码通读下来,最能带走的是三条架构判断:

  • 事件驱动内核 + 可插拔前端是编码 Agent 的正确骨架。SQ/EQ 双队列把"用户意图"与"Agent 产出"彻底解耦,于是终端、IDE、SDK、MCP 五种形态共享同一套经过打磨的 Turn 循环,新前端只需实现协议层即可;
  • 安全必须是纵深防御的默认值。OS 沙箱划硬边界、环回代理管网络、策略矩阵定裁决、Starlark 规则与 LLM guardian 减少人工打扰——四层每一层都 fail-closed,“默认最小权限"不是口号而是可验证的代码路径;
  • 上下文是被治理的稀缺资源。只增不改的历史、硬上限的注入片段、成文的 token 预算规则、自动压缩兜底——在长会话 Agent 里,上下文管理的工程化程度直接决定产品上限。

与同为顶流的 Claude Code(TypeScript + Ink,React 式终端渲染)相比,Codex 的 Rust 重写换来了启动速度、静态分发的单二进制、以及编译期类型对协议的强约束;而两者在"沙箱优先、审批分级、技能渐进披露"上的趋同,或许正标记着编码 Agent 领域工程范式的收敛。

展望后续,仓库里已经埋好了几条演进线:multi_agents 多 Agent 协作与 codex_delegate 委派、cloud-tasks 云端任务、exec-server 远程执行环境、realtime_conversation 实时语音,以及 external-agent-migration 从其他 Agent 导入配置的互操作层。对于想深入 AI Agent 工程化的开发者,这个仓库的 AGENTS.md(工程规约)、core/src/session/(Agent 循环)和 sandboxing/(安全实现)是三块最值得精读的矿区。

参考:openai/codex · Codex 官方文档 · 本文图表 SVG 源文件位于本站 /img/codex-cli-source-analysis/

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付