OpenAI Codex · Code Mode

New features find a crate first. core is last.

Open the repo and your first move is dumping it into core. The repo writes that down as a ban: look for an existing non-core crate, or make a new one. Dependency direction keeps leaf types out of the kernel.

Course goalAfter this lesson you can explain three things. Why a new feature must not default into codex-core. How dependency direction keeps it out of a layer, and where it should land after that. This ban has no machine red light — what still keeps it running.
Play first · Pick a crate for the new feature
Same new feature: hit the kernel first, then watch the rule push it to a layer
New feature
Hit Play to watch the rule walk. Or tap a crate on the left and see if it gets blocked.
Landing on the compile graph0 rebuilds
Leaves · no core dep
Kernel · 330k lines, 86 mods
25 that depend on core directly
Indirect · pulled in via client
This step’s verdictStandby
Feature in handBranch name into context
Probe landingNot placed yet
Rule that blocks itNot fired yet
Right landingWalk the rule first
Waiting to start.
Logic trail · each animation step maps to a source span
  1. workspace members is an explicit array, 135 todayCargo.toml L3
  2. The directory is core; the crate name is codex-coreAGENTS.md L4
  3. The ban: resist adding code to codex-coreAGENTS.md L76
  4. Ask first whether an existing non-core crate can host itAGENTS.md L80
  5. Otherwise make a workspace crate, and allow refactoring old codeAGENTS.md L81
  6. Review actively blocks PRs that pile into core without needAGENTS.md L83
  7. core already depends on the extracted context-fragmentscore/Cargo.toml L35
  8. A non-mechanical change stays under 800 linesAGENTS.md L127
Pick a new feature, hit Play. Watch which layer blocks it first, and where it should land.
What gets blocked is the default landingDrop it in core and 25 direct dependents plus core itself rebuild. tui still gets pulled in through client. Weld a leaf type into the kernel, and reuse means dragging in the sandbox and Guardian too.
The right landing is a leafIf an existing non-core crate can host it, live there. If not, make a crate and refactor old code. core already depends on those leaves — it can be the caller.
Last resort still needs an explanationWhen you must touch session internals, core is allowed. Review still asks why it can’t be extracted. The ban stops the habit, not a reasoned exception.
Teaching sketch: rebuild count estimates core itself plus 25 direct dependents, to show how far a dependency edge spreads. tui is marked indirect. Line numbers on the trail match openai/codex commit 4f39251a01.
Idea 1 · A new concept finds a landing first. core is last.
What problem it solves

You want a small feature on this agent: at the start of each turn, write the current git branch into the model context. Open the repo and your first move is adding it to codex-core. session, context, guardian, tools all live there. Dropping a new file in is the cheap path. No dependency table to edit. No call chain across crates.

A week later, the same reason brings three more: a helper that truncates tool output, a model-provider adapter, a session-restore corner. core/src/lib.rs grows three more mods at the top. Downstream writes use codex_core and the compile graph widens. Someone wants to reuse just the context-fragment type and finds it welded into core. Reuse means pulling the whole core — sandbox, MCP, Guardian.

What the idea is

The repo writes this as a ban. AGENTS.md says in bold English resist adding code to codex-core. A new concept asks first whether an existing non-core crate can host it. Then whether to make a workspace crate — and you may refactor old code to do it. core is last. Review is told to actively push back PRs that pile features into core.

Source: AGENTS.md lines 72–83

New concept needs a crate Existing non-core crate Can it live here Add to that crate Live there if it fits new crate Split if the boundary is clear Yes No Last resort: core Review still asks
Teaching diagram: Input is a new concept. Output is a landing. core appears only when both roads fail.

The ban landed in the file on 2026-03-26. core was already the largest crate. After that, workspace members grew from 75 to 135 — and core’s production code still grew. It stops the default toss into the kernel. It does not stop the kernel from growing.

The roster is an explicit array. Count members in codex-rs/Cargo.toml and you get 135. Directory name and crate name split: the directory is core, the package is codex-core, and use writes codex_core.

Source: codex-rs/Cargo.toml lines 1–20; AGENTS.md lines 1–5; codex-rs/core/Cargo.toml lines 1–9

By .rs line count, a few crates eat most of the volume. core 330k, tui 270k, app-server 148k. 24 are ≥10k lines; 77 are under 2k. Drop tests and core is about 103k. 25 workspace members depend on it directly. tui’s Cargo.toml has no such line — it depends on codex-app-server-client, and the client pulls core. Add one line to core and 25 direct dependents rebuild. tui still gets pulled along.

