OpenAI Codex · Code Mode

Write Context Governance into Code Review

The last lesson folded every injection into a type. Once the type passes, that text can still be legally long, frequent, and unbounded. What stops those costs is ten lines of bans at the repo root, plus a review skill that rereads the same section.

Course goalAfter this lesson, you can explain two things: why a change that implements ContextualUserFragment can still compile a PR that blows the cache, fills the window, or breaks restore of an old session; and, of the six bans, which ones land in code and which ones only people and a skill can hold.
Play first · send one change through six bans
Four PRs that look responsible, reviewed rule by rule
Change
The left side is four concrete changes. Below, switch to incremental-plus-hard-cap and see which lamps go out.
Style
Add-a-field and the oversized module may clear after the rewrite. Add-a-line and rewrite-old-row still fail their own rule even after you switch style.
This PRPending
Add a git_status field to environment
Write a full git status every turn so the model stops guessing whether the workspace is dirty.
The type system has not spoken yet.
If cleared, the model would seeFolded
6Must use a registered type
3Every injection needs a hard cap
4No single item over 10K
5Maybe over 1K → P0
2Don't churn the prefix each turn
1Append only, never rewrite old rows
Waiting to start. Pick a change and see which ban stops it.
Logic trail · each animation step maps to a source span
  1. Is the injection registered as ContextualUserFragmentAGENTS.md L100
  2. Does this item have a hard capAGENTS.md L97 · protocol.rs L3112
  3. Could a single item exceed 10K tokenAGENTS.md L98 · model_info.rs L167
  4. If a new kind can cross 1K, mark P0 for extra reviewAGENTS.md L99 · additional_context.rs L5
  5. Will it rewrite a prefix already sentAGENTS.md L96 · client.rs L272
  6. Is it appending a new row, or rewriting an old one in historyAGENTS.md L95 · session/mod.rs L3383
  7. Would rewriting an old row break restore of an existing rolloutAGENTS.md L110
Hit Play and watch which ban a typed change still hits.
What the type lets throughAll four PRs compile. The compiler only checks for a struct and a marker. It does not ask whether the text changes every turn, how long it is, or whether it rewrites an old session.
What the bans stopAdd-a-field hits rule 2, add-a-line hits rule 6, the oversized module hits 3 and 4, rewrite-old-row hits rule 1 and breaking-change item 5.
After switching styleAdd-a-field and the oversized module, rewritten as incremental-plus-hard-cap, turn the red lamps off; ones that may cross 1K stay P0. Add-a-line never went through a type; rewrite-old-row is still a patch of an old message. Switching style does not save them.
Teaching sketch: the four PRs and the style switch are classroom fixtures, to show the cost each of the six bans watches. Line numbers on the logic trail match openai/codex commit 4f39251a01.
Idea 1 · The type is the door; review is the guard
What problem it solves

Picture four PRs that look responsible. A adds a git_status field to environment context and writes the full workspace state every turn. B formats a hint in turn.rs to remind the model to run tests. C mints a fragment that stuffs a whole source file into model-visible text, with no truncation. D, when AGENTS.md changes, rewrites that one note in history in place.

All four can ship a clean struct and clean tests. The type system will clear them. The next inference round loses its cache prefix, the token bill doubles per turn, and restore from an old rollout reads rewritten history.

What the idea is

ContextualUserFragment is the registry slot for a marked stretch of text stuffed into model context. The last lesson used it to fold injections into types. From then on the compiler only accepts a struct that implements this trait. The shape can be right and the cost still illegal: long, frequent, unbounded.

Codex writes the cost as six bans in the repo-root AGENTS.md, under the heading Model visible context. The same text is copied into .codex/skills/code-review-context/SKILL.md. The review bot reads these six lines. There is no second explanation.

AGENTS.mdlines 91–100
### Model visible context

Codex maintains a context (history of messages) that is sent to the model in inference requests.

1. No history rewrite - the context must be built up incrementally.
2. Avoid frequent changes to context that cause cache misses.
3. No unbounded items - everything injected in the model context must have a bounded size and a hard cap.
4. No items larger than 10K tokens.
5. Highlight new individual items that can cross >1k tokens as P0. These need an additional manual review.
6. All injected fragments must be defined as structs in `core/context` and implement ContextualUserFragment trait
Source snapshot note: from the local repo openai/codex, checked file AGENTS.md, commit 4f39251a01, checked 2026-08-22. The code block keeps the original source. The same six lines also appear in .codex/skills/code-review-context/SKILL.md lines 7–13.

Rules 6, 3, and 4 ask about the door and the ceiling first. Skip the trait and format! a Message in the handler: it compiles, review sends it back. Implement the trait but skip a hard cap: same bounce. Tool output defaults to a 10_000-byte cut; overflow is sliced from the middle, with a warning line up front telling the model the original token count and total lines.Source: codex-rs/protocol/src/protocol.rs line 3112;codex-rs/models-manager/src/model_info.rs line 167;codex-rs/utils/output-truncation/src/lib.rs lines 12–24

Generic extra context is capped at 1_000 token. 1K is exactly rule 5's threshold: existing kinds are already cut to 1K in code; only a new kind that might cross 1K needs a human. There is no P0 enum in source, and no lint that estimates whether a new struct's body() will exceed 1K.Source: codex-rs/context-fragments/src/additional_context.rs line 5

