你一定见过这样的会话。

一个登录接口偶发超时。你把问题丢给 Codex,它开始工作:先列目录,再读认证模块,顺手把整个 services/auth/ 扫了一遍,然后跑测试,失败,再读一遍日志,又去翻网关配置,最后在某个中间件里找到一行被吞掉的超时设置,改掉,测试通过。

整个过程花了二十分钟,烧掉一百多万 token。而真正解决问题的信息,是那三行报错堆栈加上一个配置项。剩下的,全是陪跑。

这不是 Codex 不会用,这是所有仓库级 agent 的共同困境。ICML 2026 收录的 ContextSniper 把这个困境量化了出来,结论值得每个把 agent 用在真实代码库上的人认真读一遍。

一半 token 去哪儿了#

ContextSniper 由 Luk 等人提出。他们在 SWE-bench Lite 的一个 50 任务子集上做了一组对照实验,测的是「修复一个真实仓库 bug」这件事的成本结构。

基线数据相当扎眼:一个未改造的 OpenClaw agent,跑完这 50 个任务总共消耗了 6780 万 token,平均每个任务 136 万。Claude Code 更贵,总共 8597 万。

关键在于这 136 万 token 的构成。整文件的通读、宽泛的 grep 扫描、原封不动的终端输出,一层一层往上下文里堆。模型对这些内容并不会真正投入注意力,但你得为每一个 token 付两次钱:一次是输入计费,一次是压缩导致前缀缓存失效后的惩罚。

后者的代价常被低估。TokenPilot 在 2026 年 6 月的测试里记录了一个很说明问题的数字:在朴素的压缩策略下,prompt cache 的命中率从 79.2% 掉到 38.7%。缓存本来是最便宜的省钱手段,结果被压缩策略亲手毁掉。

ContextSniper 的做法是换一个思路:与其想办法把窗口撑大,不如严格筛选什么内容有资格进入窗口。它用一套三级记忆层次来检索「证据包」,结果是 OpenClaw 的 token 用量砍掉 51.5%,Claude Code 砍掉 38.9%,而 SWE-bench Lite 上的解决率只掉了两个百分点(26.0% → 24.0%,32.0% → 30.0%),这个幅度还在 50 任务样本的置信区间内。

真正有意思的不是省了钱,而是动作数的变化。OpenClaw 的工具调用次数减少了 46.4%。为什么?因为 agent 不再需要为了找回被压缩掉的证据而去重读文件、重跑搜索。省下来的不只是账单,还有时间。

指标OpenClawClaude Code
Token 降幅51.5%(6780万 → 3291万)38.9%(8597万 → 5255万)
成本降幅36.4%27.3%(15.09 → 10.97 美元)
解决率变化26.0% → 24.0%32.0% → 30.0%
动作数降幅46.4%—

「证据包」到底是什么意思#

三级记忆的核心是分工。

第一层是模型真正能看到的工作视图,只放当前这一步必需的投影。第二层保存完整内容,用惰性句柄引用,需要时再取。第三层是索引与摘要,用极小的空间记录「哪个证据在哪个位置」。

被检索出来递给模型的,不是整份文件,也不是整段日志,而是一个压缩过的证据包:精确到行的代码片段、具体的报错文本、相关的配置值。ContextSniper 的关键点在于它给这个包留了恢复路径。即使压缩后模型发现信息不够,也能顺指针回到第二层拿到原文,而不是像大多数压缩实现那样,摘要一旦生成,原文就永久消失。

这正是当前主流 agent 压缩机制最别扭的地方。Codex CLI 的自动压缩是「摘要替换原文」,一轮下去细节就没了。你没法问它「我刚才第 40 轮跑的那个 grep 输出到底是什么」,因为它答不上来,那段内容已经不在会话里了。

映射到 Codex CLI 今天的配置#

ContextSniper 是一套研究系统,Codex CLI 并没有内置同构的记忆层。但它的配置面已经能覆盖其中几个核心机制,程度不同而已。

