← 返回 Tony's Lab
OpenAI Codex · 源码级拆解

Codex
内核剖面

同一个 harness 驱动终端、IDE、桌面与云端四种外壳。把这层壳剥开,里面是一条 Rust 写的 agent 循环、一个双向 JSON-RPC 总线、一套三平台内核级沙箱,和一道审批门。本文按由外向内的顺序下潜一层层拆。

发布于 2026-10-02
源码调研 Agent 架构 沙箱 JSON-RPC
仓库
github.com/openai/codex
许可
Apache-2.0(引擎开源,桌面外壳闭源)
语言
Rust 工作区,Cargo + Bazel 双构建
规模
codex-rs 下 112 个 crate
协议
App Server:JSON-RPC over stdio (JSONL)
FIG.0 · 线束剖面REV 2026-10 SURFACES · 外壳 SANDBOX · 边界 APP SERVER CORE LOOP TOOLS 执行 STATE 记忆 MODEL
外护套=多端外壳 · 屏蔽层=沙箱与审批 · 导体=四大子系统 · 中心=模型
01
Surfaces
外壳
一个 harness,多种形态

Codex 不是「一个终端程序」,而是一个横跨四种产品的同一个 harness——那套 agent 循环与执行逻辑。差别只在外壳。

外壳形态如何接上 harness
Codex CLI / TUI终端界面历史上同进程直连 Rust core;正在改造为 App Server 客户端
IDE 扩展VS Code / Cursor / Windsurf / JetBrains / Xcode捆绑或下载平台专属 App Server 二进制,走 stdio
桌面 AppmacOS / Windows(Electron)捆绑固定版本的二进制,多线程并行编排 agents
Codex Web浏览器 + 云端容器worker 在容器里起 App Server,浏览器经 HTTP+SSE 收事件

开源边界:引擎开源,外壳不全开

Verified · 仓库结构

CLI 与整个 codex-rs 工作区是 Apache-2.0 开源的;但桌面 App 的 Electron 界面本身是闭源、代码签名的。开源仓库里与桌面相关的只有 codex-rs/cli/src/desktop_app/{mac,windows}.rs——那是负责下载并拉起闭源 App 的启动器,不是界面源码(搜遍仓库没有 Electron/renderer 目录)。所以社区才需要 codex-app-mirror 这类「原样镜像安装包」的仓库。

模型的权重同样不开放:Codex 调的是闭源 Responses API;OpenAI 的开放权重产品线是另一支 gpt-oss,与 Codex 无关。

为什么值得看源码

2025 年 4 月 CLI 上线时它还只是一个终端程序;到 2026 年,它已经变成 JetBrains、Xcode 等第三方愿意依赖的平台级协议。这条演化路径的技术决策,大部分都留在了公开仓库与 PR 里——这是本文的主要材料。

02
App Server
总线
JSON-RPC over stdio

把同一个内核塞进 IDE、桌面和云端,需要一个稳定的接缝。App Server 就是那条接缝:既是协议,也是一个长期驻留的进程。

它从哪来:一次失败的 MCP 尝试

给 VS Code 扩展复刻内核时,团队先试过「把 Codex 暴露成 MCP server」,但发现 MCP 语义很难表达 VS Code 需要的丰富交互(探索工作区、流式渲染推理、发 diff)。于是改成一个镜像 TUI 循环的 JSON-RPC 协议——这就是 App Server 的第一版。当时没打算对外,所以并没有设计成稳定 API。后来 JetBrains、Xcode 和桌面 App 都想要它,才被正式当作平台接口来维护。

一个 App Server 进程的四个部件

01
stdio reader
读入客户端 JSON-RPC
02
消息处理器
翻译成 core 操作,监听内部事件流
03
thread manager
每个 thread 起一个 core session
04
core threads
真正跑 agent 循环

关键在于翻译层:core 内部是细粒度事件,App Server 把它们收敛成一小撮稳定的、面向 UI 的 JSON-RPC 通知再推给客户端。协议完全双向——一次客户端请求可以换来许多事件,反过来服务器也能在需要审批时主动发起请求,并暂停该 turn 直到客户端回答。

三个对话原语

