TL;DR: I wired OpenAI's Codex CLI into Claude Code as an adversarial reviewer with a convergence loop. Then I pointed the loop at its own implementation. It failed the review three times before passing, and every single failure was a real bug. Here's the whole story, with the prompts and shell recipes you can steal.
The tweet that started it
A while ago Siqi Chen posted his favorite way of working with frontier models:
step 1. ask it to write a plan
step 2: "please get second opinions from codex CLI using gpt-5.6-sol @ max effort and kimi CLI using kimi 3. Revise your plan with any sound findings. repeat until convergence or up to 5 rounds."
There's also a companion repo, adversarial-execution, which applies the same idea at the other end: before you mark work as done, fresh reviewer sessions have to pass it with evidence, in a loop, until a fresh round finds nothing new.
Two ideas jumped out at me:
- Fresh sessions, not continuations. A reviewer that already critiqued round 1 will defend its own findings in round 2. Anchoring is real for LLMs too.
- "Looks right in the code is not evidence." The reviewer judges proof (test output, screenshots, command results), not vibes.
I already had a Claude Code skill that wraps the local codex CLI as a second engineer: consult it read-only, delegate a fix, run a git-aware review, resume a session. All useful. All one-shot. No loop anywhere.
So I asked Claude Code to study the tweet and the repo and tell me whether the skill was worth upgrading.
Round zero: Codex reviews the plan for using Codex better
Claude wrote a proposal: two new modes (a plan-review convergence loop and a done-gate), plus supporting rules. Decent plan. Then came the fun part. Before touching anything, I had Claude send the proposal itself to Codex for an adversarial review, using the exact contract style the proposal was recommending.
Verdict: FAIL. Eight gaps, seven of them major.
And they weren't nitpicks. My three favorites:
The output capture in the existing skill was broken. The recipes piped codex exec output through tail -40 and told Claude to grab the session id "from the header". But the header is at the start of stdout, and tail shows the end. On any long run the session id is simply gone. Better yet: this exact failure happened during the review run itself. The reviewer described a bug and the run reproduced it live. The pipeline exit code was wrong too, because without PIPESTATUS you get tail's exit code, not codex's.
False convergence. My loop definition said: converged when a round brings "no new sound findings, only repeats". Codex pointed out that a finding I accepted but fixed incompletely would come back as a "repeat" and get waved through. A repeated unresolved finding has to stay open. And hitting the round cap needed an explicit NOT CONVERGED outcome instead of quietly presenting the last plan as final.
Windows command lines have a length limit. Passing a plan, a diff, and the round history as one quoted shell argument blows past ~32k characters. Codex suggested the CLI's stdin form (codex exec ... - < prompt.txt), which my own skill was accidentally blocking with an unconditional </dev/null.
I triaged all eight findings. All eight were sound. That triage step matters, by the way. The instruction is "revise with any sound findings", not "do whatever the reviewer says". The orchestrating model stays the decision maker; the reviewer is a hostile witness, not a boss.
Simplify before you ship
The revised proposal had grown formal machinery: a findings ledger with four statuses, evidence manifests with exit codes and timestamps. My actual usage is a few Codex calls per month. That's how you end up with a skill nobody follows.
So I shipped a lean version instead. The concepts survived, compressed to one line each. A skill is instructions to a model, not code. The model can track a simple ledger in its head; what it needs written down are the rules it would otherwise get wrong:
- Re-reviews happen in fresh sessions, never
resume. - Adopt only sound findings, record rejections with a reason.
- An adopted-but-unfixed finding stays open.
- Cap the loop (3 rounds for plans, 2 for the done-gate), and report NOT CONVERGED honestly if you hit it.
- A failed or empty run never counts as a round, and never as a PASS.
The done-gate eats its own dog food
New rules in place, one question left: is the upgrade itself actually done? Perfect job for the brand-new done-gate. Fresh Codex session, max reasoning effort, evidence pack attached (diffs, validator output), and a contract that forces a machine-checkable verdict:
INTENT: PASS | FAIL - does the work satisfy the stated acceptance criteria? (evidence)
WORKS: PASS | FAIL - does it actually function, per evidence, not per code reading? (evidence)
PLAN_FIT: PASS | FAIL - does it advance the overall plan without drift? (evidence)
VERDICT: PASS only if all three are PASS, else FAIL.
GAPS: numbered with stable IDs; severity, problem, evidence, smallest fix. If none: GAPS: NONE.
IMPROVEMENTS: optional non-blocking suggestions (max 5). These never affect the verdict.
Enter fullscreen mode Exit fullscreen mode
Round 1: FAIL. Four gaps. The best one: I had fixed the output handling in modes 1 and 2 but not in the review and resume recipes, and my shiny new "pass long prompts via stdin" rule used a $PROMPT_FILE variable that no example ever created. Copy-paste the doc as written and you'd get "No such file or directory".
Round 2: FAIL. Two gaps. One was a claim I refused to take on faith: Codex said the skill's original focused-review example, codex exec review --uncommitted "focus on error handling", is an invalid invocation, because the review target flags conflict with a positional prompt. That example predated this whole upgrade. One free local test later (argument parsing fails before any model call, so it costs nothing):
$ codex exec review --uncommitted "focus on error handling" -o /dev/null
error: the argument '--uncommitted' cannot be used with '[PROMPT]'
Enter fullscreen mode Exit fullscreen mode
Confirmed. A broken example had been sitting in the skill since day one, and a fresh pair of (artificial) eyes caught it on pass two.
Round 3: PASS. All three dimensions, GAPS: NONE, nothing reopened. Convergence, for real this time.
Three rounds, three sets of genuine defects, then nothing left to find. That's exactly the behavior the pattern promises, and I got to watch it happen to my own work.
The pieces you can steal
The reliable shell wrapper for non-interactive codex runs (Git Bash, works on Windows too):
OUT=$(mktemp codex-out-XXXX.txt)
LOG="${OUT%.txt}.log"
codex exec -s read-only --skip-git-repo-check \
-c model_reasoning_effort="xhigh" \
-o "$OUT" - < prompt.txt 2>&1 | tee "$LOG" | tail -20
RC=${PIPESTATUS[0]}; echo "rc=$RC"
grep -m1 -i "session id" "$LOG"
cat "$OUT"
Enter fullscreen mode Exit fullscreen mode
Every piece earned its place the hard way: -o captures the clean final message, tee keeps the full log so the session id survives, PIPESTATUS[0] gets the real exit code, and the trailing - < prompt.txt feeds a prompt of any size through stdin.
The plan-review contract (the done-gate variant is above):
You are an adversarial reviewer of a PLAN (no code changes exist yet).
Judge the plan against the stated task and acceptance criteria AND against
the actual repo state (read the code the plan touches).
Review independently first; only then reconcile with the round history (if
provided), classifying each finding as NEW, REOPENED, or DUPLICATE.
Return exactly:
VERDICT: PASS | FAIL
GAPS: numbered with stable IDs; severity, problem, evidence, smallest fix.
If none: GAPS: NONE.
IMPROVEMENTS: optional non-blocking suggestions (max 5). These never affect
the verdict.
Do not restate the plan. Do not raise style nits as gaps.
Enter fullscreen mode Exit fullscreen mode
And because nobody wants to memorize six modes, I added a /codex slash command that acts as a dispatcher. I describe what I want in plain language; Claude walks a decision tree (continuation? edits? plan? finished work? whole diff? everything else) and picks the mode, assembles the context Codex can't see (the exact error, what was already ruled out, the diff, the evidence pack), and composes the prompt. The "what was already tried and ruled out" part is the single highest-value thing you can put in a delegate prompt. It stops the second model from re-walking your dead ends.
What I learned
- Fresh sessions beat follow-ups for re-review. A resumed reviewer defends its previous verdict. A fresh one attacks the current state.
-
Make verdicts machine-checkable. "Looks good with some concerns" can't drive a loop.
VERDICT: FAIL+ numbered gaps can. - Triage, don't obey. Both models are wrong sometimes. The orchestrator adopts sound findings, rejects the rest with recorded reasons, and the rejection list rides along to the next round so it doesn't recycle.
- Verify reviewer claims cheaply before believing them. The arg-parse test cost nothing and turned a claim into a fact.
- Cap the loop, admit the cap. Unbounded loops sound principled and burn quota. NOT CONVERGED is a perfectly good answer.
- The reviewer finds different bugs than the author. Not smarter, different. Every round proved it.
The whole exercise took an afternoon and four Codex runs on a regular ChatGPT subscription. The skill now catches its own mistakes before I ship them, which is a sentence I didn't expect to write this year.
Have you tried pitting two coding agents against each other? What broke first? Share in the comments!
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.