一个项目里的规则文件明明存在,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。

可以据此提出一个工程判断: 同一个模型、同一句用户提示,在不同工具计划或环境上下文下,可能出现不同的下一步行为。 这是输入差异带来的合理推断,不是本文完成过的一组性能实测。

如果想公平比较两个客户端,至少需要记录:

  1. 当前任务文本和附件。
  2. 项目规则的发现位置。
  3. 工具的可见或可发现范围。
  4. 工作目录与环境说明。
  5. 输出 schema 等额外约束。
  6. 历史是否经历过压缩或恢复。

仅对比模型名称,无法把这些差异排除掉。 这也是为什么“同模型换个客户端就表现不同”不能直接证明某个客户端偷偷换了模型。

历史在发送前还会被归一化

ContextManager 提供 for_prompt 和 for_prompt_annotated,后者会执行历史归一化。 该基线的 normalize_history 调用了补齐调用输出、移除孤立输出,以及按输入模态处理图像和音频的逻辑。 来源:history.rs 的模型输入准备、normalize.rs。

这些处理说明,模型输入是一份经过准备的历史,而不必等于界面记录逐项序列化的结果。 做会话回放时,应保留调用与结果的关系,再检查当前版本对不完整历史的处理。 不能把缺失信息悄悄填成成功结果。

还要区分“规格允许某模态”与“当前输入实际包含某模态”。 如果图片或音频经过过滤,模型后续解释不能只凭原始附件推断。 反过来,模型没提到附件,也不能直接证明附件被过滤。

这里需要的是实际输入证据。 代码里的条件分支用于解释可能路径,不自动证明某一次运行走过它。

工具输出截断和会话压缩不是同一件事

历史处理里可以看到工具输出的截断路径。 它针对特定内容预算;会话压缩则会形成替换后的历史窗口。 二者都可能减少后续模型能看到的原始细节,但作用位置不同。 来源:工具输出处理、compact.rs。

机制 研究时检查什么 不应直接推导什么
单项输出截断 哪项输出、预算与处理结果 整个历史都被总结了
历史归一化 调用配对、模态与有效项 所有原始消息原样发送
压缩 触发、替换历史与恢复上下文 所有细节都被完整保留

调大上下文窗口也不是自动修复这些问题的方法。 如果关键信息根本没有进入输入,窗口再大也不会补上。 如果内容已被压缩成摘要,则需要检查摘要保留了什么,而不是只看窗口上限。

压缩怎样影响后续可见信息

compact.rs 的一条实现路径会构建摘要相关历史、重新装配初始上下文,再调用 replace_compacted_history。 该文件同时记录压缩元数据;仓库还有其他压缩实现,不能把这一条路径写成全部情况。 来源:压缩历史替换路径。

从这段实现能得出的事实是:后续模型窗口可以发生替换。 “替换后一定丢失某项需求”则需要实验,源码本身不能替你提供结论。

测试压缩时,建议把关键事实放在独立文件中,同时记录版本、触发方式和事件。 分别检查任务是否保留“目标”“已完成项”“待验证项”和“约束”。 只问模型“你还记得吗”很难得到有用结果,问题本身可能已经泄露了答案。

可用下面的任务状态作为实验材料:

目标:修复 fixture 中一个明确错误。
已完成:只列出有文件或日志证据的动作。
待验证:尚未执行的检查。
约束:只允许修改指定测试文件。
事实来源:实际路径和必要内容。

这份材料是实验设计,不是声称 Codex 内部使用同样的状态格式。 本文没有运行强制压缩或长上下文耗尽测试,也没有报告保留率数字。

“模型没看到”应该怎样下结论

证据 可支持的判断
文件存在 材料在磁盘上
发现路径命中 文件满足加载路径条件
加载记录或输入证据 内容进入了相应装配阶段
最终请求内容 内容在该次请求中可见
回答正确或错误 一次行为结果,原因还需排查

尽量用更靠近发送边界的证据判断输入,用行为结果判断任务表现。 这样可以减少“没有遵守,所以没有看见”的循环论证。

Codex 的上下文分析最终要回答一个具体问题:这一轮决策依赖的事实,从哪里来,经过哪些处理,还剩下什么。 把这条路径查清楚,比笼统地增加提示词长度更容易找到问题。