All posts

/7 min/Moffat Munene

Pair-programming with AI, for working engineers

AI as the partner you always wanted, but only if you keep doing the parts of programming that make you better. The patterns we use, the failure modes we have learned to spot, and the rules we put around the agent. Post 8 of the AI Foundations series.

  • #ai
  • #foundations
  • #coding
  • #pair-programming

If you write code professionally, AI changed the job in 2023 and has not stopped since. The shape of "good engineer" in 2026 includes fluency with AI tools the same way it includes fluency with version control. You can ignore it. You will be slower than the people who do not.

This post is what we have learned about working with AI in actual production engineering work, after about two years of full-time use across a small team shipping Laravel, Vue, Python, and infrastructure. Not best practices in the abstract. The habits that survived contact with reality.

What AI is now legitimately good at

The capability surface has shifted. Things that were hard a year ago are routine now:

  • Refactors at scale. "Rename this concept across the codebase, update all the call sites, update the tests." Used to be a half-day. Now it is a 10-minute review of an agent's diff.
  • Translating between languages and frameworks. A Python script becomes a PHP artisan command in 30 seconds. A REST API becomes a GraphQL one. The model is fluent in everything.
  • Bug hunting with hints. "I am seeing X, the stack trace is Y, this commit caused it" gets you a first guess in 15 seconds. Right about half the time, but right enough that it short-circuits 30 minutes of investigation.
  • Filling in known patterns. Adding a new admin CRUD page to a Laravel + Inertia codebase. Writing a migration for a known schema change. Generating Pest tests for an existing class. Repetitive plumbing the model has seen ten thousand examples of.
  • Code review. Paste a diff. Ask "what would you push back on". Two-thirds of the comments are useful. The other third is noise. Worth the speed.

What is still hard:

  • Novel architectural decisions. "Should we use event sourcing for this domain?". Models give plausible-sounding answers that hide a lack of grounding in your specific constraints.
  • Performance work. Profiling, hot paths, memory leaks. Models hallucinate fixes that look right and do not measurably help.
  • Security-critical code. Crypto, auth, payment flows. Use AI for boilerplate; do not trust it for the security decisions.
  • Legacy code in a codebase the model has not seen. Long-context models help here but it is still where AI struggles most.

The patterns we use

Pattern 1: describe the change, let the agent draft

For 80 percent of work, the loop is:

  1. Tell Claude Code or Cursor what you want, with specifics: the file, the behaviour, the constraint.
  2. Read the diff before accepting.
  3. Run tests.
  4. Fix anything weird, either by editing the code or describing the next change.

The "read the diff before accepting" step is non-negotiable. Models confidently produce code that compiles, looks reasonable, and is wrong in ways that only show up in production. The diff review is your only checkpoint.

Pattern 2: tests first

Two flavours:

  • Write the test, ask the agent to make it pass. Forces you to specify the behaviour. Gets you a working implementation in a few iterations. Has the side effect of leaving you with a real test, which is worth more than the implementation.
  • Ask the agent to write tests for existing code. Useful when you are about to refactor untested code. The agent reads the function, generates Pest cases for the obvious paths, you review and fix the wrong ones. Beats the alternative of refactoring blind.

Pattern 3: explain code before changing it

When you do not understand a piece of legacy code, paste it into a chat with:

Explain what this function does, in plain English. Walk through the code path for [a specific input]. Flag any non-obvious behaviour or potential bugs you notice along the way.

The explanation is not always right. It is right often enough to give you a foothold, and the parts that are wrong tend to be the interesting parts (the model misread a subtle assignment, which is exactly the kind of thing the original author also messed up).

Pattern 4: agent for grunt work, human for design

The clearest division: the agent does the work that has a "right" answer (boilerplate, repetitive transforms, known-pattern wiring). The human does the work that has a "best" answer (architecture, naming, design trade-offs).

When you blur the line, you get problems. When you keep it, you get a 3x speedup on the grunt work and your design quality stays the same.

Pattern 5: agent for code review, before humans

Before opening a PR, run the diff through Claude or ChatGPT with:

Review this diff as a senior engineer reviewing a junior. Be direct. Flag bugs, security issues, unidiomatic patterns, missing edge cases, missing tests. Focus on substantive issues, not style.

The agent catches roughly half the things a careful human reviewer would. Get those in before the human looks at the diff. The human now spends their attention on the things AI cannot catch (taste, naming, fit with the rest of the codebase).

Failure modes we have learned to watch for

Confident wrong refactors

The agent renames the variable correctly in 18 of 20 places, then quietly inserts a typo in the 19th. The 19th is the file you do not run tests on. Production breaks at 3am.

Mitigation: do not skip running the full test suite on AI-touched code. Use type checking aggressively. Use feature flags so you can revert without redeploying.

Hallucinated APIs

The agent uses a Laravel facade method that looks right but does not exist. Or an npm package that has been renamed. Or a Python stdlib function from a different library. The signature is plausible. The function is fictional.

Mitigation: when the agent uses an unfamiliar API, look it up in the docs before merging. The clue is usually that the agent is "very confident" about a method that you, the experienced engineer, do not recognise.

Subtle test corruption

The agent fixes a failing test by weakening the assertion instead of fixing the bug. The test now passes; the bug is still there.

Mitigation: when an agent fixes a test, read the diff of the test, not just the diff of the implementation. If the assertions got weaker or the input got narrower, the agent took the easy way out.

Codebase contamination

You let the agent generate a pattern that becomes the de-facto convention for new code in that area, even though it is wrong, awkward, or inconsistent with the rest of the codebase. Six months later you have a wing of the codebase that "feels off" and nobody can quite say why.

Mitigation: take seriously the first time the agent produces something that smells. Push back. Refactor. Otherwise you are training the agent (and your future self via the codebase) to keep producing it.

Rules we put around the agent

These have emerged from experience, not principle:

  1. No agent commits to main. All agent-generated changes go through PRs and CI like any other change.
  2. No agent runs destructive commands without confirmation. rm, git reset --hard, migrate:fresh, DROP TABLE. The agent suggests, the human runs.
  3. No agent edits production secrets. Even with the right permissions, the agent does not touch .env.production or its equivalent.
  4. No agent changes critical safety code without a human pair. Auth, payments, billing, the things where a bug is reputationally expensive.
  5. No agent skips the test suite to merge faster. Tests are non-negotiable. Slow CI is solvable with parallelisation, not with skipping.

These are not things the agent will violate intentionally. They are things you need to be intentional about so the speed of the agent does not outpace your review capacity.

The skill that does not get worse

A worry we hear: "If the agent writes most of my code, do I get worse at programming?". Honest answer: yes, at some skills, no, at others.

Skills that erode if you let them: writing boilerplate from memory, recalling exact API signatures, building muscle memory in any one editor or framework.

Skills that compound: reading code, designing systems, debugging from telemetry, knowing what a good solution looks like, code review.

The second list is what makes someone senior. The first is what gets a junior unstuck. Letting AI handle the first list, deliberately, while pushing yourself harder on the second, is the sustainable path. The engineers we see plateau are the ones who use AI to avoid the second list, not just the first.

What this stop on the roadmap sets up

You can now use AI competently in your daily flow. The next track shifts to what AI is bad at: failure modes, hallucinations, prompt injection. Knowing the boundary of your tools is what separates "I can use this" from "I can use this safely".