原语是什么生命周期
Item最小输入/输出单元,强类型(用户消息、agent 消息、工具执行、审批请求、diff…)item/started → 可选 item/*/delta → item/completed
Turn由一次用户输入发起的一个工作单元,内部含一串 itemturn/started → … → turn/completed
Thread持久化容器,一条 Codex 会话;可创建 / 恢复 / fork / 归档历史落盘,可重连渲染一致时间线

客户端开场必须先发一次 initialize 握手,服务器借此声明能力、对齐协议版本与特性开关。传输层只要支持双向流即可——实现上默认是 stdio 上的 JSONL,但设计上兼容 TCP / HTTP2 / gRPC。

协议是怎么「定稿」的

# 从 Rust 类型生成 TypeScript 定义
$ cargo run -p codex-app-server-protocol --bin export-ts

# 其他语言:导出 JSON Schema 交给各自的代码生成器
$ cargo run -p codex-app-server-protocol --bin export-json-schema

正因为有这套生成链,社区与内部已用 Go、Python、TypeScript、Swift、Kotlin 写出各自的客户端绑定——协议是「一份来源、多语言投影」的。

早期协议仍然存在

仓库里的 codex-rs/docs/protocol_v1.md 保留了更早的 SQ/EQ(提交队列/事件队列)模型:Op 上行、EventMsg 下行,Session 同一时刻最多跑一个 Task,要并行就「一个 thread 起一个 Codex」。每个 Turn 结束会带一个 response_id 书签,可日后从任意点分叉继续。

03
Core Loop
大脑
agent 循环与上下文

所有智能都在这一个循环里:组装提示词 → 请求模型 → 拿到工具调用 → 执行 → 把结果拼回输入 → 再请求,直到模型不再调工具、而是吐出一条给用户的消息。

一个 Turn 的骨架

A
组装 prompt
指令 + 工具 + 上下文
B
请求模型
Responses API,SSE 流式
C
执行工具
沙箱内跑命令 / 打补丁
D
回填结果
结果追加进 input,重发
E
收尾
assistant 消息 → turn 结束

用的是 Responses API,服务端以 SSE 流式返回;response.output_text.delta 之类事件驱动 UI 的流式渲染,response.output_item.* 则被转成对象追加进下一轮输入。

提示词是怎么拼出来的

面向 Responses API 的 payload 里,角色按权重降序为 system > developer > user > assistant。在用户消息之前,Codex 会插入这几层:

  1. developer 沙箱说明——由 workspace_write.md、on_request.md 等模板拼成。它只约束 Codex 自带的 shell 工具;MCP 等外部工具不受 Codex 沙箱保护,需自行负责边界。
  2. developer 可选:配置里的 developer_instructions。
  3. user 用户指令聚合:$CODEX_HOME 的 AGENTS.md,再从项目根一路向下找到 cwd 的各级 AGENTS.md(默认 32 KiB 上限),最后拼上已配置 skills 的前言、元数据与用法说明——越具体的越靠后。
  4. user 环境上下文:当前工作目录与用户的 shell。

这套顺序不是随便排的——它直接决定了下面这件事。

为什么它「故意保持无状态」

ZDR友好
不用 previous_response_id
前缀缓存
命中即线性,未中则平方
32KiB
AGENTS.md 聚合上限

Responses API 有 previous_response_id 可以让历史留在服务端,但 Codex 刻意不用:这样每个请求都无状态,能支持要求零数据留存(ZDR)的客户。代价是每轮都要把整段历史重新发一遍——网络流量接近平方级增长。解法是提示词缓存:只要前缀完全一致,采样就能复用上一次的算力,回到线性。

于是「保持前缀稳定」成了硬约束。以下操作会造成 cache miss,团队必须极度小心:

  • 对话中途改变了可用工具集(MCP 服务器的 tools/list_changed 尤其危险);
  • 换了模型(第三个 prompt 条目里含模型专属指令);
  • 改变了沙箱 / 审批模式 / cwd。

对后两类,Codex 的处理不是「改前面的消息」,而是在后面追加一条新消息来表达变化——保持旧前缀不动。仓库里那个「MCP 工具枚举顺序不一致」的 bug(PR #2611)就是这条原则的反面教材。

上下文不够了怎么办:压缩

当 token 超过 auto_compact_limit,Codex 会调用 Responses API 的 /responses/compact 端点,把整段 input 换成一份更短的表示。返回里含一个特殊的 type=compaction 条目,携带一段加密的 encrypted_content,用来保留模型对原对话的「潜在理解」。早期这需要用户手动敲 /compact,现在自动触发。

模型端点可替换

鉴权方式端点
ChatGPT 登录chatgpt.com/backend-api/codex/responses
API Keyapi.openai.com/v1/responses
--oss 本地localhost:11434/v1/responses(ollama / LM Studio)
云厂商任何实现 Responses API 的端点(如 Azure)

也就是说:harness 与模型是解耦的。这也是为什么「Codex 开源」和「Codex 模型开源」是两件完全不同的事。

04
Tools
手
工具与扩展

模型只能「说」;真正动到磁盘的是工具层。Codex 的工具面已经远超最初的「shell + patch」。

工具 / crate职责
shell / unified_exec跑命令;unified_exec 把交互式与一次性执行统一到一套执行器
apply_patch结构化打补丁(自带一整套 fixtures 场景测试)
exec-server把执行搬到远端环境:协议、发现、噪声中继、文件系统 RPC
code-mode让模型在 V8 里写代码来编排工具(gRPC 会话、host、runtime)
mcp / rmcp-clientMCP 客户端:stdio 与 HTTP,支持 OAuth;托管的 codex_apps
skills按 SKILL.md 装载能力(agentskills.io 标准),带动态选择器
plugins / connectors插件市场、连接器运行时

Code Mode:为什么仓库里塞了个 V8

翻 crate 列表会看到 third_party/v8、code-mode-runtime、v8-poc——Codex 会在内嵌的 V8 里执行模型写的代码,用它来编排多个工具调用。好处是把「几十次工具往返」压成一段程序;代价是要背进一整个 JS 引擎(还有 setup-rusty-v8 这类 CI 步骤)。这是一个明确的权衡,不是顺手加的。

MCP 与 skills 的边界

MCP 服务器提供的工具不经过 Codex 的沙箱——它们各自负责自己的护栏。skills 则是「给模型看的说明书」:Codex 会把每个 skill 的元数据与用法渲染进提示词,让模型知道什么时候该用哪个。两者都由同一套策略模型纳管,但安全责任划分不同。

05
Sandbox
边界
沙箱与审批门

这是 Codex 最值得抄的一层。它把「要不要问你」和「就算你同意了,它能碰什么」拆成两个正交的机制。

核心区分

审批(approval)回答:这个动作需要先确认吗? 沙箱(sandbox)回答:一个已被放行的进程,最多能触及什么。前者管交互风险,后者管真实边界。

三种沙箱模式

read-only 不给工作区写权限 · workspace-write 只开放工作区与显式 writable_roots · danger-full-access 不设限。写权限不等于网络权限——网络是另一条独立策略,默认关闭。

平台后端:不是一个通用的容器方案

平台机制要点
macOSSeatbelt(sandbox-exec,SBPL profile)默认全拒,再为工作区 / 临时目录 / 解释器 / 网络逐条开口;子进程一般继承限制
Linuxbubblewrap + seccomp-BPF + Landlockbwrap 造文件系统视图,seccomp 过滤系统调用,Landlock 做按路径的访问控制,no_new_privs 兜底
WindowsRestricted Token + ACL + 子进程规则不映射成「目录读写」模型,另有 WFP 与包身份相关组件

注意这些是内核原语,不是 Docker 那种 VM/命名空间容器——它们约束的是宿主上的一个进程树。所以「沙箱」在这里的含义更接近 capability 收缩,而不是「隔离出一个新世界」。

execpolicy:把「哪些命令要拦」写成规则

沙箱之外还有一层命令级策略。规则是 .rules 文件(Starlark 语法),放在配置层旁的 rules/ 目录里:

~/.codex/rules/default.rules# 碰到 `gh pr view …` 时先问一句,再放它出沙箱
prefix_rule(
    pattern   = ["gh", "pr", "view"],
    decision  = "prompt",
    justification = "看 PR 可以先问一下",
    match     = ["gh pr view 7888"],          # 内联样例,加载时自检
    not_match = ["gh pr --repo openai/codex view 7888"],
)
# decision ∈ { allow, prompt, forbidden }
  • 最严优先:多条同时命中时,forbidden > prompt > allow。
  • 命令被当作参数列表比对(近似 execvp(3) 收到的形态)。
  • 项目本地 .codex/rules/ 只有在项目层被信任时才加载;管理员可用 requirements.toml 强制约束。
  • 权限模型本质是前缀匹配——对 bash -lc "…" 这种包装调用会失效,文档也点明了这个弱点。

亲手试一下这道门

下面这个模拟器把上面两层机制合到一起。选沙箱模式与一个动作,看命令的结局(示意,用于理解规则):

Approval Gate · 交互示意
$ ……
ALLOW
说明。

注:真实结果还叠加了 execpolicy 规则、是否 on-request / never 等审批策略,以及 writable_roots 等配置。这里只演示「沙箱模式 × 动作」这一层。

06
State
记忆
状态、审查与多智能体

一个能跑几小时的 agent,必须能记住自己做过什么、并在放手之前先审自己一遍。

落盘:SQLite + rollout

codex-rs/state 是一套 SQLite 存储,带分域迁移脚本(goals / logs / memory / queue / thread_history);rollout 与 rollout-trace 负责把一次执行写成可回放的轨迹。线程历史另有 thread-store 与 thread_history。这是 App Server「客户端断开也能续上」的底气。

Guardian:它自己先审自己

仓库里有 guardian-reviewer、guardian-v2(含 async_scorer、sync_reviewer)与 guardian-context。这是一套自动审查机制:在动作执行前后做评估与打分。它还带熔断——auto_review.circuit_break_action = "strict" 时,否决次数超限会把 TooManyDenials 作为 turn.error 上报。换句话说,「人类审批」之外还有一条机器审批路径。

从单 agent 到多个 agent

core/src/tools/handlers/multi_agents(_v2)、agent-roles、collaboration-mode-templates、agent-message-board、ext/agent 共同构成多智能体能力。桌面 App 卖点之一就是「并行编排多个 agent」,而这套并行的底座就在 core 里——一个 turn 内可以派生子 agent、按角色分工、通过消息板通信。

记忆与目标

memories/read、memories/write 与 ext/memories 让 agent 读写长期记忆;ext/goal 则是目标系统。配合 skills,这构成一个「跨会话累积」的闭环——也解释了为什么 Codex core 的体积远超一个聊天循环。

07
Trade-offs
取舍
这套架构的代价

把上面几层拼起来,会看到一组互相牵制的选择。它们不是「最佳实践」,而是有代价的赌注。

无状态 ⟷ 性能

为了支持 ZDR,放弃服务端会话状态,代价是流量平方级增长,全靠提示词缓存扛住。于是「别动前缀」变成一条会渗进每个新功能设计的隐性约束——加个工具、换个模型都可能悄悄让性能塌掉。

安全 ⟷ 可用

沙箱默认收紧、网络默认关闭,换来的是大量「第一次会失败、需要你放行」的摩擦。审批与沙箱正交设计缓解了这点,但沙箱不是恶意软件检测:一个被放行的程序,仍能在它的许可范围内破坏数据。workspace-write 也远比「改当前文件」宽——构建缓存、临时文件都算。

已知的攻击面

2026 年有安全团队攻破了 Claude Code、Gemini CLI 与 Codex 的默认沙箱,从三者中都取出了凭据(含一条 CVSS 10.0 链)。结论不是「别用沙箱」,而是:沙箱是边界,不是保险。禁用网络不能替代凭据卫生——把密钥从环境变量和工作区文件里挪走,仍然是最有效的一步。

能力 ⟷ 体积

112 个 crate、内嵌 V8、三平台沙箱后端、SQLite 迁移、监护人审查……换来的是「能跑几小时、可续传、可嵌入」的能力,代价是构建复杂(Cargo + Bazel 双轨、rusty-v8、代码签名 CI)与学习曲线。一个只想跑单次任务的工具,不需要背这些。

正在发生的变化

最值得盯的一条:TUI 正在被改造成 App Server 的普通客户端(PR #10192)。一旦完成,TUI 就能连到远端机器上的 Codex server——agent 贴近算力运行,笔记本合盖也不中断,本地只负责渲染与审批。这会把「App Server 是唯一稳定接缝」从一句设计宣言,变成一个可验证的事实。

4种外壳共用一个 harness
Surface
3个平台内核原语
Sandbox
2层正交边界
Approval × Sandbox
1条接缝
App Server

主要来源

  1. openai/codex 仓库(Apache-2.0):github.com/openai/codex — 目录结构、crate 清单、许可、crate 数(112)均在此核对。
  2. codex-rs/docs/protocol_v1.md — SQ/EQ、Op/EventMsg、Session/Task/Turn、response_id 书签。
  3. OpenAI:《Unlocking the Codex harness: how we built the App Server》— App Server 起源、四部件、Item/Turn/Thread 原语、多语言客户端、TUI 改造。
  4. OpenAI:《Unrolling the Codex agent loop》— 提示词组装顺序、无状态与 ZDR、提示词缓存与 cache miss、压缩端点、模型端点表。
  5. Codex 文档:Sandbox & approvals、Rules(execpolicy / prefix_rule)。
  6. 社区技术拆解(沙箱后端 Seatbelt / bwrap / seccomp / Landlock / Restricted Token)与 Novee 的沙箱攻破研究。

对照时间:2026-10-02 · Codex 迭代很快,具体 crate 名、参数与行为以其仓库当日版本为准。
文中「交互示意」是对规则的简化演示,不构成安全承诺。