专题篇章 · 拆开 OpenAI Codex

把 Coding Agent 读成一套边界系统

从上下文、轮次、工具、审批和沙箱开始读源码。重点是看 Coding Agent 如何靠明确边界赢得信任,而不只是靠一个友好的界面。

本页解决的问题

先给结论

「把 Coding Agent 读成一套边界系统」要解决的关键问题是什么?

从上下文、轮次、工具、审批和沙箱开始读源码。重点是看 Coding Agent 如何靠明确边界赢得信任,而不只是靠一个友好的界面。

判断标准

可靠性藏在交接处。 Agent 修改代码时,检查每次交接:它能看到什么、能调用什么、哪些动作需要审批,以及动作如何重放或撤销。

下一步

列出 Coding Agent 绝不能未经审批执行的三个动作。

常见误区

只用生成代码的质量评价助手。

课程目标读完能说清三件事。新功能为什么不能默认写进 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,编译图才不会从两边一起胀。

「先玩一遍 · 给新功能挑落脚的 crate」的能力藏在每次交接里

「你要给这台 agent 加一个小功能:每轮对话开头把当前 git 分支名写进模型上下文。打开仓库,第一反应是往 codex-core 里加。session、context、guardian、tools 都在那儿,新文件放进去最省事。依赖表不用改,调用链不用跨 crate」说明,Agent 的表现不只由模型决定。模型、上下文、工具、状态、权限和人之间的每次交接,都会改变任务能否继续以及出了问题能否恢复。

先写清状态,再增加能力

从「过了一周,同样的理由又进来三条。一个截断工具输出的 helper,一个模型供应商适配,一个会话恢复的边角。」出发,可以把流程拆成起始状态、下一步动作、工具返回、状态更新和停止条件。这样调试时找的是第一处丢失信息或权限的位置,而不是笼统地说“模型变笨了”。

  • workspace members 是一份显式数组,当前 135 个 Cargo.toml L3
  • 目录叫 core,crate 名叫 codex-core AGENTS.md L4
  • 禁令:resist adding code to codex-core AGENTS.md L76

成功路径不能代表系统可靠

用「写下你会挡哪一条边,以及挡住之后正确的落点。如果选最后一档,补一句评审会问什么」重放一次成功和一次失败,记录每一轮真正传入的上下文、工具结果和负责人;只要第二个人能复述这条链,系统才有可维护性。

从「先玩一遍 · 给新功能挑落脚的 crate」走到「思路一 · 新概念先找落点,core 是最后一档」

「先玩一遍 · 给新功能挑落脚的 crate」先把问题落在「同一条新功能:先撞核心,再看规则把它推到哪一层 播放 单步 重置 新功能 分支名进上下文 模型供应商适配 改 session 调度 点播放看规则怎么走。也可以直接点左侧某个 crate,看它会不会被挡。 编译图上的落点 0 个重编 叶子 · 不依赖 core context-fragments 片段类型 features 特性开关 model-provider 模型适配 新建 crate 新边界 核心 · 33 万行…」上;到了「思路一 · 新概念先找落点,core 是最后一档」,讨论继续推进到「你要给这台 agent 加一个小功能:每轮对话开头把当前 git 分支名写进模型上下文。打开仓库,第一反应是往 codex-core 里加。session、context、guardian、tools 都在那儿,新文件放进去最省事。依赖表不用改,调用链不用跨 crate」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。

把这条判断带到下一个场景

分析 Agent 时,沿着状态、动作、工具结果和下一步的顺序走一遍;每次交接都要能说明信息从哪里来、由谁确认、失败时停在哪里。

  • 「先玩一遍 · 给新功能挑落脚的 crate」:同一条新功能:先撞核心,再看规则把它推到哪一层 播放 单步 重置 新功能 分支名进上下文 模型供应商适配 改 session 调度 点播放看规则怎么走。也可以直接点左侧某个 crate,看它会不会被挡。 编译图上的落点 0 个重编 叶子 · 不依赖 core context-fragments 片段类型 features 特性开关 model-provider 模型适配 新建 crate 新边界 核心 · 33 万行…
  • 「思路一 · 新概念先找落点,core 是最后一档」:你要给这台 agent 加一个小功能:每轮对话开头把当前 git 分支名写进模型上下文。打开仓库,第一反应是往 codex-core 里加。session、context、guardian、tools 都在那儿,新文件放进去最省事。依赖表不用改,调用链不用跨 crate
  • 「最后的要点」:否则新建 workspace crate,并允许重构旧代码 AGENTS.md L81

最后的「最后的要点」把讨论落到「否则新建 workspace crate,并允许重构旧代码 AGENTS.md L81」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。

标记为已学完 阅读进度会自动记录
← 上一篇下一篇 →

继续阅读

同一条线上的下一篇。

文章讨论

读到这里,留下一个判断。

把刚想明白的地方、还没想通的问题,留给下一位一起学习的人。

正在讨论 把 Coding Agent 读成一套边界系统 拆开 OpenAI Codex
3条讨论文章讨论 · 与共学社区同步
在共学社区查看
AM
Asha Morgan内容编辑
观点实践记录

我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。

文章讨论7 有帮助
LH
Lin Harper独立开发者
观点观点

读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。

文章讨论5 有帮助
KM
Kiki Moore产品运营
问题问题

如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。

文章讨论4 有帮助