设想一个很普通的场景。你在终端里打开 Codex CLI,只敲了一句最简单的指令:Reply with pong.,意思是「回复 pong」。你大概会觉得,发给模型的东西,无非就是这 16 个字符而已。

但有人真的把 Codex 的请求截了下来,逐字节去量。结论让人意外:这 16 个字符被包装成了一个 42980 字节的 HTTP 请求体,用 o200k_base 在本地估算下来,大约 9435 个 token。而你敲的那句「Reply with pong.」,只占其中 25 个 token,比例是 0.3%。

剩下的 99.7%,全部是 Codex 自己塞进去的东西:工具定义、系统指令、权限信息、技能元数据、环境上下文,还有一整套请求的框架。

这篇文章想聊的,就是这个被大多数人忽略的「开销」,以及它对我们理解 Codex 意味着什么。

那 99.7% 到底是什么#

先看账。9435 个 token 里,有三样东西合计占掉了 7696 个:

内容序列化体积本地估算 token
工具定义(additional_tools)16741 字符3942
开发者指令(developer message)17730 字符3729
用户消息(Reply with pong.)16 字符25

工具定义是四个顶层条目:exec、wait、request_user_input,还有一个 collaboration 命名空间。但别被「四个」骗了,它们背后远不止四个动作。exec 描述了命令执行、补丁、图片检查、计划更新等一堆嵌套工具,collaboration 里又藏着六个子工具。这 3942 个 token,是一整棵工具树的说明书。

开发者指令则是 Codex 的主指令,17730 个字符,占了接近 4000 个 token。它告诉模型「你是谁、你要怎么干活、遇到什么情况该怎么处理」。换句话说,你每敲一句「修个 bug」,Codex 都会先把这将近 2 万字符的行为手册重新铺一遍。

这个实验是怎么做的#

作者没有调用任何外部模型。他把 Codex 指向了一个跑在本地的 HTTP 服务器,这个服务器只做三件事:记录每一次请求、抹掉敏感的请求头、然后返回一个写死的假响应。

于是整条链路变成了这样:Codex 客户端 → 本地记录器 → 固定的假响应。记录器能精确地告诉我们客户端到底发了什么,但它无法展示真实服务商收到请求后还会做什么改动、有没有命中缓存、最终按什么口径计费。所以文章里的 token 数都是本地估算,不是账单数字,而请求体本身是逐字节准确的。

测试用的是 Codex CLI 0.145.0,模型是 gpt-5.6-sol。

AGENTS.md:指令链会被完整发出去#

Codex 会从仓库根目录开始,一路到它启动时所在的目录,逐层查找 AGENTS.md,越靠近启动目录的指令越靠后。作者做了个对照实验:一个根目录 AGENTS.md 和一个子目录 AGENTS.md,各放 100 个唯一标记。

从仓库根目录启动,只发送根目录的 100 个标记,子目录的不会出现;从子目录启动,则 200 个标记全部发送;从根目录启动后再执行 ls child,也不会把子目录的指令加进来。

也就是说,决定自动指令链的是启动目录。只有当你显式去读那个文件,它的内容才会作为普通的工具历史进入请求。

体积测试用的是高熵的合成标记,比如 PAIRED_AGENTS_1000_0001,这种字符串在 o200k_base 下会被切成 11 个 token,而普通英文单词不会。所以表格里的数字是「指令会被完整传输」的证据,并不等于你自己的 AGENTS.md 的真实开销。但趋势是明确的:250 个合成标记让请求多了 2530 个 token,1000 个标记则多了 11030 个 token,而且是线性地全量增加。

Skills 分两阶段加载#

仓库级技能放在 .agents/skills/ 目录下。作者发现,技能最初只贡献自己的名字、描述和路径,SKILL.md 的正文要等到真正被读取时才进入请求。

一个技能(12 个词的描述加 200 个词的正文)大约让首个请求增加 125 个 token,十个技能就是 1250 个 token。但无论一个还是十个,正文里的标记在首个请求里一个都没出现。这是一种省成本的懒加载:先给模型看目录,用到哪个再展开哪个。

MCP 工具描述被延迟加载#

MCP 的行为更极端。作者测试了本地 MCP 服务器,初始请求里根本不包含这些自定义工具的名字和描述,exec 接口只是多了一段通用的「MCP 发现」引导。直到 exec 把延迟的工具条目打印出来,那些描述才真正进入请求。

延迟加载一旦触发,代价也不小。一个服务器三个工具(每个 40 个标记),让请求多了 1522 个 token;两个服务器七个工具,多了 4210 个 token。最夸张的是,一个塞了 5000 个标记的工具描述,即使被截断成「头尾采样」,仍然保留了 1046 个标记,下一个请求因此膨胀了 9631 个 token。

一次真实的 bug 修复,请求如何逐轮膨胀#

