OpenAI Codex · 程式碼模式

把上下文治理寫進 code review

上一章把往上下文裡塞東西收成型別。型別過了,這段文字仍然可以合法地又長、又勤、又無界。擋這些後果的,是倉庫根十行禁令,外加一份會把同一節再讀一遍的評審 skill。

課程目標讀完能說清兩件事:為什麼實現了 ContextualUserFragment 的改動,編譯器仍會放行一份會打掉 cache、撐滿視窗或讓舊會話恢復失敗的 PR;以及六條禁令裡,哪幾條落在程式碼裡,哪幾條只能靠人和 skill 守。
先玩一遍 · 一次改動送進六條禁令
四份看起來很負責的 PR,逐條過評審
改動
左邊是四份具體改動。下面可以換成增量加硬 cap,看哪幾盞燈會滅。
寫法
加欄位和超長模組改完可能放行。加一句、改舊行,換寫法也過不了各自那條。
這份 PR待審
給 environment 加一個 git_status 欄位
每輪寫入完整 git status,模型就不會猜工作區髒不髒。
型別系統還沒開口。
若放行,模型會看到未展開
6必須走登記過的型別
3每條注入要有硬上限
4單條不得超過 10K
5可能過 1K 標 P0
2不要每輪改前綴
1只追加,不改舊行
等待開始。選一份改動,看哪條禁令攔住它。
邏輯軌跡 · 動畫每一步對應原始碼裡的哪一段
  1. 注入有沒有登記成 ContextualUserFragmentAGENTS.md L100
  2. 這一條有沒有硬上限AGENTS.md L97 · protocol.rs L3112
  3. 單條是否可能超過 10K tokenAGENTS.md L98 · model_info.rs L167
  4. 新種類若可能過 1K,標 P0 另審AGENTS.md L99 · additional_context.rs L5
  5. 會不會每輪改已經發出去的前綴AGENTS.md L96 · client.rs L272
  6. 是追加新行,還是改歷史裡的舊行AGENTS.md L95 · session/mod.rs L3383
  7. 改舊行會不會讓已有 rollout 恢復失敗AGENTS.md L110
點播放,看一份改動過型別之後,還會撞上哪條禁令。
型別放過的東西四份 PR 都能編過。編譯器只看有沒有 struct、有沒有 marker,不看這條文字每輪變不變、有多長、會不會改舊會話。
禁令攔住的東西加欄位撞第 2 條,加一句撞第 6 條,超長模組撞第 3、4 條,改舊行撞第 1 條和 breaking change 第五項。
換寫法之後加欄位和超長模組改成增量加硬 cap,紅燈會滅,可能過 1K 的仍標 P0。加一句沒走型別,改舊行仍是 patch 舊訊息,換寫法過不了。
教學示意:四份 PR 與寫法切換為課程化設定,用來展示六條禁令各自盯的那一類成本。邏輯軌跡右側行號對應 openai/codex 倉庫 commit 4f39251a01。
思路一 · 型別是入口,評審是門禁
它解決什麼問題

想像四份看起來很負責的 PR。甲給 environment 上下文加一個 git_status 欄位,每輪寫入完整工作區狀態。乙在 turn.rs 裡 format 一句 hint,提醒模型跑測試。丙新建一個 fragment,把整個原始檔塞進模型可見文字,不寫截斷。丁在 AGENTS.md 一改時,就地改寫歷史裡那一條說明。

四份都能寫出乾淨的 struct、乾淨的測試。型別系統會放行。下一輪推理的 cache 前綴會被打掉,token 帳單會按輪翻倍,從舊 rollout 恢復時會讀到被改過的歷史。

思路是什麼

ContextualUserFragment 是往模型上下文裡塞一段帶標記文字的登記口。上一章用它把注入收成型別。編譯器從此只接受實現了這個 trait 的 struct。形狀對了,成本還可以不合法:又長、又勤、又無界。

