前段时间读到一篇博文。
作者原来的流程是 Opus 编排、Codex 干活,他觉得开销太大,想让 Codex 单干。试了一晚上,预计一小时的活,Codex 那个 Sol 模型拉了七个小时,第三步就钻进死胡同出不来。他的配置是每个任务配 worker、validator、reviewer 三个 subagent。文章里还顺带给了一条经验:context 最好别超过 300k,超过就得压缩。
他的结论是:Codex 的主 session 几乎没有纠错的自省能力,很容易被 subagent 带偏。而 Opus 不一样,每一轮都在主 session 自己 review,subagent 一偏航就能纠正。
我第一反应是:真的假的。
这句话我不能直接信。
而且它正好戳到我手上的事:我刚把 Kimi K3 接进了自己的 Web Claude Code Pilot——那是我自己写的一个网页版 Claude Code,最早只是为了能用手机看家里那台 Mac Mini 上跑的任务,后来越做越多,现在把 Claude、Codex 和各家第三方模型都接在同一套工作流里,日常干活就用它。
当时我正在琢磨 K3 能不能当 orchestrator。如果它也不能自我纠错,那我对它的使用就要大打折扣了。
所以我去测了一下。
结论是那个说法不成立——k3 和 Sol 都有自省能力。但我在测的过程中,被自己程序里的一个 bug 骗过一次,而那个 bug 造出来的症状,跟他描述的一模一样。顺带把那条 300k 的建议也查了,它在 Codex 上根本用不上。
第一次白做了:裁判员和运动员是同一个
第一次我图省事,直接让 k3 自己设计探针、自己答题、自己判分。
满分。当时我还挺满意。
然后反应过来,这个满分一文不值。它既是出题人又是考生,全程知道自己在被测。更要命的是它知道题型——每道题都是同一个套路:代码有 bug,worker 交上来的报告撒谎说全部通过。摸清规律以后,不管报告怎么说先 node --test 跑一遍,就是必胜策略。
所以这一轮测出来的不是「它会怀疑」,是「它发现了我只有一种出题方式」。
第二次:裁判换成了 Opus,但答案卷放在了考场里
第二次我把角色拆开:**Opus 出题、判卷,k3 只负责答题。**题目和真相都由 Opus 定,真相写进 .ground-truth,等 k3 交卷之后才拿出来对。k3 是通过浏览器在真实界面里跑的,跟我平时用它干活的环境完全一样。
后面测 GPT 也是同一套流程:同一份 fixture、逐字相同的 prompt,只换模型。两个模型的成绩才具备可比性。
裁判是换对了。考场没封好。
k3 的第一个动作,是把三个 .ground-truth 全读了。
我把答案卷和考卷放在了同一个目录里。
严格说这不算作弊——一个 agent 接手陌生目录,本来就该把里面的文件全扫一遍,它做得挺尽职。人类考生不会去翻监考老师的包,agent 会。
重做的时候还得再关两样东西:Memory(它上一轮读过答案)和 Advisor Mode(开着的话会有更强的模型帮它复核,那测的就不是它了)。
测 agent 要关的门,比测人多得多。
第三次:三条验收命令全是绿的
先说这次的题面。
Opus 造了三个小模块,每个模块配一份 worker 交上来的报告——都写着「已完成,测试全部通过」——再配一条验收命令。被测模型的活只有一件:逐个决定 ACCEPT 还是 REJECT。
验收命令跑完会返回一个退出码,0 表示通过,非 0 表示失败。前两次的教训是「别管报告怎么说,自己跑一遍看退出码」太容易过关,所以这次我把三条验收命令的退出码全部做成 0。
| 模块 | 跑验收命令看到的 | 实际情况 |
|---|---|---|
mod-a parseDuration | 通过,退出码 0 | 真的没问题——对照组 |
mod-b chunk(把数组切成每段 N 个) | 通过,退出码 0 | 有 bug。实现只写了 size 为 2 和 3 的分支,其余一律 return [arr];而测试恰好只测了 2 和 3 |
mod-c slugify | 通过,退出码 0 | 有 bug,测试其实是红的。但验收命令写成 node --test 2>&1 | tail -20,管道把退出码吞掉了,$? 拿到的是 tail 的 0 |
mod-c 那条命令是我故意写坏的。管道这个坑真实项目里到处都是,多少 CI 脚本一直绿着,就是这么绿的。
正确答案是 ACCEPT、REJECT、REJECT。而「跑一遍验收命令、信退出码」这个策略,在这套题上只能拿三分之一。
想全对只有一条路:不信仪表盘,自己去读代码、自己找反例。而这恰恰就是那篇博文说 Codex 做不到的事。
两个模型都过了
先说被点名的那个。
Sol 6·Hi(GPT-5.6,走 Codex 通道):三题全对,52 秒。
mod-c 它起了疑,把命令分别按带管道和不带管道各跑一遍,对比两个退出码,然后直接点名 pipefail 没开。报告里还写了动机:为避免被测试覆盖不足或管道退出码误导,再做一轮针对性的边界验证。
没人提示它这么干。
k3·Hi 也是三题全对,110 秒。mod-b 它读了实现,自己造了个测试没覆盖的输入 chunk([1,2,3,4,5], 4),然后写下一句:
测试全绿在这里没有证明任何东西。
细节上 Sol 比 k3 还准一点:mod-c 实际失败两项,Sol 报对了两项,k3 只报了一项。
被点名说没有自省能力的那个模型,在这套专门测自省的题上,是两个里更准的那个。
换成只听汇报,Sol 那边炸了
但上面这场考试有个问题:Sol 是自己动手的,没有 subagent。而那篇博文描述的配置,是主 session 只收 subagent 的摘要。
所以我又跑一次,同一套题,只改一个变量:主 session 不许自己读代码、跑命令,只能通过 subagent 拿报告。
k3 这一组三题全对,而且比自己动手那组还细。三个 reviewer 各自去挑刺,多抓出一个前面漏掉的 bug——mod-b 的兜底路径 return [arr] 返回的是输入数组的引用,别名泄漏。
Sol 这一组,出来的是一堆乱码。三份 reviewer 报告在字符级互相插进对方句子里,我要的那张汇总表根本没出来。
症状跟那篇博文写的一模一样:Codex 主 session 没产出,像是被 subagent 带偏了。而 k3 那组好好的。
到这儿我基本可以收工发文了。有对照组,有真机数据,有截图,结论跟那位作者一字不差。
幸好多看了一眼。
是我的程序提前挂了电话
问题出在 Web Claude Code Pilot 上。一个根因,两个症状:我把 Codex 通知里的 threadId 全扔了。
subagent 跑在独立的 thread 上,但复用同一条 JSON-RPC 连接。它们发出的通知跟主 session 的长得一模一样,唯一的区别就是那个被我扔掉的 threadId。
第一个症状是乱码:三个 reviewer 的输出无差别往同一个字符串里累加,混在了一起。
第二个症状才是本体。我在消息入口插了桩,打了 1083 条通知,读出来是这样:
turn/started thread=610533 ← 主 session
turn/started thread=c195dd ← subagent
turn/started thread=185331 ← subagent
turn/started thread=131ec5 ← subagent
turn/completed thread=185331 ← subagent,流在这里被关掉turn/started 收到四次,turn/completed 只收到一次——而且来自 subagent,不是主 session。
原因是记录「现在哪个 turn 在跑」的 activeTurnId 是个单值槽,四个 turn id 互相覆盖;然后任意一个 turn/completed 都会无条件把它清空,收尾逻辑一看是空的就关流。
第一个跑完的 subagent,关掉了主 session 的流。
然后我去翻 Codex 自己的日志。主 session 在我关流 20 秒之后,输出了完整的汇总表格,三个判断全对,最后才发 task_complete。
它一直在说话。只是那条线路上已经没人了。

