新功能先找落脚的 crate,core 是最后一档
打开仓库,第一反应是往 core 里加。仓库把这件事写成禁令:先找现有的非 core crate,否则新建一个。依赖方向会把叶子类型挡在核心之外。
codex-core。依赖方向怎么把它挡在某一层之外,挡住之后正确的落点在哪。这条禁令没有机器红灯,靠什么还在运转。
- workspace members 是一份显式数组,当前 135 个Cargo.toml L3
- 目录叫 core,crate 名叫 codex-coreAGENTS.md L4
- 禁令:resist adding code to codex-coreAGENTS.md L76
- 先问现有的非 core crate 能不能住AGENTS.md L80
- 否则新建 workspace crate,并允许重构旧代码AGENTS.md L81
- 评审对不必要地进 core 的 PR 主动挡AGENTS.md L83
- core 已经依赖拆出去的 context-fragmentscore/Cargo.toml L35
- 非机械改动一次不超过 800 行AGENTS.md L127
你要给这台 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 行
禁令写进文件的日期是 2026-03-26。当时 core 已经是最大 crate。立完之后 workspace 成员从 75 个长到 135 个,core 的生产代码却还是涨了。挡的是默认往核心扔,挡不住核心继续长。
清单是一份显式数组。codex-rs/Cargo.toml 的 members 数一遍是 135。目录名和 crate 名要分开看:目录叫 core,包名叫 codex-core,use 的时候写成 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 行
上帝包的失败模式是这次很小、先放核心。把默认落点写成明文,让评审有权挡,不依赖某种语言。换个语言重写,最小形态仍是这三问:现有包能不能住,该不该新建包,进核心凭什么拆不出去。
依赖方向反了,编译图会从两边一起胀。core 自己已经依赖 61 个 codex-* crate,包括拆出去的 context-fragments 和 features。如果新的上下文类型再写回 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 行
旁边还有两把尺子。文件目标 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 行
编译图的方向是物理约束。叶子可以独立编译,被多个中心复用。中心一旦吞下叶子类型,复用成本变成拉进整个中心。这个形状换语言也成立。
DSH:能力全是插件,没有特权内核
DSH 根 AGENTS.md 把原则写成加粗英文:everything is a plugin。Cordis 只收服务、类型化事件和可逆副作用。模型适配器、工具注册表、会话日志、agent loop 都是插件。没有需要打补丁的特权内核。新包落进现有分组时,根 package.json 不用改,glob 会发现它。
Codex 用编译期 crate 换掉了这套装卸器,新能力必须改 members 数组、写 BUILD.bazel、重新编译。能下手的位置只剩评审和 CI。评审管该不该进 core,CI 管两份锁有没有一起改。
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 成员截断工具输出,该落在哪
又来一个小功能:工具返回太长时先截断,再交给模型。它看起来像 helper,放进 core 的 tools 旁边最省事。按刚才的三问推演:现有非 core crate 有没有更合适的家,该不该新建一个只要字符串工具的小包,进 core 会挡住哪一条依赖边。
写下你会挡哪一条边,以及挡住之后正确的落点。如果选最后一档,补一句评审会问什么。