本集对应课程章节:CHAPTER(OpenAI Codex · 代码模式 —— 把上下文治理写进 code review)
内容来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》),本页为二次演绎的解读与音频稿件版本。
上一章把「往上下文里塞东西」收成了类型:编译器从此只接受实现了 ContextualUserFragment 的结构体。但类型过了,这段文字仍然可以合法地又长、又勤、又无界。
本集回答两个问题:
ContextualUserFragment 的改动,编译器仍会放行一份会打掉 cache、撑满窗口、或让旧会话恢复失败的 PR?核心判断:类型管形状,评审管成本。 挡住成本后果的,是仓库根十行禁令,外加一份会把同一节原文再读一遍的评审 skill。
| 维度 | 读完本集你能做到 | 对应证据 |
|---|---|---|
| 六条禁令 | 逐条复述 AGENTS.md「Model visible context」六条及其源码落点 | AGENTS.md L91–100 |
| 分组判断 | 把六条归为「入口 / 上界 / 时机与注意力」三组 | 课程逻辑轨迹 |
| 评审实操 | 对一份注入类改动,判断它撞第几条 | 四份 PR 教学案例 |
| 缓存前缀 | 解释为何「每轮重写 environment」会让 cache 从第一层作废 | client.rs L262–274、prompt_caching.rs |
| 历史不可改 | 说明 replace_compacted_history 如何开新窗口而非改旧行 | session/mod.rs L3373–3418 |
| 远端压缩 | 说明服务端 transcript 不可信时的本地重渲染滤网 | compact_remote.rs L354–372 |
| 自动化缺口 | 说清六条里哪几条有测试影子、哪几条纯靠人 | AGENTS.md L91–100 + 源码 |
| 横向视野 | 对比 DeepSeek Harness 的「Model-visible ⟺ logged」与 Codex 差异 | DSH AGENTS.md L107 |
出处:仓库根 AGENTS.md 第 91–100 行,标题 Model visible context。同一段原文被抄进 .codex/skills/code-review-context/SKILL.md 第 7–13 行——评审机器人读到的就是这六条,没有第二套解释。
| 条 | 原文要点 | 盯的成本 | 源码落点 |
|---|---|---|---|
| 1 | No history rewrite —— 上下文必须增量构建 | 改旧行 → 恢复失败 | session/mod.rs L3383、AGENTS.md L95 / L110 |
| 2 | 避免频繁改动上下文造成 cache miss | 每轮改前缀 → 缓存作废 | client.rs L272、AGENTS.md L96 |
| 3 | 不允许无界条目,必须有上界与硬 cap | 无界注入 | protocol.rs L3112、AGENTS.md L97 |
| 4 | 单条不得超过 10K tokens | 单条过大 | model_info.rs L167、AGENTS.md L98 |
| 5 | 新的单条若可能超过 1K tokens,标 P0 并额外人工评审 | 注意力分配 | additional_context.rs L5、AGENTS.md L99 |
| 6 | 注入片段必须在 core/context 定义为 struct 并实现 ContextualUserFragment | 入口未登记 | AGENTS.md L100 |
分组:第 6 条管入口;第 3、4 条管上界;第 1、2、5 条管时机与注意力。
任何注入类改动,按固定顺序过:登记了吗(第 6 条)→ 有硬 cap 吗(第 3 条)→ 会超 10K 吗(第 4 条)→ 每轮变吗(第 2 条)→ 是追加还是改旧行(第 1 条)→ 新种类可能超 1K 吗(第 5 条)。顺序错了会浪费一整轮评审。
| 改动 | 内容 | 撞哪条 | 换写法能否救 |
|---|---|---|---|
| 甲 | 给 environment 加 git_status 字段,每轮写入完整工作区状态 | 第 2 条(每轮改已发出的前缀) | 改成增量 + 硬 cap,红灯可灭;可能过 1K 仍标 P0 |
| 乙 | 在 turn.rs 里 format! 一句 hint 提醒跑测试 | 第 6 条(未走 trait,handler 直接拼 Message) | 换写法也过不了——必须先登记为类型 |
| 丙 | 新建 fragment 把整个源文件塞进可见文本,不写截断 | 第 3、4 条(无界 + 可能过 10K) | 增量 + 硬 cap 可灭灯 |
| 丁 | AGENTS.md 变更时就地改写历史里那一条说明 | 第 1 条 + breaking changes 第 5 项(从已有 rollout 恢复会话) | 仍是在给旧消息打补丁,换写法无效 |
关键:四份都能写出干净的 struct 和干净的测试,编译器只看有没有 struct、有没有 marker,不看这条文字每轮变不变、有多长、会不会改旧会话。
教学说明:四份 PR 与写法切换为课程化设定,用于展示六条禁令各自盯的那一类成本;逻辑轨迹右侧行号对应真实源码。
原理:消息历史从前往后拼,缓存按前缀匹配。前面任何一处变了,后面全部作废。每轮重写 environment XML、每轮换一次工具清单 → cache 从第一层作废,代价按轮翻倍。
易忽略的细节:即使内容这一轮与上一轮完全相同,只要它是被重写而非被追加,位置或格式的任何抖动都会打断前缀。所以第 2 条说的是「避免频繁改动」,不是「避免大改动」。
源码落点
core/src/client.rs L262–274prompt_cache_key —— core/src/guardian/review.rs L932–934prompt_caching.rs 要求连续两轮的 instructions 和 tools 必须一致典型踩坑:AGENTS.md 改了 → 最省事是找到历史里那条 UserInstructions 把正文换掉。当前轮少占一条消息,token 看起来还降了;但从旧 rollout 恢复时读到的是被改过的文本,会话对不上。
压缩看似也在改历史(旧窗口从 live history 消失)。若把压缩做成「打开历史文件改一行」,第 1 条与 breaking changes 第 5 项会一起被踩中。
Codex 的答法:给压缩换一个定义
replace_compacted_history 把新表整表装进 live history,旧内容以带 replacement_history 的 CompactedItem 追加到 rollout,不回改旧行replace_history 标了 #[cfg(test)] —— 这条路在生产中被关掉core/src/session/mod.rs L3373–3418、AGENTS.md L95 / L110远端压缩的额外滤网:服务端送回的 transcript 不可信,developer 消息直接丢弃,再由本地按当前 world state 把带 marker 的 fragment 重新渲染进去。历史继续增量构建,压缩继续换窗口 —— core/src/compact_remote.rs L354–372。
为什么长期成立:追加写 + 用快照换窗口,是日志系统的通用形状。事件溯源换的是投影,LSM 树换的是 SSTable,都不回改已经写下的旧行。禁止就地更新,恢复才有得对。
问一句就够了:旧内容是被追加成一条可回放的记录,还是被就地改写? 前者是换窗口,后者是改历史。生产代码里若出现就地换表的函数,检查它是否仅存在于 #[cfg(test)] 之下。
| 条 | 自动化程度 | 兜底机制 |
|---|---|---|
| 1 | 有集成测试影子 | prompt_caching.rs 之外基本靠人 |
| 2 | 有集成测试 | prompt_caching.rs:连续两轮 instructions / tools 必须一致 |
| 3 | 局部 cap | 工具输出侧按字节 10_000 截断,超出从中间砍,前置警告行告知原 token 数与总行数(protocol.rs L3112、output-truncation/src/lib.rs L12–24) |
| 4 | 局部 cap | model_info.rs L167 |
| 5 | 纯靠人 | 无 P0 枚举,也无 lint 估算新 struct 的 body() 是否超 1K(additional_context.rs L5,通用附加上下文卡 1_000 token) |
| 6 | 部分 | 拦得住「未实现 trait 就走 render_full」,拦不住在 handler 里直接拼一段 Message |
结论:skill 的存在本身就说明执行主体是评审,不是编译器。把禁令原文抄进 SKILL.md,机器人与人读同一段文字——规则只有一份,就不会漂移。
format! 一段 Message,编译能过,评审打回,改写法无效。这两条必须在写第一行代码之前就决定,不能寄望于事后修补。
六条禁令没有写总量数字,代码用两层补上:
| 层 | 机制 | 出处 |
|---|---|---|
| 模型窗口 | full_context_window_limit 硬顶 | core/src/session/context_window.rs L53–54 |
| 会话树 | RolloutBudget 按加权 token 记账,用尽对整棵 thread 停写 | core/src/rollout_budget.rs L45–65、L74–79 |
含义:40 条都合法、每条 9K,总量仍会被满窗或会话预算拦住。单条合规不等于整体合规。
| 项目 | 主张 | 覆盖范围 |
|---|---|---|
| DeepSeek Harness | 「Model-visible ⟺ logged」:送到模型请求里的东西必须能从会话日志重建;新的模型可见输入必须对应一条 session 事件(AGENTS.md L107) | 管可见与落盘对齐;不管是否无界、是否每轮改、单条是否超 10K |
| Codex | 六条禁令 + ContextualUserFragment 类型入口 | 管入口、上界、时机与注意力 |
| Claude Code | —— | 用 Model visible context / ContextualUserFragment / unbounded context 检索,未找到公开的上下文注入评审规范。REVIEW.md 是给审查模型看的产品化 PR 规则,与运行时工程红线不是同一层。本格留空待补 |
额外值得学的一点:DSH 会把被否决过的路立档——一篇讨论「是否把压缩的定义包与唯一实现折在一起」的笔记,Status 写明 rejected,并单独留下 Alternatives considered(将来可能有远端 / recall 后端,但不构成现在拆包的理由)。Codex 一侧没有这样的 rejected/ 目录,后来者只能从 prompt_caching.rs 与 Guardian 注释反推「某次每轮注入 git status 为何撤回」。
注入模型上下文的改动,合并前逐项自查:
core/context 登记为 struct 并实现 ContextualUserFragment(第 6 条)full_context_window_limit 或 RolloutBudget 拦住CompactedItem 追加而非就地换表SKILL.md 中一致(规则只有一份)有人要在 session/turn.rs 里 format! 一段 <workspace_map>,把当前目录树塞进去,声称只有调试时才开。
openai/codex,核对文件 AGENTS.md,commit 4f39251a01,核对日期 2026-08-22;代码块保留源码原文。AGENTS.md 与 Agent Notes(2026-08-22);Claude Code 一格为已检索未找到,不是「不存在」,留待新的还原源码复查。类型管形状,评审管成本。注入必须登记为类型,必须有硬 cap,只追加不改旧行。压缩换窗口并留下 CompactedItem。just 查不到的那几条,靠人和会把同一节原文再读一遍的 skill。
来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本页为二次演绎的解读与音频稿件版本,音频与本页配套发布。