作者搭了一个有配置 bug 的 Python 小项目:显式的 false 会被用户默认值覆盖。任务是修复这个优先级 bug,加回归测试,再跑测试套件。这个 trace 记录的是「请求体积如何增长」,不是模型的推理过程,因为每一步动作都是预先定好的。

从初始任务到最后校验 diff,一共 10 个动作:

动作原始字节本地估算 token
初始任务441899815
搜索4528910114
读文件4627810368
初次测试4695910529
添加回归4763510701
回归失败4883210968
二次检查4995311254
应用修复5047111386
测试通过5117011554
校验 diff5238911889

最终请求比第一个大了 2074 个 token。搜索到的结果、读到的文件内容、测试输出、失败信息、补丁、最终 diff,全都留在了后面的每一轮里。上下文不是一个静态的快照,而是一口越挖越深的井。

文件、终端输出、图片,怎么进入上下文#

一个关键结论是:Codex 不会自动上传整个仓库。作者造了五个文件,包括正常源码、被 .gitignore 忽略的文件、假的 .env、一个两万行的日志,还有一个在 AGENTS.md 里被点名的文件。第一次请求里,这些文件的内容一个都没出现。

rg --files -uu 只会把文件名加进去,不会加内容。但一旦显式读取,即使是被忽略的文件、假的 .env,内容也会进入后续请求。.gitignore 挡不住显式读取。

那个两万行的大日志,会被截断成「头尾采样」后才进入请求。终端输出同理:10 行、100 行的结果会原样保留到下一轮;一万行的输出会被截断成头尾采样,但请求仍然从 9584 个 token 涨到了 25835 个 token。更值得注意的是,重复输出不会被去重,ANSI 格式文本和 Python 堆栈也都会原样留在历史里。

图片则以 data URL 的形式传输。一张 32×32 的 PNG 变成 442 字符的 base64 URL;一张更大的渐变 PNG 会被 Codex 重新缩放、重新编码成 1600×1600,变成 71666 字符的 data URL。两张图一起出现时,两个 data URL 都会保留。

压缩:把历史再送进一次请求#

当历史积累得太大,Codex 会把它再送进一次模型请求,用摘要替换掉原始内容。在自定义 provider 这条路径上,作者抓到了完整的两阶段过程:先发送累积的历史加上一个摘要提示词,再围绕保留下来的用户消息和返回的摘要重建对话。

他用合成用量强制触发压缩:报告 13000 个输入 token、20000 的上下文窗口、12000 的压缩阈值。于是产生了三个请求:初始请求约 9378 个 token,包含正常的工具和用户请求;压缩请求约 21408 个 token,包含累积历史和一个摘要提示词,工具列表是空的;恢复请求约 9500 个 token,工具和原始用户消息回来了,而原始的调用和输出被合成摘要替换掉了。

这里有个值得记住的细节:压缩之后,某个信息可能只存在于模型生成的摘要里,而不是原始的某条消息或某段工具输出里。换句话说,你无法保证一条关键线索在压缩后还「原样」存在。

到底什么会离开你的机器#

作者把信息分成了两类。一类在任何人工具使用之前就存在:Codex 的指令、可见的工具接口、被发现的 AGENTS.md 指令链、技能元数据、环境上下文,以及用户提示词。另一类只有在你做了某个动作之后才出现:读文件后的内容、跑命令后的终端输出、发现后的 MCP 描述、附件里的图片。

未读取的仓库文件、被忽略的文件、假的 .env,都不会自动出现。文章的收尾那句话写得很妙:「The prompt was the seed. The request was the working state.」提示词是种子,请求才是工作状态。

这对我们意味着什么#

这篇文章的价值,不在于「原来 Codex 会发这么多东西」这种猎奇,而在于它把一个我们平时看不到的边界摆到了台面上:当你把一段代码、一个目录交给 Codex,到底有多少信息会离开这台机器。

对普通用户,它是一份诚实的提醒。.gitignore 不是隐私边界,显式读取会穿透它;终端输出、堆栈信息、甚至图片,都可能被送出去。如果你在意某个文件不该外传,最可靠的做法是别让 Codex 去读它,而不是指望一层忽略规则。

对工程团队,它解释了一个很实际的现象:为什么 Codex 的 token 消耗总是比想象中高,为什么上下文窗口会被快速填满,为什么「少让它读东西」能立竿见影地省钱。工具定义和系统指令这些固定开销几乎是恒定的,真正能控制的,是让哪些文件、哪些输出进入历史。

更深一层,它也印证了 Codex 官方最近在《Codex as a platform》里传递的信号:真正决定一个智能体表现和成本的,往往不是模型本身,而是模型周围那一整套执行机制。这 4 万字节,就是那套机制最直观的一张快照。

参考来源:What Codex Actually Sends to the Model,作者 0xkato,发布于 0xkato.xyz。 原文链接:https://www.0xkato.xyz/what-codex-actually-sends-to-the-model/