OpenAI Codex · 代码模式

新功能先找落脚的 crate,core 是最后一档

打开仓库,第一反应是往 core 里加。仓库把这件事写成禁令:先找现有的非 core crate,否则新建一个。依赖方向会把叶子类型挡在核心之外。

课程目标读完能说清三件事。新功能为什么不能默认写进 codex-core。依赖方向怎么把它挡在某一层之外,挡住之后正确的落点在哪。这条禁令没有机器红灯,靠什么还在运转。
先玩一遍 · 给新功能挑落脚的 crate
同一条新功能:先撞核心,再看规则把它推到哪一层
新功能
点播放看规则怎么走。也可以直接点左侧某个 crate,看它会不会被挡。
编译图上的落点0个重编
叶子 · 不依赖 core
核心 · 33 万行,86 个 mod
直接依赖 core 的 25 个
间接 · 经 client 带上
这一步的判定待命
手里的功能分支名进上下文
试探落点还没放
挡住它的规则尚未触发
正确落点先走一遍规则
等待开始。
逻辑轨迹 · 动画每一步对应源码里的哪一段
  1. workspace members 是一份显式数组,当前 135 个Cargo.toml L3
  2. 目录叫 core,crate 名叫 codex-coreAGENTS.md L4
  3. 禁令:resist adding code to codex-coreAGENTS.md L76
  4. 先问现有的非 core crate 能不能住AGENTS.md L80
  5. 否则新建 workspace crate,并允许重构旧代码AGENTS.md L81
  6. 评审对不必要地进 core 的 PR 主动挡AGENTS.md L83
  7. core 已经依赖拆出去的 context-fragmentscore/Cargo.toml L35
  8. 非机械改动一次不超过 800 行AGENTS.md L127
选一个新功能,点播放。看它先被挡在哪一层,正确落点在哪。
挡住的是默认落点往 core 一放,25 个直接下游加 core 自己要重编,tui 还会顺着 client 被带上。叶子类型焊进核心,复用它就得把沙箱和 Guardian 一起拉进来。
正确落点在叶子现有的非 core crate 能住就住。住不下再新建 crate,并重构旧代码。core 已经依赖这些叶子,它可以当调用方。
最后一档仍要解释必须摸 session 内部状态时,可以进 core。评审仍要问为什么拆不出去。禁令挡习惯,挡不住有理由的例外。
教学示意:重编个数按 core 自己加 25 个直接下游估算,用来展示依赖边会蔓延到哪。tui 标成间接。逻辑轨迹右侧行号对应 openai/codex 仓库 commit 4f39251a01。
思路一 · 新概念先找落点,core 是最后一档
它解决什么问题

你要给这台 agent 加一个小功能:每轮对话开头把当前 git 分支名写进模型上下文。打开仓库,第一反应是往 codex-core 里加。session、context、guardian、tools 都在那儿,新文件放进去最省事。依赖表不用改,调用链不用跨 crate。

过了一周,同样的理由又进来三条。一个截断工具输出的 helper,一个模型供应商适配,一个会话恢复的边角。core/src/lib.rs 顶部又多三个 mod。下游只要写 use codex_core,编译图跟着变宽。有人想单独复用上下文片段那个类型,发现它焊在 core 里。要复用就得把整个 core 拉进来,包括沙箱、MCP、Guardian。

思路是什么

仓库把这件事写成禁令。AGENTS.md 用加粗英文写 resist adding code to codex-core。新概念先问现有的非 core crate 能不能住。再问该不该新建一个 workspace crate,并且允许为此重构旧代码。core 是最后一档。评审遇到往 core 里堆功能的 PR,被要求主动挡回去。

出处:AGENTS.md 第 72 至 83 行

新概念 要找一个 crate 现有非 core crate 能不能住 加进那个 crate 能住就住 新建 crate 边界清楚就建,并重构 不能 最后一档 core 评审仍要问
教学化结构图:输入是一个新概念,输出是落点。core 只在两条路都走不通时出现。

禁令写进文件的日期是 2026-03-26。当时 core 已经是最大 crate。立完之后 workspace 成员从 75 个长到 135 个,core 的生产代码却还是涨了。挡的是默认往核心扔,挡不住核心继续长。

清单是一份显式数组。codex-rs/Cargo.tomlmembers 数一遍是 135。目录名和 crate 名要分开看:目录叫 core,包名叫 codex-coreuse 的时候写成 codex_core

出处:codex-rs/Cargo.toml 第 1 至 20 行;AGENTS.md 第 1 至 5 行;codex-rs/core/Cargo.toml 第 1 至 9 行

.rs 行数,少数几个吃掉大半体积。core 33 万行,tui 27 万,app-server 14.8 万。大于等于 1 万行的 24 个,小于 2 千的 77 个。core 去掉测试后剩约 10.3 万行。25 个 workspace 成员直接依赖它。tui 的 Cargo.toml 没有这一行,它依赖 codex-app-server-client,再由 client 拉 core。往 core 加一行,直接下游 25 个要重编,tui 仍会被带上。

