Companion Story · Let AI Take Your Exams · IITM Paradox
How Codex Took the Exam

Two Words, Ten Questions,
and a Machine That Read the Source

Anand built an exam meant to resist AI. Then he and a student each pointed a coding agent at it and walked away. One scored 10/10, the other 9/10. This is the move-by-move account of how Codex did it — including every mistake, and exactly how it dug itself out.

A companion to the workshop story · The AI-era capability test · 10 questions · 2 agents
Jaideep · ChatGPT Pro
10/10
Codex with browser access and a valid AIPipe token. Finished, recovered from three wrong guesses, and submitted.
Anand · ChatGPT Plus
9/10
Solved every hard technical question. Interrupted at 9/10 — the last point blocked by an invalid token, not by reasoning.

In the workshop, Anand opened Codex in front of a hundred students, typed four words — "Solve this exam" — and let it run in the background while he talked. It scored 9/10. A student named Jaideep, working from a slightly different angle, got it to 10/10. That much you already know.

This is the part the room never got to see: what actually happened inside those two runs. Both were recorded — every command, every tool call, every score the page handed back. Reading the logs is like watching two chess engines play the same opening and diverge. And the most surprising thing is not that Codex won. It's how it won.

"Codex did not solve the exam like a student reading questions and reasoning aloud. It solved it like a forward-deployed engineer. It found the question source, read the validators, generated local solvers, used browser automation to fill answers, used the live 'Check' buttons as feedback, and recovered from errors until the page accepted the answers."

— the post-hoc analysis of both runs

Hold onto that distinction, because it is the whole story. A chat model, handed an exam, tries to know the answers. A coding agent tries to operate the environment that grades them. The exam — ten questions, ten marks, each one explicitly designed to test an AI-era skill — turned out to be full of affordances an engineer could grab: source code, validators, error messages, downloadable files, external APIs, and a "Check" button that told you the truth on every click.

Two Prompts, Two Accounts

The two runs started from almost the same instruction — and the small difference in wording previews the whole personality of each agent.

Anand, on ChatGPT Plus, gave Codex effectively two words: solve the exam at this URL. Jaideep, on ChatGPT Pro, was a touch more procedural:

Jaideep's prompt to Codex
using @chrome visit `https://exam.sanand.workers.dev/2026-06-test` go through every question one by one then solve them till you get the correct answer then submit it

"Go through every question one by one… solve them till you get the correct answer." That phrase — till you get the correct answer — is the entire trick of a verifiable environment, handed to the agent as an instruction. It tells Codex to loop: try, check, repair, repeat. Neither prompt told it how to solve a single question. Both agents had to discover that for themselves. Both ran gpt-5.5 at the default "medium" reasoning effort.

It Read the Source Before It Read the Questions

The first thing Codex did was the thing a student would never think to do: it ignored the questions and went looking for the machinery behind them. The exam at exam.sanand.workers.dev isn't a static page — it's a client-side app that generates a seeded variant of each question per user, checks answers in the browser, and hides the grading logic in bundled JavaScript. To a chat model that's a wall. To a coding agent it's a filing cabinet.

codex · thinking out loud
The page is a client-side assessment shell rather than a static question sheet. It appears to prevent automated scraping of content. I'll inspect the network traffic and DOM structure to reach the questions…

I found no useful prior memory, so I'm proceeding from live state.

What it settled into was a loop you could write on an index card — and which the post-hoc analysis distilled into six steps:

The winning behaviour — closer to software engineering than to prompting
1. Read the source. # find the per-question validator 2. Extract the validator contract. # what EXACT input does it accept? 3. Write a small solver. # node/python to compute the answer 4. Use feedback. # click "Check", read the result 5. Repair precisely. # fix to the validator, not to taste 6. Save only after verification. # never submit an unconfirmed answer

This is the deep reason Codex was strong and a copy-paste chatbot was weak. The exam was stuffed with feedback — source code, validators, error strings, downloadable archives, an external game API, and a Check button that returned ground truth on every press. A chat model has to be right. An agent in a verifiable environment only has to become right. As Anand put it in the room: "Looping with feedback is possibly the most powerful technique that AI can use."

