Codex 频繁压缩上下文又爱发散:原因与解决思路
用 Codex 跑实验代码时,常出现两类连锁问题:上下文压缩过于频繁,以及模型自行脑补新方向后被写进压缩摘要,变成后续必须完成的主线任务。文章拆解背后的机制,给出外置计划、规则约束、压缩时机管理、版本控制兜底等可操作方案,并说明它与 Claude Code 的分工取舍。
用 AI 代理跑实验代码的人,多半遇到过这种场面:离开工位十几分钟,回来发现工程目录里多了一堆自己没要求过的文件,test 目录里的用例数量还在往上涨,而对话记录里对方非常笃定地说「按照你的要求,我已经完成了某某模块」——你翻遍聊天记录,也没找到自己什么时候提过这个需求。
这类抱怨集中在两个点上:一是上下文压缩(compaction)触发得太频繁;二是模型容易自行发散,凭空想出一个新方向。真正麻烦的是两者叠加:发散出来的想法还没被你否掉,压缩就发生了,摘要把这段推测保留成了「用户的目标」,于是脑补内容被当成主线任务在下一轮继续推进。比起单纯的幻觉,这种经过压缩误判的偏离更难纠正,因为它带着「已确认」的身份进入了后续对话。
把症状拆开:三个问题其实是一条链
单独看每个现象都不算致命,串起来才形成破坏力:
- 发散:模型在回答主问题时顺带提出一个「顺便还可以做 X」的设想。
- 未清理:这个设想没有被明确标注为「待确认」,也没有被删除,就留在上下文里。
- 压缩:上下文达到阈值,历史被摘要成一段更短的描述,摘要倾向于保留可执行的、像是目标的句子。
- 固化:下一轮开始时,这段摘要成了新的事实基础,X 变成「用户要求的任务」。
一旦走到第 4 步,你再纠正就要付出好几轮对话的成本,而每多一轮,又更接近下一次压缩。
为什么这类模型更容易触发压缩
几个方向的原因值得了解,但不必当成定论:
- 可用上下文预算不同。不同产品、不同模型档位默认的上下文窗口与压缩触发阈值不一样,窗口小的自然更早压缩。
- 推理过程本身消耗 token。长链推理、频繁的工具调用(读文件、跑测试、贴日志)都会持续推高占用,一次任务里压缩好几轮并不奇怪。
- 训练倾向偏「快」。不少用户反馈,这类模型更习惯直接给出可执行方案,而不是先用一两句话确认意图,于是推测性内容出现得更早、更多。
- 压缩是有损摘要。它没有能力区分「用户明确要求」和「模型自己提议」,只能按语义重要性保留,而提议往往写得比需求更像一条任务。
看清这条链路后,解决思路就明确了:在压缩发生之前,让上下文中不存在模棱两可的「目标」。
可操作的做法
1. 把计划外置成文件
不要指望对话历史替你记住计划。在仓库里放一份纯文本的任务清单,每轮开始前让代理读取,结束时要求它更新。
# PLAN.md
## 当前目标(唯一)
实现 A 模块的输入校验
## 明确不做
- 不新增测试文件,除非我在对话中口头要求
- 不重构 B 模块
- 不引入新依赖
## 待我确认的提议
(代理只能往这里追加,不得直接执行)
- [ ] 建议增加 C 接口的缓存层
关键在最后一段:给发散内容一个合法的落脚点。模型想提建议时把它写进「待确认」,而不是直接动手。这比单纯禁止发散更有效,因为它承认了模型提建议的价值,同时切断了「提议直接变任务」的路径。
2. 用规则文件固定边界
多数代理工具支持项目级规则文件(例如 AGENTS.md 或同类配置),把行为约束写死在仓库里,比每轮口头叮嘱可靠:
## 工作方式
- 回答与本仓库代码相关的问题前,先检索并引用具体文件,不要凭记忆作答
- 任何超出当前目标范围的改动,先提出并等待确认
- 每完成一个步骤,用三行以内汇报:做了什么 / 下一步 / 有无偏离
- 不创建新的测试文件或目录,除非用户明确要求
- 不要在回复末尾提出「顺便还可以…」式的扩展建议
最后一条看起来粗暴,但对实验类工作很实用:发散建议在这种场景里几乎全是负收益。
3. 主动管理压缩时机,别等它自动发生
自动压缩往往发生在你最不希望它发生的时刻。更好的做法是自己掌握节奏:
- 一个子任务完成后,主动开新会话,把 PLAN.md 和关键文件路径贴进去当起点,而不是让旧上下文无限累积。
- 察觉对话已经开始绕圈(同一个问题反复解释)时立刻切分任务,这通常意味着上下文已经接近饱和。
- 让代理在长任务中定期输出一份「当前状态摘要」,你确认无误后再继续,这样即使压缩发生,摘要也有正确的基线可参照。
4. 收敛任务粒度,压缩工具输出
上下文膨胀很大一部分来自工具返回值:整份日志、整份测试输出、整棵目录树。可以在规则里要求只贴关键片段和行号范围。任务粒度同理——「把整个实验流程跑通」这种目标几乎必然触发多次压缩,拆成「先改数据加载,再改评估脚本」就稳得多。
5. 实验设计类任务要换一种问法
如果目标是让它给出对比实验、消融实验的建议,直接问「给我实验方案」容易得到形式化的条目:维度齐全,但每条都说不清「这组实验想回答什么问题」。可以强制要求每个设计项附带一句论证:
每一项实验请按三行输出:要验证的假设 / 对照组与变量 / 如果结果相反说明什么。说不出第二行的条目请删掉。
这种约束把模型从「列清单」推向「做判断」,产出质量差别通常很明显。
6. 用版本控制兜底
最实际的保险是让偏离可控:每轮开始前确保工作区干净,每轮结束提交一次,提交信息写清意图。这样即使代理自行改了一堆文件,一条 git status 就能看清影响面,回滚成本接近零。把「跑测试前先看 diff」当成习惯,比事后争论「我没让你改这个」有用得多。
关于上下文参数的取舍
调整上下文窗口上限、开启实验性的上下文管理机制,确实是缓解压缩频率的直接手段,社区里也有相关讨论和官方文档。但需要注意两点:窗口开得越大,单次请求越慢、消耗越多,而且窗口大不等于不会跑偏,它只是把跑偏的代价推迟了。建议顺序是:先把任务边界和计划文件做好,再考虑调参数。具体可调范围和默认值随产品版本变化较快,请以官方最新文档为准,不要照搬别人的数值。
另一个容易被忽略的因素是运行环境本身。长任务跑到一半,本地机器休眠、网络切换导致连接断开,上下文丢了只能重来,前面的压缩管理全白做。如果经常跑几小时以上的实验流程,把任务放到一台长期在线的远程机器上执行(例如新加坡机房的独享 Mac,按月租用、远程连接),能减少这类中断,但这是环境层面的补丁,替代不了提示词和流程上的约束。
两种代理的分工,不必二选一
从实际使用反馈看,两者差异比较稳定:
| 场景 | 更适合的一方 | 原因 |
|---|---|---|
| 探索性判断、第一版思路、方案对比 | Claude 系 | 首句正确率较高,倾向先确认再动手 |
| 已有明确规格的实现、批量改造 | Codex 系 | 执行快,token 效率高,愿意一次推到底 |
| 长时间自主推进 | 需要额外约束 | 发散与压缩叠加的风险最高 |
| 需要反复质疑你的方案 | Claude 系 | 会反驳,对做研究反而有利 |
所以更务实的做法是按阶段切换:用一方把实验计划和假设敲定,用另一方执行实现,不要把「哪个模型更强」当成一道单选题。
几点提醒
- 压缩后的摘要必须复核。把它当成一份别人写的会议纪要,看到不属于自己需求的任务项,当场删掉并要求代理重述目标。
- 「禁止发散」的规则不要写得太绝,否则模型连必要的异常提示都不敢说。给它一个「待确认区」比全面封口好。
- 每次纠正之后,最好让代理把修订后的目标复述一遍并写回 PLAN.md,否则下一次压缩可能又把旧版本捡回来。
小结
频繁压缩和爱发散不是两个独立缺陷,而是一条从「提议」到「既定任务」的污染链。切断它的关键动作只有一个:在压缩发生前,让上下文里只留下你亲自确认过的目标。外置计划文件、规则约束、主动切分会话、收敛任务粒度、版本控制兜底,这几件事做到位,大多数「我没让你做这个」的对话都可以避免。