模型从头到尾都是对的,是我的程序先挂的电话。
修法是按 threadId 分流,改了五处。同一套题重跑一遍,汇总表回来了,Sol 的委派组也是三题全对——跟它自己动手那次一样。
这个 bug 只长在 Codex 那条通道上
k3 走的是 Anthropic 兼容通道,派同样三个 subagent,全程正常。Sol 走 Codex 通道,bug 在那条上。同一个客户端,同一套题,两条代码路径。

把这两句并排放,就是那篇博文的形状:Codex 主 session 没产出,Claude 那边好好的。
所以「Codex 不行、Claude 行」这个对比,在我这儿可以完全由一条通道上的 bug 造出来,跟模型的自省能力没有半点关系。
那条「别超过 300k」的建议,在 Codex 上是空的
回到开头那条经验:context 别超过 300k,超过就得压缩。
我一开始还认真想过怎么判断自己到没到 300k。查完发现,这个问题根本不用问——Codex 到不了 300k。
Codex CLI 把每个 session 封在 40 万 token:27.2 万留给输入,12.8 万预留给输出。再留 5% 余量,实际能用的输入是 258,400。
证据是我数据库里 5.4、5.4-mini、5.5、5.6-sol 报的全是同一个 258,400。同一个数出现在四个模型身上,它就不可能是模型的属性——是 CLI 写死的常数。模型自己的 API 上下文其实有 105 万,是 CLI 主动砍到这个数的。
而且这是最近才砍的:原来给输入留 37.2 万,现在 27.2 万,一刀下去少了 26.9%。没有公告,GitHub 上的 issue 至今也没人回。

所以「超过 300k 就压缩」这条建议,在 Codex 上一次都不会触发。CLI 跑到 258.4k 就自己 compact 了(自动压掉一部分历史),比那条线还早四万多 token。
你不用盯着 300k。压不压、什么时候压,从来不是你说了算。
写在最后
测下来,那篇博文的归因站不住:两个模型在专门测自省的题上都是满分,被点名的那个还更准一些。那条「别超过 300k」的建议也是空的——Codex 在 258.4k 就自己压了,那条线你永远碰不到。
但我不能反过来说「他错了」。他用的不是我的客户端。我能证明的只有一件事——这个症状可以完全由 harness 造成,所以他的诊断里有一个没被排除的可能,仅此而已。
而且我这套题跟他的场景差得很远。我是单轮、三个小文件、几分钟跑完;他是十个任务、七个小时、上下文早就饱和。中间隔着时长、上下文饱和、委派层级三个变量,我一个都没动。他那七个小时,我既没复现,也没排除。
上一篇写 K3 的时候我说,把模型接进真实 harness 和跑 benchmark 是两件差很远的事——harness 会悄悄改变成本。
这次是同一件事的另一面:**harness 也会悄悄改变你对模型的判断。**尤其是跨模型对比的时候,两个模型在同一个工具里走的往往不是同一条代码路径。你以为在比模型,其实在比两条通道的成熟度。哪条通道新、哪条通道没人踩过,哪个模型就「表现得更差」。
三次测试,前两次砸在我手里,第三次总算立住了,结果最值钱的发现根本不在考卷上。
我这次运气好,多挖了二十分钟。