出处:codex-rs/cli/Cargo.toml 第 41 至 42 行;codex-rs/tui/Cargo.toml 第 30 至 31 行

为什么长期成立

上帝包的失败模式是这次很小、先放核心。把默认落点写成明文,让评审有权挡,不依赖某种语言。换个语言重写,最小形态仍是这三问:现有包能不能住,该不该新建包,进核心凭什么拆不出去。

思路二 · 叶子类型待在叶子 crate
它解决什么问题

依赖方向反了,编译图会从两边一起胀。core 自己已经依赖 61 个 codex-* crate,包括拆出去的 context-fragmentsfeatures。如果新的上下文类型再写回 core,想复用它的人必须把沙箱和 Guardian 一起拉进来。叶子依赖核心,核心再依赖叶子,边界就没了。

思路是什么

拆出去的 crate 只带自己需要的那一点。context-fragments 的包清单几乎没有业务依赖,只碰 protocol 和一段字符串工具,对外 re-export 两个片段类型和一个 trait。core 可以依赖它,它不依赖 core。git 分支名这种片段走这条路:类型落在 fragments,core 当调用方。模型供应商适配已经抽到 codex-model-provider。必须摸 session 内部状态的东西,才走到最后一档,评审仍要问为什么拆不出去。

出处:codex-rs/context-fragments/src/lib.rs 第 1 至 6 行;codex-rs/core/Cargo.toml 第 26 至 42 行

叶子 crate fragments / features core 依赖叶子 codex-core 可以调用,不许吞回去 25 个直接下游 cli / app-server / ext 类型焊进 core,复用成本变成拉进整个中心 类型留在叶子,谁需要谁依赖这一小包
教学化结构图:箭头只允许从中心指向叶子,叶子不能回头依赖 core。

旁边还有两把尺子。文件目标 500 行,大约超过 800 行就开新模块。非机械改动一次不超过 800 行,复杂逻辑压到 500。三层一起看,针对的是同一件事:人和 AI 都倾向于把改动写大,写进已经很大的文件。

出处:AGENTS.md 第 49 至 61 行;AGENTS.md 第 125 至 131 行

这条禁令本身没有 lint,没有 CI job。仓库里检索 resist adding code to codex-core,只命中 AGENTS.md 这一处。挡得住的是旁边那几条:依赖没刷 Bazel lock,CI 红;include_str! 没改 BUILD.bazel,Bazel 红。core 禁令挡得住习惯,挡不住有理由的例外,也挡不住漏看。

出处:AGENTS.md 第 37 至 43 行

core 是最后一档,不是默认档。
为什么长期成立

编译图的方向是物理约束。叶子可以独立编译,被多个中心复用。中心一旦吞下叶子类型,复用成本变成拉进整个中心。这个形状换语言也成立。

横向对比 · 同一道题的另一种答法

DSH:能力全是插件,没有特权内核

DSH 根 AGENTS.md 把原则写成加粗英文:everything is a plugin。Cordis 只收服务、类型化事件和可逆副作用。模型适配器、工具注册表、会话日志、agent loop 都是插件。没有需要打补丁的特权内核。新包落进现有分组时,根 package.json 不用改,glob 会发现它。

Codex 用编译期 crate 换掉了这套装卸器,新能力必须改 members 数组、写 BUILD.bazel、重新编译。能下手的位置只剩评审和 CI。评审管该不该进 core,CI 管两份锁有没有一起改。

两侧均已核对源码 · 2026-08-22 · DSH · 插件包

Grok Build:清单自动生成,靠目录分层

Grok 根 Cargo.toml 第一行写明这份 workspace 是生成的,人应该改各 crate 自己的清单。members 按同一口径数是 79。组织方式写在根 README:pager 是 TUI,shell 是运行时,tools 和 workspace 是领域能力,common / build 是叶子。

它没有写成给评审看的 core 禁令。切分本身被当成地图,膨胀靠组合入口和抽 crate 消化。Codex 多付的是评审文本和双构建锁。

两侧均已核对源码 · 2026-08-22 · Grok · 79 个 Workspace 成员
课堂练习
01

截断工具输出,该落在哪

又来一个小功能:工具返回太长时先截断,再交给模型。它看起来像 helper,放进 core 的 tools 旁边最省事。按刚才的三问推演:现有非 core crate 有没有更合适的家,该不该新建一个只要字符串工具的小包,进 core 会挡住哪一条依赖边。

写下你会挡哪一条边,以及挡住之后正确的落点。如果选最后一档,补一句评审会问什么。

Takeaway:新概念先找现有的非 core crate,否则新建 crate 并重构。core 是最后一档,评审对进核心的 PR 必须问为什么拆不出去。叶子类型待在叶子 crate,编译图才不会从两边一起胀。