第一,在入口处设闸。 对应的是 tool_output_token_limit。测试输出五万 token,Codex 会在注入上下文前按这个上限截断。默认值比较宽松,把它压到 8000 到 12000 之间,就等于强制 agent 拿压缩过的证据干活,而不是拿原始堆栈。

# ~/.codex/config.toml
tool_output_token_limit = 10000

要注意这里的差别:ContextSniper 的闸门是按意图分类的,它会判断这段输出值不值得留下。tool_output_token_limit 是一刀切的,通过的回溯日志和失败的报错拿同样的预算。所以它是个近似解,不是等价物。

第二,让压缩更早、更有结构。 model_auto_compact_token_limit 控制触发点。默认大约在窗口的 80% 到 90%,等到那时候才压缩,留给摘要的空间已经很紧张。调低一点,压缩频率上升,但每次摘要能保留的细节更多。

model_context_window = 200000
model_auto_compact_token_limit = 140000
tool_output_token_limit = 10000

第三,用钩子做确定性过滤。 意图判断可以不用等模型,直接在工具调用前拦一道:

[hooks.pre_tool_use.file_read_gate]
command = "python3 /opt/hooks/evidence-gate.py"

这个脚本可以做很朴素的事,比如拦截对整个目录的通配读取,或者对超过某个体积的文件先返回结构摘要而不是全文。它比调整模型行为可靠,因为它是确定性的。

第四,把 AGENTS.md 当作持久证据层。 这是我认为性价比最高的一招。AGENTS.md 在每次会话里都会被重新读取,天然免疫压缩。凡是「永远成立」的东西,端口号、接口基址、认证格式、目录约定,全都搬进去,别指望它们在摘要里活下来。

## 关键上下文(压缩后必须保留)
- 认证接口基址:https://api.example.com/v3
- 数据库端口:5432,不要改动
- Token 格式:Bearer JWT,RS256 签名

配置写得越厚,压缩需要保住的东西就越少。

第五,按工作类型分层。 探索阶段和实现阶段的上下文需求完全不同,用命名 profile 分开:

[profile.explore]
model_auto_compact_token_limit = 120000
tool_output_token_limit = 8000

[profile.implementation]
model_auto_compact_token_limit = 180000
tool_output_token_limit = 16000

Codex CLI 现在还缺什么#

诚实地说,有几件事靠配置补不上。

没有三层记忆的恢复能力。一旦压缩,原始证据就取不回来了,agent 只能重跑一遍工具调用,这正是那 46.4% 多余动作的来源。

没有可寻址的追加式记录。rollout.jsonl 按时间顺序记流水,但它不是为「按地址随机检索」设计的,你没法精确拿到某一条历史证据。

没有跨轮的持久运行时状态。一个轮次里算出来的中间结论,如果不写进磁盘或重新塞回提示词,下一轮就没了。

所以眼下这些配置是缓解,不是解法。但缓解已经很值钱了。一个跑在 95% 上下文占用、身后叠了三轮压缩的会话,输出质量明显不如一个干净的、上下文聚焦的会话,这跟模型能力无关,是上下文架构的问题。

一点个人看法#

ContextSniper 最值得记住的一句话,其实是它反过来的表述:默认的「把东西全读进来,让模型自己挑」这个模式,大约浪费了一半的预算,而模型根本不会用到那些被浪费的内容。

这和我平时使用 agent 的体感是一致的。我们总以为给模型更多信息是安全的,读全文件总是比读片段稳妥。但在真实仓库里,信息过剩本身就是一种污染。它挤压了真正重要的证据的注意力权重,还在压缩时制造连锁损失。

如果你只打算改一个地方,我建议从 AGENTS.md 开始。把那些永远不变的事实从对话里搬到文件里,再给工具输出设个上限。这两个动作加起来不到十分钟,但能立刻让你的会话在长时间运行后依然保持清醒。

窗口大小是厂商给的,上下文纪律是你自己挣的。

参考来源#

本文基于 Daniel Vaughan 的 Codex CLI 知识库文章 ContextSniper and the Evidence Packet Pattern 整理重写,原始研究为 Luk 等人的 ContextSniper(ICML 2026),以及 TokenPilot(Xu et al., 2026)关于缓存感知压缩的实验数据。