一个项目里的规则文件明明存在,Codex 却没有按它做;某段错误日志在界面里还看得到,后续回答却像没见过。 遇到这些现象,先别急着判断模型“忘性大”。 你需要确认:文件有没有被发现,内容有没有进入有效上下文,以及后续请求使用的是哪一份历史。
这篇文章从公开实现回答“模型这一轮能看见什么”。 讨论范围是项目指令、工具规格、历史处理与压缩,不涉及获取未公开提示词。 产出是一套可检查的输入路径,以及几组能控制变量的实验。
源码固定在 46d252f8bf8b09f4ef765bc72e40f02c229da3e6,文档核对日期为 2026-10-20。
官方网页没有据以确认独立的最后修订日期,本文用检索日期和源码提交限定范围。
先把“上下文”拆成四份材料
“上下文太长”常把多个问题混在一起。 阅读源码时,至少应区分下面四份材料。 这是本文的分析框架,不是官方存储格式。
| 材料 | 例子 | 为什么不能直接等同模型输入 |
|---|---|---|
| 磁盘材料 | 代码、日志、AGENTS.md |
存在不等于已加载 |
| 运行时状态 | 当前环境、配置、工具计划 | 需要按实际路径装配 |
| 会话历史 | 消息、工具调用与输出 | 发送前可能经过处理 |
| 本次模型输入 | 当前请求中的实际内容 | 才是本轮推断的直接依据 |
因此,“我把文件放进仓库了”只是材料准备。 “我在界面里看见它”也只是观察到某种展示记录。 要解释模型行为,还需要追踪发现、装配和发送路径。
AGENTS.md 的读取首先是路径问题
OpenAI 官方说明,Codex 会组合全局与项目指令。
项目发现沿项目根目录到当前工作目录的路径进行,每层优先检查 AGENTS.override.md,再检查 AGENTS.md,然后检查配置的回退文件名。
每个目录最多采用一份,空文件与总字节上限也会影响收集。
来源:Custom instructions with AGENTS.md,核对日期 2026-10-20。
这里最容易出错的不是措辞,而是启动目录。 例如你从仓库根启动,却以为某个深层目录的规则会自动成为启动时已发现的项目指令。 先核对当前目录与发现路径,才能判断后续行为是否偏离预期。
在固定提交中,可以从 agents_md.rs 追踪:
load_project_instructions
→ 项目指令收集
→ agents_md_paths
→ candidate_filenames
→ LoadedInstructions 相关结构
这段顺序是阅读线索,省略了错误处理和环境分支。 来源:AGENTS.md 发现实现。
做一个不会靠“自述”误判的规则实验
准备下面的独立测试仓库。 先确保上层目录没有其他会影响实验的规则文件。 如需使用独立 Codex 配置目录,应先按对应版本文档完成配置,而不是覆盖日常配置。
context-fixture/
AGENTS.md
app/
AGENTS.override.md
fixture.txt
根规则只写一个可观察要求,例如在最终答复中给出 ROOT_MARKER。
子目录 override 再增加 APP_MARKER,避免先用互相冲突的要求增加变量。
从根目录和 app 目录各启动一轮,记录实际工作目录与结果。
| 实验 | 控制变量 | 检查项 |
|---|---|---|
| 根目录启动 | 同一任务、同一模型 | 根规则的可观察要求 |
| 子目录启动 | 只改变启动目录 | 子目录要求是否出现 |
| 同层加入普通规则 | 保留 override | 候选文件优先次序 |
| 修改后启动新会话 | 保持其他条件 | 新发现结果是否变化 |
模型说“我加载了某文件”只能作为线索。 更强的证据来自加载实现、可观察行为与相应运行记录。 即使某次没有输出标记,也不能只靠这一点断言文件没被加载,模型执行偏差仍是另一个变量。
官方将这类发现描述为每次运行开始时构建指令链;不要假定磁盘每改一次,当前所有请求都会自动重新加载。 上面的最后一项因此明确启动新会话。 来源:AGENTS.md 的发现时机。
Prompt 还包含工具与输出约束
固定提交的 build_prompt 并非只接收一段用户文本。
它构造的 Prompt 包含 input、工具规格、base instructions 和输出 schema 等内容。
其中还存在增量工具路径,不能从某次请求没有重复完整规格,推断工具已经被移除。
来源:turn.rs 的 build_prompt。
可以据此提出一个工程判断: 同一个模型、同一句用户提示,在不同工具计划或环境上下文下,可能出现不同的下一步行为。 这是输入差异带来的合理推断,不是本文完成过的一组性能实测。
如果想公平比较两个客户端,至少需要记录:
- 当前任务文本和附件。
- 项目规则的发现位置。
- 工具的可见或可发现范围。
- 工作目录与环境说明。
- 输出 schema 等额外约束。
- 历史是否经历过压缩或恢复。
仅对比模型名称,无法把这些差异排除掉。 这也是为什么“同模型换个客户端就表现不同”不能直接证明某个客户端偷偷换了模型。
历史在发送前还会被归一化
ContextManager 提供 for_prompt 和 for_prompt_annotated,后者会执行历史归一化。
该基线的 normalize_history 调用了补齐调用输出、移除孤立输出,以及按输入模态处理图像和音频的逻辑。
来源:history.rs 的模型输入准备、normalize.rs。
这些处理说明,模型输入是一份经过准备的历史,而不必等于界面记录逐项序列化的结果。 做会话回放时,应保留调用与结果的关系,再检查当前版本对不完整历史的处理。 不能把缺失信息悄悄填成成功结果。
还要区分“规格允许某模态”与“当前输入实际包含某模态”。 如果图片或音频经过过滤,模型后续解释不能只凭原始附件推断。 反过来,模型没提到附件,也不能直接证明附件被过滤。
这里需要的是实际输入证据。 代码里的条件分支用于解释可能路径,不自动证明某一次运行走过它。
工具输出截断和会话压缩不是同一件事
历史处理里可以看到工具输出的截断路径。 它针对特定内容预算;会话压缩则会形成替换后的历史窗口。 二者都可能减少后续模型能看到的原始细节,但作用位置不同。 来源:工具输出处理、compact.rs。
| 机制 | 研究时检查什么 | 不应直接推导什么 |
|---|---|---|
| 单项输出截断 | 哪项输出、预算与处理结果 | 整个历史都被总结了 |
| 历史归一化 | 调用配对、模态与有效项 | 所有原始消息原样发送 |
| 压缩 | 触发、替换历史与恢复上下文 | 所有细节都被完整保留 |
调大上下文窗口也不是自动修复这些问题的方法。 如果关键信息根本没有进入输入,窗口再大也不会补上。 如果内容已被压缩成摘要,则需要检查摘要保留了什么,而不是只看窗口上限。
压缩怎样影响后续可见信息
compact.rs 的一条实现路径会构建摘要相关历史、重新装配初始上下文,再调用 replace_compacted_history。
该文件同时记录压缩元数据;仓库还有其他压缩实现,不能把这一条路径写成全部情况。
来源:压缩历史替换路径。
从这段实现能得出的事实是:后续模型窗口可以发生替换。 “替换后一定丢失某项需求”则需要实验,源码本身不能替你提供结论。
测试压缩时,建议把关键事实放在独立文件中,同时记录版本、触发方式和事件。 分别检查任务是否保留“目标”“已完成项”“待验证项”和“约束”。 只问模型“你还记得吗”很难得到有用结果,问题本身可能已经泄露了答案。
可用下面的任务状态作为实验材料:
目标:修复 fixture 中一个明确错误。
已完成:只列出有文件或日志证据的动作。
待验证:尚未执行的检查。
约束:只允许修改指定测试文件。
事实来源:实际路径和必要内容。
这份材料是实验设计,不是声称 Codex 内部使用同样的状态格式。 本文没有运行强制压缩或长上下文耗尽测试,也没有报告保留率数字。
“模型没看到”应该怎样下结论
| 证据 | 可支持的判断 |
|---|---|
| 文件存在 | 材料在磁盘上 |
| 发现路径命中 | 文件满足加载路径条件 |
| 加载记录或输入证据 | 内容进入了相应装配阶段 |
| 最终请求内容 | 内容在该次请求中可见 |
| 回答正确或错误 | 一次行为结果,原因还需排查 |
尽量用更靠近发送边界的证据判断输入,用行为结果判断任务表现。 这样可以减少“没有遵守,所以没有看见”的循环论证。
Codex 的上下文分析最终要回答一个具体问题:这一轮决策依赖的事实,从哪里来,经过哪些处理,还剩下什么。 把这条路径查清楚,比笼统地增加提示词长度更容易找到问题。