Codex 把成本寫成六條禁令,放在倉庫根 AGENTS.md,標題就叫 Model visible context。同一段原文被抄進 .codex/skills/code-review-context/SKILL.md。評審機器人讀到的就是這六條,沒有第二套解釋。

AGENTS.md第 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
原始碼快照說明:依據本地倉庫 openai/codex,核對檔案 AGENTS.md,commit 4f39251a01,核對日期 2026-08-22。程式碼塊保留原始碼原文。同一段六條還出現在 .codex/skills/code-review-context/SKILL.md 第 7 到 13 行。

第 6 條和第 3、4 條先問入口和上界。沒走 trait,handler 裡直接 format! 一段 Message,編譯能過,評審打回。走了 trait 但沒寫硬 cap,同樣打回。工具輸出側預設按位元組 10_000 截斷,超了從中間砍,前面加一行警告,告訴模型原 token 數和總行數。出處:codex-rs/protocol/src/protocol.rs 第 3112 行;codex-rs/models-manager/src/model_info.rs 第 167 行;codex-rs/utils/output-truncation/src/lib.rs 第 12 至 24 行

通用附加上下文另卡在 1_000 token。1K 正好是第 5 條的門檻:已有種類被程式碼截到 1K,新的可能超過 1K 的種類才需要人看。原始碼裡沒有 P0 列舉,也沒有 lint 去估一個新 struct 的 body() 會不會超過 1K。出處:codex-rs/context-fragments/src/additional_context.rs 第 5 行

第 2 條盯的是時機。能追加就追加。每輪重寫環境 XML、每輪換工具清單,前綴對不上,cache 從第一層作廢。會話級客戶端把跨 turn 穩定和 turn 內粘滯拆開,sticky token 不準跨 turn 重放。Guardian 審查會話故意複用同一條 trunk,好保住 prompt_cache_key。整合測試 prompt_caching.rs 盯的就是連續兩輪 instructions 和 tools 必須一致。出處:codex-rs/core/src/client.rs 第 262 至 274 行;codex-rs/core/src/guardian/review.rs 第 932 至 934 行

擬議注入 一份 PR 型別入口 有 struct 才編得過 六條評審禁令 6 必須走 trait 3 / 4 硬 cap 與 10K 5 過 1K 標 P0 2 不要每輪改前綴 1 只追加,不改舊行 人審盯時機和最壞長度 可以合併 形狀對,成本也可接受
教學化結構圖:型別是入口。過了入口,還要過六條禁令,才談得上合併。
為什麼長期成立

型別系統證明的是形狀。它證明不了這條文字會不會每輪變、有沒有上界、會不會改已經發出去的前綴。這些是過程屬性,換語言重寫也得另開一扇門。

日常命令也補不上這扇門。just fmtjust test 擋住格式、鎖檔案漂移和一部分 API 破壞。它們讀不懂你是不是每輪注入了 git status。六條裡零條有專用 lint。第 1、2 條有整合測試影子,第 3、4 條靠區域性 cap,第 5 條純靠人。第 6 條攔得住沒實現 trait 就走 render_full 的路,攔不住在 handler 裡直接拼一段 Message。skill 存在,說明執行主體是評審。

思路二 · 壓縮換視窗,留下記錄
它解決什麼問題

AGENTS.md 改了,最省事的做法是找到歷史裡那一條 UserInstructions,把正文換掉。當前輪少佔一條訊息,token 看起來還降了。從舊 rollout 恢復時,讀到的是被改過的文字,會話對不上。

壓縮看起來也像在改歷史:舊視窗從 live history 裡消失。若有人把壓縮做成開啟歷史檔案改一行,第 1 條和 breaking changes 第五項會一起被踩中。第五項點名的就是從已有 rollout 恢復會話。

思路是什麼

