If you use Codex Desktop, you have probably noticed the gray ghost text that appears in your composer input right after the model finishes an answer. You look at what the agent just built, reach down to type "wait, the cancel button doesn't work when offline" or "consolidate those three buttons into one action", and notice that the exact sentence is already sitting there in gray letters waiting for you to press Tab.
The first few times it happened, it felt like mind reading. Then I checked how it was implemented. When an agent can guess your reaction before you type it, the obvious engineering question is: why am I still the one running the test, critiquing the UI, and steering the design?
I packaged this workflow as an open-source skill (simulate-user on GitHub) for Claude Code, OpenAI Codex, and Google Antigravity. Here is how reverse engineering that ghost text turned into a full-cycle development accelerator.
Reverse-engineering ghost text in Codex
Inside the Rust engine behind Codex (codex-rs), next-prompt prediction is not powered by a separate, small autocomplete model running in the background. It is powered by the exact same frontier model you are chatting with, using an elegant architecture trick.
The engine takes a snapshot of the current conversation history (ForkSnapshot) and launches an ephemeral background sample. Crucially, it sets disableMcp: true and strips out all tool declarations. Because the prompt prefix is byte-identical to the conversation that just completed, the inference request hits the server's KV prompt cache at roughly a 99% hit rate. The model generates the suggested prompt in a few hundred milliseconds at near-zero marginal cost.
// Conceptual engine pipeline in codex-rs
let fork = conversation.fork_snapshot();
let prediction_stream = fork.sample(SampleConfig {
disable_tools: true,
schema: json_schema!({
"type": "object",
"properties": {
"suggestion": { "type": ["string", "null"] }
},
"required": ["suggestion"]
}),
interceptor: SampleInterceptor::abort_on_tool_call(),
});
Two small design details make this work in practice:
- The null abstention valve: The schema explicitly permits
suggestion: null. If the agent just performed an unambiguous one-step request and there is no obvious follow-up, the model returnsnull. It does not force a suggestion just to fill the box. - Natural preference logging: Every time you submit a prompt, the client records whether you pressed Tab without changes (
accepted), edited the ghost text (edited), or ignored it completely and typed your own words (overridden). It quietly builds preference pairs out of ordinary daily usage.
Why naive "critique your own code" fails
Most developers who try to automate code review with AI try one of two patterns, and both break down quickly:
1. Author blindness. You tell the same agent that wrote the code: "Now review your changes and tell me what is broken." The model looks at its own work and recites the exact same assumptions it had when writing it. If it forgot that an API throws on an empty array, it will forget that in its self-review too.
2. Forced nitpicking. You tell an agent: "Perform 3 rounds of adversarial critique." By Round 3, every real bug has already been discussed, but the prompt forces the agent to keep talking. It starts inventing busywork: renaming private helper functions, suggesting extra TypeScript interfaces that wrap single strings, or adding docstrings to self-explanatory five-line functions.
The predictive development & verification architecture
To turn prompt prediction into a reliable autonomous review cycle across Claude Code, Codex, and Google Antigravity, I built an adversarial verification loop around five rules that govern both design decisions and code quality.
Builder & Designer turn
Implements UI layout, features, or architectural refactors in an isolated workspace. Runs standard build and lint checks.
Simulated User Critic
Tool-disabled fork evaluates candidate diff. Predicts human reactions: UI/UX critique, scope calls, or negative-state QA.
Autonomous iteration & tests
Refines layout hierarchy, fixes friction, and executes headless test hooks. Loops back to builder until passing or abstaining.
1. An isolated cross-model critic
The simulator never runs inside the builder's conversation context. When a builder model (for example, Gemini in Antigravity or Claude in Claude Code) finishes writing code or designing a layout, an independent critic model inspects the commit diff. Using a different model family breaks author blindness: Claude often spots the race conditions or awkward desktop button groupings that Gemini glossed over, and Codex spots edge cases in shell pipes and mobile wrapping that Claude assumed were safe.
2. Upgrading ghost text with real verification hooks
Codex's composer predictor has a strict one-second timeout and cannot touch any tools. But an asynchronous development loop has plenty of time. When the critic predicts a human complaint, it does not just return a complaint string; it is required to attach an executable verification check or DOM inspection test.
{
"action": "pushback",
"predicted_human_reaction": "Deleting a file leaves an empty card in the UI",
"verification_check": "node tests/mobile.cjs http://localhost:4173",
"expected_failure": "assert.equal(cards.length, 9)"
}
The runner executes that exact command in a headless environment. If the check passes (meaning the predicted complaint was a false alarm), the prediction is discarded. If the check fails, the runner passes the reproduction command and the failure output directly back to the builder agent for another fix cycle.
3. The four-way decision contract
Every prediction must resolve to one of four explicit actions:
| Action | Condition | Next step |
|---|---|---|
pushback |
A concrete defect, layout overflow, stale control, or verified regression. | Dispatches critique and error log to builder agent for fix round. |
suggest |
The code works, but an obvious UI polish or small follow-up exists. | Queues a non-blocking suggestion for review. |
abstain |
The build is clean, tokens match, tests pass, and no regression is found. | Terminates the loop immediately. Zero nitpicking. |
needs_real_user |
Sensitive gates (billing, emails, hardware, releases) or open taste ties. | Hard stop. Presents verified options and waits for approval. |
The abstain action is what makes the loop usable. In testing across 140 autonomous tasks, introducing an explicit abstention valve dropped false-positive critique loops from 38% down to under 4%. The moment the work is solid, the critic returns prediction: null and exits.
When high-level aesthetic choices are genuinely open, rather than guessing forever, the critic directs the builder to prepare two or three clean mockups (A/B/C at desktop and phone widths with one recommended direction) and pauses with needs_real_user so the human makes the final taste call.
4. Cold-start seeding from local conversation transcripts
Teaching an LLM how you think sounds like it requires weeks of prompt tuning. In reality, every developer who uses AI coding tools already has their personal training dataset sitting on their hard drive.
Between ~/.claude/projects, ~/.codex/sessions, and Antigravity logs, I had over 2,800 real conversation transitions formatted as [Assistant finishes turn] → [Daniel replies]. Extracting those transitions into structured examples revealed five clear buckets of instincts:
- UI/UX and design critique: "Table clips on my phone; stack it into one column and reuse our house tokens" or "Consolidate those three buttons into one action."
- Negative-path verification: "What happens if the network drops halfway through?" or "Did you verify this doesn't steal window focus?"
- Friction removal: "Make those terminal commands 1-click execution pills so I don't have to copy-paste."
- Scope discipline: "Work in smaller steps" or "Don't build a new database migration for this, just read the existing JSON file."
- Direct execution: "Give me which slot to plug this into now without the explanation."
By pinning twelve concrete, structured examples at the top of the critic's system prompt, the model adopts your review priorities without needing a fine-tuned model.
5. The local calibration loop
Whenever the system finishes an autonomous run and hands the result to me, it logs my actual response:
- Hit: If I accept the work without complaint, the critic's
abstainverdict was correct. - Edited: If I run a slightly different test or tweak spacing, the test runner updates its fixture library.
- Overridden: If I spot a design glitch or bug that the critic completely missed, that exact transition becomes a new few-shot seed example in the critic's prompt file.
What this changes in practice
Before using predictive simulation, working with AI agents had an exhausting cadence: the agent would announce it was done, I would open the browser, spot a clipped table on mobile, find an obsolete Delete button left behind after deletion, copy the console error, type three paragraphs of critique into the chat, and wait. The human was the slowest link in the iteration harness.
With predictive simulation running in the background, by the time my phone buzzes or the terminal rings to tell me an agent is finished, the critic has already caught the missing mobile breakpoint, consolidated the cluttered controls, verified the negative cancel path, and handed it back to the builder. The agent arrives with finished, verified work because it already iterated with a simulated version of my brain before I ever arrived.