Rule 2 watches timing. Append when you can. Rewrite the environment XML every turn, swap the tool list every turn, and the prefix no longer matches: cache dies from layer one. The session-level client splits cross-turn stability from in-turn stickiness; a sticky token must not replay across turns. Guardian review sessions reuse the same trunk on purpose, to keep prompt_cache_key. The integration test prompt_caching.rs watches that instructions and tools stay identical across two consecutive turns.Source: codex-rs/core/src/client.rs lines 262–274;codex-rs/core/src/guardian/review.rs lines 932–934

Proposed inject One PR Type door Needs a struct to compile Six review bans 6 must impl trait 3 / 4 hard cap & 10K 5 P0 if over 1K 2 don't churn prefix 1 append, no rewrite Humans watch timing & worst length Can merge Shape is right; cost is acceptable
Teaching diagram: the type is the door. Past the door, you still owe the six bans before anyone talks merge.
Why it lasts

A type system proves shape. It cannot prove whether this text changes every turn, whether it has a ceiling, or whether it rewrites a prefix already sent. Those are process properties. Rewrite it in another language and you still need a second door.

Daily commands cannot fill this door either. just fmt and just test catch formatting, lockfile drift, and some API breakage. They cannot read whether you inject git status every turn. Zero of the six have a dedicated lint. Rules 1 and 2 have integration-test shadows; 3 and 4 lean on local caps; 5 is purely human. Rule 6 can stop a path that calls render_full without implementing the trait; it cannot stop you from stitching a Message in the handler. The skill exists because the executor is review.

Idea 2 · Compaction swaps the window and leaves a record
What problem it solves

When AGENTS.md changes, the cheapest move is to find that UserInstructions row in history and swap the body. This turn uses one fewer message; token count even looks lower. Restore from an old rollout reads the rewritten text. The session no longer matches.

Compaction looks like rewriting history too: the old window vanishes from live history. If someone implements it as opening the history file and editing a line, rule 1 and breaking-change item 5 both get stepped on. Item 5 names restore of a session from an existing rollout.

What the idea is

replace_compacted_history loads the new table wholesale into live history. Old content is appended to the rollout as a CompactedItem carrying replacement_history. Old rows are not rewritten. The comment says “Compaction starts a new history window”. Rule 1 and compaction can coexist because compaction is defined as opening a new window. The in-place table swap on the production path is replace_history, marked #[cfg(test)].Source: codex-rs/core/src/session/mod.rs lines 3373–3418;AGENTS.md line 95、line 110

Remote compaction has one more filter. A transcript from the server is untrusted: developer messages are dropped, then the local side re-renders marked fragments from the current world state. History keeps building incrementally. Compaction keeps swapping windows.Source: codex-rs/core/src/compact_remote.rs lines 354–372

Rewrite the old row in place live item 3 Same id, body swapped Old rollout mismatches; restore fails Compaction swaps the window New window loaded into live Old rows leave the current view rollout appends CompactedItem carries replacement_history Next turn appends only the delta Restore lands in the same window
Teaching contrast: the top rewrites the old row itself; the bottom leaves a replayable window-swap record.
Types govern shape; review governs cost. Compaction swaps the window and leaves a record.
Why it lasts

Append-only writes and snapshot window swaps are the usual shape of a log. Event sourcing swaps a projection; an LSM tree swaps an SSTable. Neither rewrites a row already written. Ban in-place updates, and restore has something to match.

Who owns the total? The six bans write no number. Code fills that with two layers: full_context_window_limit is the hard top of the model window; the session tree's RolloutBudget bills weighted token and stops writes for the whole thread when spent. Forty legal items at 9K each still get stopped by a full window or the session budget.Source: codex-rs/core/src/session/context_window.rs lines 53–54、lines 74–79;codex-rs/core/src/rollout_budget.rs lines 45–65

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

DeepSeek Harness: a principle plus notes, few bans

Line 107 of the DSH repo-root AGENTS.md says “Model-visible ⟺ logged”: anything sent in a model request must be rebuildable from the session log; a new model-visible input must map to a session event. It governs visibility versus disk, not whether an item is unbounded, rewritten every turn, or over 10K.

Rejected paths get a file. A note on whether to fold compaction's definition crate and its only implementation together marks Status as rejected, and leaves Alternatives considered: a future remote or recall backend is not reason enough to split the crate now. Codex has no rejected/ directory telling a later reader why an every-turn git status inject was pulled. Later readers reverse-engineer it from prompt_caching.rs and Guardian comments.

Source checked on both sides · 2026-08-22 · DSH · Agent Notes and AGENTS.md

Claude Code: product review and runtime red lines are not the same layer

Search the reconstructed source for Model visible context, ContextualUserFragment, unbounded context: no public review spec for context injection turns up. REVIEW.md is productized PR rules for a review model — which issues to flag in review — not the same layer as the engineering red line for stuffing text into model context at runtime.

This cell stays empty. If newer reconstructed source shows up, recheck these words.

Searched · no matching spec · 2026-08-22
Classroom Exercise
01

Can this format stay

Someone wants to format a <workspace_map> in session/turn.rs, stuff in the current directory tree, and claims it only turns on while debugging. Walk the six rules: which lamps go red, and what rewrite would let it stay.

A harder follow-up: if the directory tree's worst case exceeds 1K token, should the PR title mark P0, and is there an attribute macro in source that marks it for you.

Takeaway:Types govern shape; review governs cost. An injection must register as a type, must have a hard cap, and must append without rewriting old rows. Compaction swaps the window and leaves a CompactedItem. The rules just cannot see are held by people, and by a skill that rereads the same source section.