replace_compacted_history 把新表整表裝進 live history,舊內容以帶 replacement_historyCompactedItem 追加到 rollout,不回改舊行。註釋寫明 「Compaction starts a new history window」。第 1 條和壓縮能共存,因為壓縮被定義成開新視窗。生產路徑裡那條就地換表的函式叫 replace_history,上面標了 #[cfg(test)]出處:codex-rs/core/src/session/mod.rs 第 3373 至 3418 行;AGENTS.md 第 95 行、第 110 行

遠端壓縮還有一層濾網。服務端送回來的 transcript 不可信,developer 訊息直接丟掉,再由本地按當前 world state 把帶 marker 的 fragment 重新渲染進去。歷史繼續增量構建,壓縮繼續換視窗。出處:codex-rs/core/src/compact_remote.rs 第 354 至 372 行

就地改舊行 live 第 3 條 同一 id,正文被換掉 舊 rollout 對不上,恢復失敗 壓縮換視窗 新視窗裝進 live 舊行從目前畫面消失 rollout 追加 CompactedItem 帶著 replacement_history 下一輪只追加差值 恢復時回到同一扇視窗
教學化對照:上面改的是舊行本身,下面留下一條可回放的換窗記錄。
型別管形狀,評審管成本。壓縮換視窗,留下記錄。
為什麼長期成立

追加寫、用快照換視窗,是日誌系統的通用形狀。事件溯源換的是投影,LSM 樹換的是 SSTable,都不回改已經寫下的舊行。禁止就地更新,恢復才有得對。

總量誰來管,六條沒寫數字。程式碼用兩層補上:模型視窗的 full_context_window_limit 是硬頂,會話樹的 RolloutBudget 按加權 token 記帳,用盡對整棵 thread 停寫。40 條都合法且每條 9K,總量仍會被滿窗或會話預算攔住。出處:codex-rs/core/src/session/context_window.rs 第 53 至 54 行、第 74 至 79 行;codex-rs/core/src/rollout_budget.rs 第 45 至 65 行

橫向對比 · 同一道題的另一種答法

DeepSeek Harness:原則加筆記,少寫禁令

DSH 倉庫根 AGENTS.md 第 107 行寫的是 「Model-visible ⟺ logged」:送到模型請求裡的東西,必須能從會話日誌重建;新的模型可見輸入,必須對應一條 session 事件。它管的是可見與落盤對齊,不管這一條是不是無界、是不是每輪改、單條是不是超過 10K。

否決過的路會立檔。一篇討論要不要把 compaction 的定義包和唯一實現折在一起的筆記,Status 寫明 rejected,還單獨留下 Alternatives considered:將來可能有遠端或 recall 後端,不夠成為現在就拆包的理由。Codex 這一側沒有 rejected/ 目錄告訴後來者,某次每輪注入 git status 為何撤回。後來者只能從 prompt_caching.rs 和 Guardian 註釋反推。

兩側均已核對原始碼 · 2026-08-22 · DSH · Agent Notes 與 AGENTS.md

Claude Code:產品審查和執行時紅線不是一回事

用 Model visible context、ContextualUserFragment、unbounded context 檢索還原原始碼,找不到公開的上下文注入評審規範。REVIEW.md 是給審查模型看的產品化 PR 規則,用來標記該不該在審查裡指出某類問題,和執行時往模型上下文塞東西的工程紅線不是同一層。

這一格空著。後續如果有新的還原原始碼,可以按這幾個詞複查。

已檢索 · 未找到對應規範 · 2026-08-22
課堂練習
01

這句 format 能留嗎

有人要在 session/turn.rs 裡 format 一段 <workspace_map>,把當前目錄樹塞進去,聲稱只有除錯時才開。按六條逐條過:哪幾條亮紅,改成什麼樣才能留。

進階一問:若目錄樹最壞超過 1K token,PR 標題要不要標 P0,原始碼裡有沒有對應的屬性宏替你標。

Takeaway:型別管形狀,評審管成本。注入必須登記為型別,必須有硬 cap,只追加不改舊行。壓縮換視窗並留下 CompactedItem。just 查不到的那幾條,靠人和會把同一節原文再讀一遍的 skill。