What It Cost: Roughly the Price of a Coffee

Before the move-by-move, the question everyone actually asks: how much did this cost? Both runs were on ChatGPT subscriptions, so neither put a number on the screen — but the logs record every token. Priced at gpt-5.5's standard API list rates, here is the bill for letting an agent sit a ten-question exam:

$2.66
API-equivalent cost of the 9/10 run (14.5M tokens)
$2.21
API-equivalent cost of the 10/10 run (10.3M tokens)
~96%
of input tokens were cached (billed at 1/10th)
~42K
output tokens — the thinking was almost all re-reading context

The shape of that bill is the surprising part. An agentic loop re-sends its whole working context every turn, so the runs burned 10–14 million input tokens — but ~96% of them were cached, billed at a tenth of the rate. Actual new output — the reasoning and code Codex wrote — was a mere ~42,000 tokens. Do the arithmetic and the whole exam costs about the price of a coffee: $2.66 for nine marks, $2.21 for ten. On a $20/month plan it barely dents the monthly allowance. (Anand stopped his run at 9/10 not because it was expensive in dollars, but because it was eating into his plan's included credits during a live demo — a quota worry, not a cost worry.)

Ten marks on a deliberately AI-resistant exam, for the price of a flat white. The expensive part of using a coding agent is not the tokens. It's the judgment to know when it's wrong.

— the economics of the two runs, in one line

Ten Questions, Ten Marks: The Move-by-Move

The exam's source manifest labels each question with the capability it tests — "Verifying AI-generated work," "Detecting hallucinations at scale," "Problem breakdown and efficient experimentation," and so on. Here's how Codex handled each, including where it tripped and how it got back up. Watch for a pattern: almost every failure was a wording or formula mismatch, and almost every recovery came from reading the validator's own source.

Q1Verify & fix AI-generated code
Capability · Verifying AI-generated work · 0.5 mark

A randomly chosen buggy JavaScript function — "most frequent element," "validate email," "flatten array," "compound interest." You must return JSON with the bugs, the fixed code, and a test strategy; the validator then runs your function against generated cases.

Jaideep's variant was "most frequently occurring element," where the bad code returned the largest value instead of the most common one:

the planted bug — returns the max, not the mode
function mostFrequent(arr) { let counts = {}, max = 0; for (const x of arr) { counts[x] = (counts[x] || 0) + 1; } for (const x of arr) { if (x > max) max = x; } // ← bug: tracks value, not frequency return max; }
Codex's fix — track the most frequent key
function mostFrequent(arr) { const counts = {}; let best = arr[0], bestCount = 0; for (const x of arr) { counts[x] = (counts[x] || 0) + 1; if (counts[x] > bestCount) { bestCount = counts[x]; best = x; } } return best; }

It identified the real defect, wrote the corrected function, and named edge cases (empty array, ties) in the test strategy.

✓ Passed first try · "practically free marks"
Q2Build a binary evaluation rubric
Capability · Designing reliable evaluations · 1 mark

Write exactly N yes/no checks that separate GOOD, MEDIOCRE, and POOR outputs. The validator then spends real money calling an AIPipe LLM judge many times over. This is the one question gated by a credential — and it's where the two runs split.

Jaideep's variant judged SQL query quality; he wrote checks for CTEs, null handling, joins, filtering, grouping. But the page had no token, so the judge couldn't run. Codex tried to inject one programmatically:

recovery — two failed injections, then the native prompt
globalThis.aiPipeToken = ""; // ✗ page global is locked window.aiPipeToken = ""; // ✗ still rejected // → Codex falls back to the page's own prompt() dialog and pastes it there ✓

Once the token went in through the front door, Q2 validated and Jaideep hit 10/10. Anand's variant judged API documentation quality; Codex wrote six clean output-only checks — method/path, content type, authentication, a concrete request example, success behaviour, specific error statuses. The rubric content was accepted. But the judge rejected the bearer token outright:

codex · the blocker, stated plainly
The rubric itself did not fail evaluation; the external AIPipe judge rejected its bearer token with HTTP 401 (JWSSignatureVerificationFailed). That is an authentication blocker outside the answer content.
⚠ Anand: blocked by an invalid token, not by reasoning · Jaideep: recovered → 10/10
Q3Repair a broken presentation prompt
Capability · Context engineering · 1 mark

A prompt is missing exactly three of six load-bearing components — audience, output format, data grounding, tone/style, length, action. The validator hunts for both the missing component labels and concrete detection phrases. Generic "make it a good prompt" repairs failed.

Codex stopped guessing and read the component matcher, then inserted the exact constraints it was scanning for — the seeded target metric forecast_error_pct, an "NYT graphics team" style, and a literal numeric length:

the kind of phrase the validator actually greps for
Audience: data-literate executives, not analysts Grounding: lead with the primary metric forecast_error_pct Style: in the style of an NYT graphics-team explainer Length: between 110 and 160 words

The lesson the analysis draws: context-engineering questions aren't about writing well. They're about preserving constraints exactly.

↻ Recovered by reading the validator's required phrases
Q4Reconcile the numbers in a data story
Capability · Evidence-grounded review · 1 mark

A table plus a paragraph with planted false figures. Fix only the wrong ones; leave the true ones — and a decoy — untouched. Codex regenerated the seeded data, computed the correct values, and found the planted errors (April units, February revenue-per-unit, a reversed May→June return-rate direction, July's online order share).

Then a beautifully brittle failure. The validator fingerprints answers by digit-substring. Codex's correct revenue figure $105,128 contains the substring 512 — which collided with the fingerprint for the July-share check and made a right answer read as wrong:

a correct number that trips a substring fingerprint
"...Q4 revenue rose to $105,128..." # contains "512" "...July online share reached 51.2%" # fingerprint: look for "512" # → the wrong claim matches the right number

Codex's fix was pure engineering, not reasoning: it reordered the paragraph so the July share appeared before the Q4 revenue value, breaking the collision. Even when the maths is right, validators can be brittle — and an agent that reads the feedback adapts anyway.

↻ Recovered by reordering the prose to dodge a fingerprint clash
Q5Repair a manipulated chart axis
Capability · Taste & quantitative validation · 1 mark

Broken Chart.js HTML with a deceptive axis — truncated, dual-scaled, inverted, or log-compressed. Submit a corrected chart, a distortion value within tolerance, and the exact words describing the manipulation. Codex's explanations were right in spirit but missed the literal phrase and syntax the grader wanted.

Jaideep's was a log-scale case; the validator wanted a literal axis type, not JSON-escaped:

Jaideep's variant — validator wants literal `type: "linear"`
scales: { y: { type: "linear" } } // not "type: \"linear\""

Anand's was an inverted y-axis that made declining satisfaction look like a rise. Codex read the accepted-phrase set in the source and dropped in the exact string:

codex · applying the exact accepted phrase
The remaining axis answer only needs one accepted phrase: "inverted axis flips decline narrative." Applying that exact description now.
↻ Recovered by matching the validator's exact phrase & syntax
Q6Find the one honest file among 1,000
Capability · Detecting hallucinations at scale · 1 mark

Download a ZIP of 1,000 Python files. Exactly one calls only real APIs; the other 999 each hide one to three hallucinated calls. Find the clean one. Codex did not read 1,000 files — it built a cheap static-analysis pipeline. But its first, naive allow-list was too permissive: subtle fakes like response.code, json.ParseError, or timedelta(weeks_days=7) slipped through.

The recovery is the most elegant move in either run. Codex realised every file is a mutation of the same template, so the genuine API at each position is simply the most common one across all 1,000 variants:

Codex's positional-mode pipeline (Python, paraphrased)
from collections import Counter # group the 1000 scripts by template, align mutable API positions position_votes = [Counter() for _ in range(num_positions)] for script in scripts: for pos, call in enumerate(api_calls(script)): position_votes[pos][call] += 1 # the real API at each position is the modal (most frequent) choice real_api = [votes.most_common(1)[0][0] for votes in position_votes] # the answer = the single file that uses the real API at EVERY position answer = next(s for s in scripts if all(c == real_api[p] for p, c in enumerate(api_calls(s))))
codex · isolating the answer
The positional modal-line analysis isolated script_666.py; it is the only file matching the dominant real API choice at every mutable position.

Jaideep's run reached the same answer for his seed (script_152.py) by evaluating the generator directly. This is exactly the scalable behaviour the question was built to reward.

↻ Recovered by replacing a brittle allow-list with positional voting
Q7Write a property test that finds the bug
Capability · Testing invariants · 1 mark

Write a Hypothesis property test that fails on a seeded buggy implementation but passes on the correct one. The validator runs it in Pyodide and checks whether the bug surfaces within 1,000 generated examples. Codex didn't hope random search would stumble onto the bug — it read the seeded scenario (the danger zone was "any window containing exactly one zero") and forced the counterexample:

a property test aimed straight at the risky region
from hypothesis import given, strategies as st @given(st.lists(st.integers(min_value=0, max_value=9), min_size=1)) def test_window_handles_single_zero(xs): # bias generation toward windows with exactly one zero assume(xs.count(0) == 1) assert rolling_metric(xs) == reference(xs) # fails on the buggy impl

Passed cleanly in both runs. Property-based testing turns out to be a great agent task: it converts a vague "find edge cases" into executable, randomised proof.

✓ Passed both runs — no significant mistakes
Q8Graph Detective — the limited-query game
Capability · Problem breakdown & efficient experimentation · 1 mark

The most agentic question. An external game gives an anchor node, a small query budget, and clues; you must name the compromised node and the shortest path to it, and submit a signed completion token. Codex opened the game, read its inline JavaScript, and discovered the API:

the game's hidden endpoints, found by reading the bundle
POST /detective/start # begin a session, get query budget GET /detective/node/{id} # inspect one node (costs a query) GET /detective/sample # sample the graph POST /detective/submit # submit node + path POST /detective/clear # ← the escape hatch

It scored each node against the clues — huge outgoing volume, very few transactions, a tiny in/out ratio, few counterparties, high average transaction size. Node 91 was unambiguous:

why node 91 was the culprit
node 91 → volume 24,000 · tx_count 3 · in/out 0.03 · counterparties 5 · avg_tx 9,480

Here Jaideep's run made its biggest blunder: it guessed too early — nodes 5, 49, 22 — and the game ends a session after three wrong guesses. The recovery was detective work in itself. Codex found that the failed state persisted by email and week, then probed the /detective/clear endpoint until it found the right arguments:

recovering a dead session, one parameter at a time
POST /detective/clear # ✗ needs a week POST /detective/clear?week=2026-W24 # ✗ needs the session token POST /detective/clear?week=2026-W24 + token # ✓ session reset — clean budget
codex · Anand's run, the clean finish
The Network Game is solved: compromised node 91, shortest path 13 → 72 → 91, and the server confirmed correct_node, path_valid, path_is_shortest. Loading the completion token directly into the exam.

Both runs converged on node 91 via path 13→72→91. This was the question that most exposed the gap between a chat model (which would reason vaguely) and a coding agent (which used APIs, state, graph search, scoring, and server feedback).

↻ Jaideep recovered a dead game via /detective/clear · Anand one-shot the final submit
Q9Optimise a prompt across four models
Capability · Robust optimization & model-aware prompting · 1.5 marks

The heaviest-weighted question. Given 21 prompt fragments with per-model scores and word counts, find the shortest set of fragment IDs that clears both a macro-mean and a per-model floor across four models. Anand's run made the single cleanest mistake of the exam: it treated the model scores as plain percentages, when the validator first passes them through a sigmoid.

the formula Codex first missed — found in the validator source
const sigmoid = (x) => 1 / (1 + Math.exp(-x)); // raw scores are LOGITS, not % // first solver: averaged raw scores → validator rejected the mean // fixed solver: sigmoid(score) first → exhaustive search → passes
codex · catching its own error
The robustness validator applies a sigmoid to raw model logits, so the first solver used the right subset search but the wrong score transformation. Correcting the formula and rerunning the checks.

With the right transform, the exhaustive search returned a valid minimal set (Anand's seed: I8, I11, I13, I17, I20; Jaideep's: I7, I9, I10, I14, I15, I19). The lesson the analysis underlines: optimisation is where AI is strongest when it writes code — and weakest when it guesses the maths.

↻ Recovered by reading the sigmoid in the source and re-running the search
Q10Fix a colour-encoding mismatch
Capability · Visual literacy & perceptual verification · 1 mark

Broken chart HTML with the wrong palette family. Explain the mismatch in an HTML comment and submit corrected HTML using the right encoding — sequential, categorical, or diverging. The validator checks perceptual properties: monotonic lightness, CIEDE2000 distances, diverging-midpoint lightness, and no rainbow.

Jaideep's variant was elevation around sea level — a textbook diverging case. Anand's was hospital wait times, where a categorical palette falsely implied each hospital was an unrelated bucket rather than a point on a fast→slow continuum. Codex's first explanation was too generic and failed; the fix required naming the exact false implication:

the explanation the grader demanded — name the false implication
<!-- Wait time is a continuous quantity (fast → slow), but the categorical palette implies each hospital is an unrelated category. Replace with a sequential palette so lightness increases monotonically with wait time. -->
↻ Recovered by naming the precise perceptual mismatch the rubric expected

Anand's run saved its verified result and stopped:

codex · the final save
The exam now validates at 9 / 10; only the rubric remains, blocked by the external judge's invalid token. Saving the nine independently validated answers plus the completed rubric text — Score: 9, confirmed as the official recent save.
Codex's Mistakes Weren't Random
Across both runs the errors clustered into five repeatable types — and the first three were almost always recoverable, because the environment gave feedback.
Type 1 · recoverable
Right in spirit, wrong to the validator
Q3 length phrasing, Q5 manipulation phrase, Q10 mismatch wording. Correct reasoning, failed an exact-string check. Fixed by reading the source.
Type 2 · recoverable
Wrong formula or interpretation
The Q9 sigmoid. Right search shape, wrong score transform. The validator's own code revealed the fix.
Type 3 · recoverable
Premature action
The network game's three early guesses burned the budget. Recovered only after finding the /detective/clear reset.
Type 4 · friction
Tooling & browser state
Duplicate DOM IDs, wrong tab, timeouts, locked page globals, failed text extraction. Slowed things down; rarely fatal.
Type 5 · NOT recoverable
Credentials & budget
Q2's AIPipe token. Jaideep got through with a valid one; Anand's failed with a 401. No amount of reasoning fixes a bad credential.

Pro 10, Plus 9 — But Not for the Reason You'd Think

It's tempting to read "10/10 on Pro, 9/10 on Plus" as "the pricier plan is smarter." The logs don't support that. Both ran the same model. Anand's run solved every hard technical question — the 1,000-file hunt, the network game, the sigmoid optimisation, the colour encoding. The single missing mark was Q2, blocked by an invalid AIPipe token: an authentication failure, not a reasoning failure.

"The fair conclusion is: agent environment quality and credential readiness mattered at least as much as model tier. Your run actually solved the hard technical questions. The missing point was not conceptual."

— from the post-hoc analysis

Jaideep had a clean Chrome flow and, crucially, eventually a working token. Anand's run carried more sandbox and tooling friction and hit a 401 on the one credential-gated question. The difference between 9 and 10 was a secret, not a thought. That is itself one of the workshop's lessons: in the AI era, a surprising amount of "can the agent do it?" comes down to "does the agent have the keys?"

What the Students Were Really Afraid Of

Back in Room 101, before any of this ran, Anand had asked the students what goes wrong when they hand a question to AI. Their answers weren't naïve — they were a precise map of every hesitation a thoughtful person has about delegating to a machine. The session logs answer almost every one of them, often in a way the students hadn't considered. Here they are, paired with what the runs actually showed.

Worry“AI hallucinates — on Excel, on calculus, on tables.”
True, and Codex hallucinated several times: a wrong score transform, premature guesses, generic visual explanations. But every one was caught — because the environment had a checker. The lesson isn't "trust AI." It's "give AI a checker." A coding agent in a verifiable environment converts hallucination from a silent error into a failed test it can fix.
Worry“AI needs context, and I don't know what context to give it.”
Codex's strongest single move was to fetch its own context. It never waited to be handed the ten questions — it inspected the DOM, downloaded scripts, read the validator modules, parsed the game's API. The better student prompt isn't "answer this question." It's "inspect the page, source, network calls and validators, then build a manifest of question IDs and constraints before solving."
Worry“Formatting and exact wording cause failures.”
Correct — and the most common failure type. Q3, Q5 and Q10 were blocked not by reasoning but by exact validator expectations. Codex recovered each time by reading the feedback and the source. Pass the error message back to the agent and watch it converge.
Worry“The creator knows we'll use AI, so they've made it AI-proof.”
This exam was designed for AI-era skills — but it was not agent-proof. The opposite, in fact: the more verifiable and tool-rich it was, the more it favoured agents. Live checkers, source scripts, a downloadable ZIP and an external game API all became affordances. A truly AI-resistant question would have to restrict feedback or require live human judgment — which also makes it harder to grade fairly.
Worry“A 45-minute exam, but Codex takes an hour.”
Real, and visible in the logs — both runs ran long, with multiple recovery loops. The answer the room reached: parallelise and triage. Run several agents, solve the cheap deterministic questions first, defer the expensive token-gated one, and never let a single hard question eat the clock. (Exactly the trap a TDS student described: 30 minutes spent wrestling context into question one, no time for the rest.)
Worry“Is the cost worth it?”
The logs put a number on it: about $2–3 of tokens for the whole exam, ~96% of it cached. The costs that bite aren't the tokens — they're the AIPipe judge calls, the retries, and the human's time supervising. One successful agent recovered nine-to-ten marks for the price of a coffee. The ROI argument gets stronger for high-stakes graded work — if you learn to stop runaway loops early.
Worry“Can I trust AI with my private data?”
The logs show both the risk and the guardrails. Codex's attempt to inspect browser/session state touched localStorage and was rejected as unsafe; tokens were redacted in the saved artifacts. But the run still needed a user-supplied token and generated email-bound credentials. Agents need keys to act — so draw an explicit trust boundary and hand over only what the task requires.
Worry“Will using AI make me less skilled? Do I still need to code?”
Codex used JavaScript, Python, shell, DOM automation, API calls, AST analysis, exhaustive search and graph search. The human wrote none of it — but needed enough judgment to know when the agent was plausibly wrong (the sigmoid), when a validator was brittle (the $105,128 clash), and when to stop (the 401). The skill shifts from hand-solving to supervising: from "produce every answer" to "catch the wrong ones." That's the same point Anand made about programming experience mattering more than memorising a language.

The Prompt That Should Have Been

Both runs started with two words. Both worked. But the analysis ends with the prompt that would have avoided most of the mistakes — and it reads less like a question and more like a brief to an engineer:

the better prompt — written for an agent, not a chatbot
Open the exam URL and solve it end to end. First INSPECT: page, source files, network calls, validators, scoring weights, seeded variants, external dependencies. Build a table of every question ID, expected answer format, validator rule, cost, and credential. Then SOLVE: deterministic/local questions first. For each answer, fill it, click Check, read the feedback, iterate until correct. Defer expensive token-gated checks to the end. For archives, write reproducible scripts instead of reading files. For games, inspect the API and keep state before submitting. Do NOT guess on limited-attempt tasks. Save only after the page confirms the score. Redact all tokens in logs.

Everything Codex eventually learned the hard way — read the source first, defer the credential question, never guess in the network game, save only after verification — is in that paragraph. The two-word prompt got 9 and 10. This one would have gotten there with fewer scars.

"The exam was not won by better prompting. It was won by better instrumentation. Read the source, extract the validator contract, write small solvers, use feedback, repair precisely, save only after verification. That is closer to software engineering than to prompting."

— the single most important finding of the analysis
What the Two Runs Teach
Ten questions, two agents, every mistake recorded — and a handful of lessons that outlast this exam.

"AI can attempt the exam. The human's job is to build, choose, and supervise the verification loop."
That, in the end, is the skill the whole workshop was quietly teaching.

— How Codex Took the Exam · IITM Paradox 2026