An AI Pair-Programming Workflow That Actually Works
AI pair programming works when you stop treating the model as an autocomplete engine and start treating it as a second engineer with a different failure mode. The failure mode is confidence: a model will hand you a plausible-looking diff whether it understood the problem or not. A workflow that actually works builds in the checks a human pair partner would provide naturally, catching bad assumptions before they become a merged bug. This article walks through that workflow end to end, using terminal-based agents like Codex CLI, Claude Code, and similar tools as the reference implementation.
Why most AI pairing sessions fail
The typical failure looks like this: a developer opens a chat panel, types "add rate limiting to the login endpoint," gets back a diff, skims it, and merges it. Ten minutes later, someone in code review points out the rate limiter uses in-memory state that resets on every deploy, so it does nothing in production. The model was not wrong about rate limiting. It was wrong about the deployment topology, because nobody told it the app runs on three autoscaled instances behind a load balancer.
This is not a model-capability problem. It is a context and process problem. Human pair programming works because the navigator constantly injects context the driver does not have: "we tried that last month and it broke prod," "check the ticket, it says session-based not IP-based," "that function is called from four places, grep first." AI pair programming fails when you skip the navigator role entirely and let the model drive alone.
The fix is a workflow with four stages: framing, planning, driving, and review. Each stage has a specific job, and skipping any one of them is where bugs creep in.
Stage 1: Frame the problem before you open the tool
Before you type a single prompt, write down three things in a scratch file or the ticket itself:
- The actual constraint (not the feature request). "Add rate limiting" is a request. "Prevent more than 5 failed logins per account per minute, survives restarts, must not add a new infra dependency" is a constraint.
- What "done" looks like, concretely. A test that passes, a curl command that returns a specific status code, a metric that shows up in a dashboard.
- What the model cannot know. Deployment topology, the existence of a similar utility three files away, an incident from last quarter, a compliance requirement.
This takes two minutes and it is the single highest-leverage step in the whole workflow, because it is the only stage where you are not competing with the model's tendency to sound confident. If you skip framing, you end up debugging the model's assumptions instead of your problem.
Stage 2: Ask for a plan, not code
Every terminal AI agent worth using today (Codex CLI's /plan or planning mode, Claude Code's plan mode, similar features in other tools) supports a read-only planning pass before it touches files. Use it every time, even for changes that feel small.
A concrete example with Codex CLI:
codex
> /plan
Add per-account login rate limiting: 5 failed attempts per account
per 60 seconds, backed by Redis (already used for sessions, see
lib/redis.ts), returns 429 with Retry-After header. Must not
regress the existing login test suite in tests/auth/login.test.ts.The model comes back with a plan: which files it will touch, what new code it will add, what existing tests it might break, and any assumptions it is making. This is where you catch the wrong turn before it costs you a diff review. If the plan says "I will store attempt counts in a module-level object," that is your signal to correct course immediately: "no, this runs on multiple instances, it has to be Redis, see lib/redis.ts for the client."
Reading a plan takes thirty seconds. Reading a diff and realizing the architecture is wrong, then asking for a rewrite, takes fifteen minutes and burns your patience along with your context window.
Stage 3: Drive in small, reviewable increments
Once the plan looks right, let the agent implement it, but do not ask for the whole feature in one shot if it touches more than two or three files. Break it into commits you can actually review:
> Implement step 1 only: the RateLimiter class in lib/rate-limiter.ts
with the Redis-backed sliding window logic. No wiring into the
login route yet. Add unit tests.Review that diff. Run the tests. Then:
> Now wire RateLimiter into the login route in routes/auth.ts.
Return 429 with a Retry-After header when limited. Add an
integration test that hits the route 6 times.This is the driver/navigator split translated to an async workflow. The model drives (writes code), you navigate (review each increment before the next one starts). The reason this matters more with AI than with a human pair is that a human pair partner course-corrects mid-sentence when they sense confusion. A model does not; it will happily build twelve files on a wrong premise and hand you the whole thing at once if you let it. Small increments cap the blast radius of any single wrong assumption.
A useful terminal habit: keep a second pane running git diff --stat so you can see file-by-file scope creep as the agent works, without waiting for it to finish.
Stage 4: Review like the model is a junior engineer with no memory
The review gate is where AI pair programming earns its keep or loses it. Treat every diff from the agent as you would from a junior engineer on their first week: competent at syntax, unreliable on judgment calls, and with zero memory of the incident that taught your team why a certain pattern is banned.
A checklist that catches the recurring failure modes:
- Does it handle the concurrency reality of your system? Models default to single-process assumptions unless told otherwise. Rate limiters, caches, and counters are the usual victims.
- Did it invent an error path? Ask "what happens if Redis is down here" out loud, then check if the code answers that, or if it will throw an unhandled exception in production.
- Did it silently change behavior outside the ticket? Diff review, not just the new code. Models sometimes "helpfully" refactor an adjacent function while they are in the file.
- Are the new tests actually testing the constraint, or just testing that the code does what the code does? A test that mocks Redis and asserts the mock was called proves nothing about the sliding window logic being correct.
- Would this pass review from the person on your team who is most skeptical of AI-written code? If you cannot picture their objection clearing, do not merge yet, ask the model to address it directly.
Run this checklist as a literal prompt back to the agent when you are unsure:
> Before I merge this, walk through what happens if the Redis
connection drops mid-request in the rate limiter. Does the
login route fail open or fail closed? Is that the right choice
here?Models are often better at auditing their own code against a specific question than at generating it correctly the first time unprompted. Use that asymmetry.
Putting it together: a repeatable loop
Here is the shape of the workflow condensed into steps you can actually run every time:
- Write the constraint, the definition of done, and what the model cannot know. Two minutes, in the ticket or a scratch file.
- Open a plan-mode session and describe the constraint, referencing existing code by path where relevant.
- Correct the plan if it makes a wrong architectural assumption. Do this before any code is written.
- Implement in increments of one logical unit each (one class, one route, one migration).
- Review each increment with
git diffbefore requesting the next one. - Run the test suite after each increment, not just at the end.
- Before merging, ask the model to walk through the failure paths out loud.
- Read the final diff once more as a whole, not just increment by increment, checking that the pieces compose the way you expect.
This loop is slower than "prompt, accept, merge" for any single change. It is faster over a month, because it eliminates the class of bug that comes from an unreviewed confident wrong answer, the kind that ships, gets discovered in production, and costs an incident review plus a rollback plus a postmortem. The workflow trades a few minutes of navigator discipline per change for avoiding that tax.
Making the tool part of your terminal habits
The workflow above works with any terminal-based coding agent, but it works best when the tool is wired into habits you already have rather than being a separate app you context-switch into. A few practical patterns:
- Keep the agent session running in a dedicated terminal tab or tmux pane alongside your editor, not in a browser tab you have to alt-tab to. The friction of switching contexts is where people skip the review step.
- Point the agent at your actual test command early in the session ("run
npm test -- tests/authafter each change") so it self-checks instead of you manually running tests after every increment. - Keep a running log of corrections you had to make ("it defaulted to in-memory state, told it to use Redis") for a week. Patterns in that log tell you what context is missing from your project's README or agent instructions file, so the next session starts smarter.
- If your tool supports a persistent instructions file (an
AGENTS.md, aCLAUDE.md, or equivalent), put the deployment topology, the multi-instance constraint, and the "check lib/redis.ts before adding new state" rule there once, so you stop repeating it every session.
That last point compounds. The first ten sessions with any AI coding tool involve a lot of "no, we do it this way here" corrections. Capture those corrections in a project instructions file and the tenth session starts already knowing what the first session had to learn the hard way.
FAQ
Does this workflow slow down simple changes, like fixing a typo or a one-line config value? No, and it should not. The four-stage loop is for changes with real design decisions: new state, new endpoints, concurrency, external services. A one-line fix does not need a plan step. Use judgment on scope; the workflow exists to prevent wrong assumptions on non-trivial work, not to add ceremony everywhere.
Which terminal AI agent should I use for this, Codex CLI, Claude Code, or something else? The workflow is tool-agnostic; it describes a process, not a product. Codex CLI, Claude Code, and comparable terminal agents all support a planning or read-only mode, incremental diffs, and instruction files, which are the three features this workflow depends on. Pick based on which one integrates best with your existing terminal and editor setup, not based on which one claims better benchmark scores.
How do I stop the agent from making sweeping changes across many files when I only asked for one thing? Say so explicitly in the plan step: "touch only these files" or "implement step 1 only, do not wire it in yet." Models default to being thorough, which reads as helpful but produces oversized diffs. Scoping the request narrowly at the plan stage is far more reliable than trying to trim an already-generated diff after the fact.
Is it worth writing tests myself instead of asking the agent to write them? Write the test that defines "done" yourself, or at minimum review it line by line if the agent writes it, because a test written by the same session that wrote the implementation can quietly test the implementation's own assumptions rather than the actual requirement. For everything else, letting the agent draft tests and then reviewing them against your definition of done from stage 1 is a reasonable split of labor.
What is the single biggest mistake people make with AI pair programming? Skipping the plan step and going straight to "implement this feature," then reviewing the resulting diff as if reviewing were the only checkpoint. By the time a full feature is implemented, a wrong architectural assumption is baked into every file, and correcting it costs a near-total rewrite. Catching the same assumption at the plan stage costs one sentence.
Does pairing with AI replace human code review? No. It changes what human review is for. If you follow this workflow, human review should rarely catch "this is architecturally wrong," because that gets caught at the plan stage. Human review is still where domain judgment calls happen: whether the product behavior is right, whether the tradeoff matches team conventions, whether this is the right problem to be solving at all. Do not skip human review because an AI reviewed itself; the two catch different things.
AI CodingShip full-stack AI apps at conversation speed — specs, agents, deploys, all from the terminal.
CodexLearn to drive OpenAI's coding agent: real tasks, safe sandboxing, and terminal-to-cloud workflows that ship.