把 Coding Agent 读成一套边界系统
从上下文、轮次、工具、审批和沙箱开始读源码。重点是看 Coding Agent 如何靠明确边界赢得信任,而不只是靠一个友好的界面。
本页解决的问题
先给结论「把 Coding Agent 读成一套边界系统」要解决的关键问题是什么?
从上下文、轮次、工具、审批和沙箱开始读源码。重点是看 Coding Agent 如何靠明确边界赢得信任,而不只是靠一个友好的界面。
可靠性藏在交接处。 Agent 修改代码时,检查每次交接:它能看到什么、能调用什么、哪些动作需要审批,以及动作如何重放或撤销。
列出 Coding Agent 绝不能未经审批执行的三个动作。
只用生成代码的质量评价助手。
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 会挡住哪一条依赖边。
写下你会挡哪一条边,以及挡住之后正确的落点。如果选最后一档,补一句评审会问什么。
「先玩一遍 · 给新功能挑落脚的 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」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。
我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。
读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。
如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。
还没有这篇文章的讨论。