← Blog

melik@london:~/blog$ cat shipping-code-with-ai-coding-agents.md

What I've learned shipping production code with AI coding agents

For most of this year, almost all of the code I've shipped at work has been written by AI coding agents, Claude Code and Codex. These weren't demos or side projects. First it was a production API platform for scientific data, and now I'm back in identity and access management, where mistakes cost more. I still own every line that merges. The agent types, but I'm accountable.

This isn't a hype piece. It's what has actually worked for me, what hasn't, and the habits I've had to build to stay honest about the difference.

Start from a specification, not a prompt

Agents are very good at writing code that matches something. They're much less reliable at deciding what that something should be. If you hand an agent a vague requirement, it will fill the gaps confidently, and often plausibly, but not always correctly.

So I don't start with a prompt. Requirements usually arrive as documents or spreadsheets, and the first step turns them into a structured specification. Code generation only starts once the spec exists, and the spec becomes the contract between me, the agent and the reviewers. A few things matter more than I expected:

I package this kind of procedure as a skill, a small, versioned instruction file the agent loads when the task matches. A stripped-down skeleton looks like this:

---
name: requirements-to-spec
description: Use when turning a written requirement into a service
  specification, before any code is written.
---
# Requirements to spec
1. Read the source requirement. Quote each statement you rely on.
2. List every ambiguity as an open question. Do not invent answers.
3. Write the spec: operations, request and response shapes,
   error cases and non-functional constraints.
4. Map each requirement to the spec section that covers it.
5. Report uncovered requirements and open questions, then stop.

Treat skills like code, including deleting them

Skills beat long, copy-pasted prompts because they're reusable, reviewable and versioned. But they aren't free: every skill an agent can see takes up a little of its context, in every session.

At one point I had far more skills installed than I actually used. Instead of guessing, I went through my session logs, counted which skills had really been used, and removed or hid the rest. A skill is code, and code that's never called is just weight.

Run agents in parallel, but give each one a lane

The biggest change to my day was running several agent sessions at once, each owning one ticket or one pull request. That works well, with two conditions:

Parallelism raised my throughput. It also moved the bottleneck, which turned out to be the most important lesson of all.

Review is where the defects are found

When I looked back at where the real defects in agent-written code were caught, most weren't caught by unit tests. They were caught in review: a separate agent pass over the diff, automated pull-request review, and human reviewers.

The kinds of problems differed too. Review found the issues tests aren't designed for: unsafe handling of untrusted input and credentials, drift from the published API contract, concurrency mistakes, and resilience gaps such as retries that never stop or deadlines that don't actually bound anything. Tests mostly caught logic errors in freshly written code.

So review gets the most structure. My multi-agent review loop looks roughly like this:

  1. A separate agent session reviews the diff with fresh context, never the session that wrote the code.
  2. Every finding is verified against the code before it goes anywhere. Review agents and bots produce false positives, and relaying them unchecked wastes everyone's time.
  3. Findings are triaged as blocking or non-blocking.
  4. Earlier review threads are read first, so we don't raise duplicates or quietly reverse points already settled.
  5. Nothing outward-facing is posted unseen. I read the draft of every PR comment and ticket update before it goes out.

The core of the review instruction is short:

Review this diff for correctness, security and contract drift.
For each finding: file, line, what breaks, and a concrete input
that triggers it. Mark it blocking or non-blocking.
Do not report style unless it hides a bug.
If you cannot construct a failing scenario, say so and drop it.

That last line matters most. It turns "this looks risky" into something you can check.

Tests still matter, for a different reason

None of this makes tests pointless. Their role just changes.

There's also a cost to admit: agents write wrong tests too. Debugging an agent's test is real work, and it's easy to overlook when you're counting how many tests were written.

What doesn't work

The habits that keep it honest

If I had to reduce this to a checklist:

Where this leaves the engineer

My job has moved from typing code to specifying, reviewing and deciding. That doesn't make it easier. Judgement matters more when the code arrives faster than you can read it. The engineers who'll do well with these tools are the ones who are clear about what they want, who check carefully, and who stay accountable for what merges.

I drafted this article with an AI agent and edited it myself.