Source: codex-rs/cli/Cargo.toml lines 41–42; codex-rs/tui/Cargo.toml lines 30–31

Why it lasts

The god-crate failure mode is “this one’s small, drop it in the kernel.” Write the default landing in plain text so review has the right to block — no language required. Rewrite in another language and the minimum shape is still these three questions: can an existing package host it, should you make a new one, and if it enters the kernel, why can’t it be extracted.

Idea 2 · Leaf types stay in leaf crates
What problem it solves

Reverse the dependency direction and the compile graph swells from both sides. core already depends on 61 codex-* crates, including extracted context-fragments and features. Write a new context type back into core, and anyone who wants to reuse it must pull the sandbox and Guardian too. Leaves depend on the kernel, the kernel depends on leaves — the boundary is gone.

What the idea is

An extracted crate carries only what it needs. The context-fragments package list has almost no business deps — protocol plus a string helper — and re-exports two fragment types and one trait. core may depend on it. It does not depend on core. A git branch-name fragment takes this road: the type lands in fragments, core is the caller. Model-provider adapters already live in codex-model-provider. Only things that must touch session internals go to last resort, and review still asks why they can’t be extracted.

Source: codex-rs/context-fragments/src/lib.rs lines 1–6; codex-rs/core/Cargo.toml lines 26–42

Leaf crate fragments / features core depends on leaves codex-core May call. Must not swallow back. 25 direct dependents cli / app-server / ext Weld the type into core and reuse means pulling the whole center Leave the type in a leaf. Whoever needs it depends on that small crate.
Teaching diagram: Arrows may only point from the center to the leaves. A leaf must not depend back on core.

Two more rulers sit beside it. File target 500 lines; past about 800, open a new module. A non-mechanical change stays under 800, complex logic down to 500. Read the three together and they aim at the same thing: humans and AIs both like to write a change large, into a file that is already large.

Source: AGENTS.md lines 49–61; AGENTS.md lines 125–131

The ban itself has no lint, no CI job. Search the repo for resist adding code to codex-core and you hit only AGENTS.md. What does fire is the neighbors: skip a Bazel lock refresh and CI goes red; include_str! without a BUILD.bazel change and Bazel goes red. The core ban stops the habit. It does not stop a reasoned exception, or a miss.

Source: AGENTS.md lines 37–43

core is last. Not the default.
Why it lasts

Compile-graph direction is a physical constraint. A leaf can compile alone and be reused by many centers. Once a center swallows a leaf type, reuse means pulling the whole center. That shape holds in any language.

Side-by-side · another answer to the same question

DSH: every capability is a plugin. No privileged kernel.

DSH’s root AGENTS.md writes the principle in bold English: everything is a plugin. Cordis only takes services, typed events, and reversible side effects. Model adapters, the tool registry, session logs, the agent loop — all plugins. No privileged kernel to patch. When a new package lands in an existing group, the root package.json stays put. A glob finds it.

Codex swapped that loader for compile-time crates. A new capability must edit the members array, write BUILD.bazel, and recompile. The only hands left are review and CI. Review asks whether it should enter core. CI asks whether both locks moved together.

Source-checked on both sides · 2026-08-22 · DSH · plugin packages

Grok Build: the roster is generated. Layering is directories.

Grok’s root Cargo.toml says on line 1 that this workspace is generated. People should edit each crate’s own manifest. Count members the same way and you get 79. The root README is the map: pager is the TUI, shell is the runtime, tools and workspace are domain capability, common / build are leaves.

It never wrote a core ban for review to read. The split itself is the map. Growth is digested by composing entries and extracting crates. What Codex pays extra is review prose and a dual build lock.

Source-checked on both sides · 2026-08-22 · Grok · 79 workspace members
Classroom Exercise
01

Truncating tool output — where should it land?

Another small feature: when a tool return is too long, truncate it before the model sees it. It looks like a helper. Parking it next to tools in core is the cheap path. Walk the three questions: does an existing non-core crate have a better home, should you make a tiny string-utility crate, and which dependency edge does core block.

Write down which edge you would block, and the right landing after that. If you pick last resort, add the question review will ask.

Takeaway: A new concept looks for an existing non-core crate first, or makes a crate and refactors. core is last. Review must ask why a kernel PR can’t be extracted. Leaf types stay in leaf crates, or the compile graph swells from both sides.