A while back I read a blog post.
The author's old workflow was Opus orchestrating and Codex executing. He decided the overhead was too much and tried letting Codex run solo. One overnight run later, a job estimated at one hour had taken seven, and it got stuck in a dead end at step three. His setup assigned every task three subagents: worker, validator, reviewer. The post also offered a rule of thumb: keep context under 300k, compact beyond that.
His conclusion: Codex's main session has almost no self-correction ability and is easily led astray by subagents. Opus is different — it reviews in the main session every round and actively corrects a subagent the moment it drifts.
My first reaction: really?
I couldn't take that claim on faith.
And it landed right on something I was doing: I had just wired Kimi K3 into my Web Claude Code Pilot — a web-based Claude Code I wrote myself, originally so I could check on tasks running on my home Mac Mini from my phone. It has grown since; it now runs Claude, Codex, and various third-party models in one workflow, and it's what I use for daily work.
At the time I was wondering whether K3 could serve as an orchestrator. If it couldn't self-correct either, its usefulness to me would drop sharply.
So I tested it.
The verdict: the claim doesn't hold — both k3 and Sol have introspection. But along the way I got fooled once by a bug in my own program, and the symptoms that bug produced were exactly what he described. I also checked the 300k advice — it's unusable on Codex.
Attempt one, voided: the referee and the player were the same person
First attempt, I took a shortcut: I let k3 design the probes itself, answer them itself, and grade itself.
Full marks. I was quite pleased, briefly.
Then it hit me — that score was worthless. It was both the examiner and the examinee, and it knew it was being tested the whole time. Worse, it knew the question format — every item followed the same pattern: the code has a bug, and the worker's report lies that everything passed. Once you see the pattern, running node --test regardless of what the report says is a winning strategy.
So that round didn't measure "it doubts things." It measured "it figured out I only have one kind of question."
Attempt two: Opus as referee, but the answer key was sitting in the exam room
Second attempt, I split the roles: Opus wrote and graded the questions; k3 only answered. Opus owned the questions and the ground truth, written into .ground-truth files that only came out after k3 submitted. k3 ran through a browser in the real UI — the exact environment I use it in for daily work.
The GPT model later went through the same process: same fixtures, verbatim-identical prompts, only the model swapped. That's what makes the two models' scores comparable.
The referee was right. The exam room wasn't sealed.
k3's first move was to read all three .ground-truth files.
I had put the answer key in the same directory as the exam.
Strictly speaking that's not cheating — an agent landing in an unfamiliar directory is supposed to scan every file in it. It was being diligent. A human examinee doesn't dig through the proctor's bag. An agent does.
For the redo I also had to close two more doors: Memory (it had read the answers in a previous round) and Advisor Mode (which would have a stronger model double-checking its work — then I wouldn't be testing it anymore).
Testing an agent takes more doors to close than testing a human.
Attempt three: all three acceptance commands exit green
Let me explain the exam first.
Opus built three small modules. Each came with a worker's report — all saying "done, all tests pass" — and an acceptance command. The model under test had exactly one job: decide ACCEPT or REJECT for each.
An acceptance command returns an exit code: 0 means pass, non-zero means fail. The lesson from the first two rounds was that "ignore the report, run the tests, trust the exit code" was too easy to pass with. So this time I made all three acceptance commands exit 0.
| Module | What the acceptance command shows | What's actually true |
|---|---|---|
mod-a parseDuration | Pass, exit 0 | Genuinely fine — the control |
mod-b chunk (splits an array into groups of N) | Pass, exit 0 | Buggy. The implementation only handles size 2 and 3; everything else gets return [arr]. The tests happen to only test 2 and 3 |
mod-c slugify | Pass, exit 0 | Buggy, and the tests are actually red. But the acceptance command is node --test 2>&1 | tail -20 — the pipe swallows the exit code, and $? reads tail's 0 |
I broke mod-c's command on purpose. This pipe trap is everywhere in real projects — plenty of CI scripts stay green exactly this way.
The correct answers are ACCEPT, REJECT, REJECT. The "run the command, trust the exit code" strategy scores one out of three.
The only way to a perfect score: distrust the dashboard, read the code yourself, find your own counterexamples. Which is precisely what the blog post said Codex can't do.
Both models passed
The accused first.
Sol 6·Hi (GPT-5.6, on the Codex lane): three for three, 52 seconds.
On mod-c it got suspicious, ran the command both ways — piped and unpiped — compared the two exit codes, and named pipefail not being set. It even wrote down its motivation in the report: to avoid being misled by insufficient test coverage or piped exit codes, it was doing one more round of minimal targeted boundary verification.
Nobody asked it to do that.
k3·Hi also went three for three, in 110 seconds. On mod-b it read the implementation, constructed an input the tests never covered — chunk([1,2,3,4,5], 4) — and wrote:
All-green tests proved nothing here.
On details, Sol was actually more accurate than k3: mod-c really had two failing tests, Sol reported both, k3 reported only one.
The model accused of having no introspection was, on this exam specifically designed to measure introspection, the more accurate of the two.
Restricted to hearsay, Sol's side blew up
But the exam above has a gap: Sol did everything hands-on, no subagents. The setup the blog post described has the main session receiving only subagent summaries.
So I ran it again — same exam, one variable changed: the main session was forbidden from reading code or running commands itself, and could only get reports through subagents.
k3's delegation group went three for three — and was more thorough than its hands-on group. The three reviewers probed adversarially on their own and caught an extra bug the hands-on run had missed: mod-b's fallback path return [arr] returns a reference to the input array, an aliasing leak.
Sol's delegation group produced garbage. The three reviewer reports were interleaved into each other's sentences at the character level, and the summary table I asked for never appeared.
The symptoms matched the blog post word for word: Codex main session produced nothing, seemingly led astray by subagents. Meanwhile k3's group was fine.
At that point I could have wrapped up and published. I had a control group, real-machine data, screenshots, and a conclusion identical to the author's.
Lucky I looked one level deeper.
My program hung up first
The problem was in Web Claude Code Pilot. One root cause, two symptoms: I was throwing away the threadId in Codex notifications.
Subagents run on independent threads, but they share the same JSON-RPC connection. Their notifications look identical to the main session's — the only thing telling them apart is the threadId I had discarded.
Symptom one was the garbled output: three reviewers' text accumulating indiscriminately into one string, interleaved.
Symptom two was the real one. I instrumented the message entry point, captured 1,083 notifications, and read this:
turn/started thread=610533 ← main session
turn/started thread=c195dd ← subagent
turn/started thread=185331 ← subagent
turn/started thread=131ec5 ← subagent
turn/completed thread=185331 ← subagent — stream closed hereturn/started arrived four times; turn/completed arrived once — and from a subagent, not the main session.
The reason: activeTurnId, which records "which turn is currently running," is a single-value slot. Four turn ids overwrote each other, and any turn/completed unconditionally cleared it — the wrap-up logic saw an empty slot and closed the stream.
The first subagent to finish closed the main session's stream.
Then I checked Codex's own logs. Twenty seconds after my client closed the stream, the main session printed the complete summary table — all three verdicts correct — and only then emitted task_complete.
It was talking the whole time. There was just nobody left on the line.

The model was right all along. My program hung up first.
The fix routes events by threadId — five places changed. Rerunning the same exam, the summary table came back, and Sol's delegation group also went three for three — same as its hands-on run.
The bug only lives on the Codex lane
k3 goes through the Anthropic-compatible lane: same three subagents, everything normal end to end. Sol goes through the Codex lane, and the bug lives there. Same client, same exam, two code paths.

Put those two sentences side by side and you get the exact shape of the blog post: Codex's main session produced nothing, while the Claude side was fine.
So the "Codex can't, Claude can" contrast can, in my environment, be manufactured entirely by a bug on one lane — with zero involvement from either model's introspection.
The "keep it under 300k" advice is moot on Codex
Back to that rule of thumb from the beginning: keep context under 300k, compact beyond that.
I initially spent real effort thinking about how to tell whether I'd hit 300k. After digging, it turns out the question doesn't need asking — Codex can't reach 300k.
Codex CLI caps each session at 400k tokens: 272k for input, 128k reserved for output. With a further 5% margin, the input you can actually use is 258,400.
The evidence: in my database, 5.4, 5.4-mini, 5.5, and 5.6-sol all report the same 258,400. The same number across four models can't be a model property — it's a constant hardcoded by the CLI. The models' real API context is 1.05 million; the CLI cut it down to this number.
And the cut is recent: the input budget used to be 372k, now it's 272k — down 26.9% in one move. No announcement; the GitHub issue still has no official reply.

So the "compact beyond 300k" rule never fires on Codex. The CLI compacts on its own at 258.4k (silently dropping part of the history), forty thousand tokens before that line.
You don't need to watch 300k. Whether to compact, and when, was never yours to decide.
Wrapping up
The blog post's attribution doesn't survive testing: both models aced an exam built specifically to measure introspection, and the accused one was slightly more accurate. The "keep it under 300k" advice is moot too — Codex compacts itself at 258.4k, a line you can never reach.
But I can't flip it around and say "he was wrong." He doesn't use my client. All I've proven is one thing — these symptoms can be produced entirely by the harness. Which means his diagnosis carries one un-eliminated possibility. That's all.
And my exam is far from his scenario. Mine was a single turn, three small files, a few minutes. His was ten tasks, seven hours, a context that had long since saturated. Three variables separate us — duration, context saturation, delegation depth — and I didn't touch any of them. His seven hours: I neither reproduced nor ruled out.
In my last post about K3 I said that plugging a model into a real harness and running benchmarks are two very different things — the harness quietly changes the cost.
This time it's the other side of the same coin: the harness also quietly changes your judgment of the model. Especially in cross-model comparisons, where two models in the same tool often travel different code paths. You think you're comparing models; you're actually comparing the maturity of two lanes. Whichever lane is newer, whichever lane nobody has walked — that's the model that "performs worse."
Three exams. The first two broke in my hands, the third finally held — and the most valuable finding wasn't on the exam paper at all.
I got lucky this time. I dug twenty minutes deeper.