AI-native delivery

What Building Production Software With Coding Agents Actually Requires

An honest account of AI-native delivery: the domain work, the specification, the adversarial review, and the operating discipline that surround the part where an agent writes the code.

Coding agents are very good now. That is the least interesting sentence anyone can write about them, and it is where most discussion of the subject stops.

What follows is the other part: an account of what a delivery model actually needs around a coding agent for the output to survive contact with production. I define product behaviour, requirements, acceptance criteria, and operating constraints, and I direct coding agents through implementation, review, testing, and production validation. I do not position myself as the engineer who manually authored the production code. That division is the premise of this piece, not a disclaimer at the bottom of it, because the whole argument is about which side of it the difficulty lives on.

Short version: the agent removed the typing. It did not remove the deciding, and it moved a large amount of work forward, into the part of the process where a mistake is cheap and invisible rather than expensive and live.

The part that is not the code

Take a feature from “someone wants this” to “this is running and nobody is being harmed by it” and enumerate the distinct activities:

understanding the operational reality the feature exists in · deciding whether the feature is the right response to that reality · learning the domain well enough to know what correct looks like · specifying the behaviour precisely enough to be wrong · enumerating the ways it will quietly fail · writing the code · reviewing the code against the specification rather than against itself · designing tests around business consequences · deploying · watching what actually happens · turning what happened into something the system remembers.

One of those is generation. Coding agents have made that one dramatically faster and substantially better. Every other item on the list is either unchanged or has become more important, because the rate at which unreviewed decisions can now enter a codebase has gone up by an order of magnitude, and the bottleneck moves to whoever is deciding.

The uncomfortable implication is that AI-native delivery does not reduce the amount of thinking required. It removes the natural pacing that used to distribute that thinking across the days it took to type everything out.

Domain understanding comes first

The systems I work on are pay-per-call marketplace infrastructure: real-time bidding on inbound calls, routing, attribution, settlement, publisher payout, and the financial operations that close the month. Every requirement I write starts from something I have personally had to do inside that business — reconcile a statement a partner disputes, work out why a call that should have routed did not, explain to a buyer why their conversion count disagrees with ours.

That is not biographical colour. It is the mechanism by which requirements become correct.

Consider a single question: what counts as a conversion? An agent handed “bill the buyer when the call converts” will produce something plausible and self-consistent within an hour. To know whether it is right, you need to know that the qualifying condition is contractual and varies per buyer, that a term can be a duration threshold on connected talk time rather than on the call leg, that “connected” itself has more than one defensible definition in a telephony stack, that the same event has to drive both the buyer invoice and the publisher payout so those two can never be computed independently, and that a mismatch between the term recorded on the contract and the term used at settlement will not produce an error — it will produce a set of numbers that reconcile perfectly and are wrong in one direction. (That last one is not hypothetical; it is a specific incident and it cost real money.)

None of that is in the prompt unless someone knows to put it there. A coding agent will make a reasonable decision on every ambiguity it encounters. Reasonable is not the standard. The standard is the one the contract says, and there is nothing in the training data of any model that contains the contract.

Research until you can challenge the implementation

The rule I hold myself to is deliberately phrased in the negative: if I do not understand how a feature should work, I cannot responsibly evaluate what an agent produced.

This is a higher bar than it sounds, and it is not the same as being able to read the code. A generated implementation is legible almost by construction — clear names, sensible structure, a docstring explaining its own reasoning. Reading it back is a poor test, because the thing you are reading is a fluent explanation of whatever it happens to do. Fluency is not evidence, and the comment always agrees with the code.

So the research goes until I can do something specific: state, in advance, what the implementation must do at the boundaries, and then check whether it does that. What happens on a duplicate webhook. What happens when a provider returns a status the integration guide does not document. Which of two systems wins when they disagree about the same call, and why that precedence is the right one commercially rather than merely a choice. What the tenancy rule is for this table, and whether the query respects it.

I do this research with AI as well — it is very good at accelerating the reading of a provider’s API surface, at surfacing the edge cases in a protocol, at arguing both sides of a design question. The difference is what happens to the output. Research assists a decision I then own and can defend. It does not substitute for one.

Specification: the artifact that does the actual work

Before implementation starts there are four artifacts, and their order is not arbitrary.

The PRD says what the product must do, for whom, and what business condition it exists to change. It is short and it is mostly about the condition. A feature whose PRD cannot name what changes operationally when it ships usually should not be next.

The requirements state system behaviour precisely enough to be wrong — endpoints, parameters, states, transitions, timing, tenancy, idempotency, and what each integration is permitted to assume about the others. The test of a requirement is not whether it sounds right. It is whether a reader could point at a specific sentence and say “that is not what the system should do.” Prose that cannot be contradicted has not specified anything.

The acceptance criteria are the observable conditions under which the work is finished, written before implementation begins. Written afterwards, “done” is negotiated against whatever was produced, and the answer is always yes.

The failure modes are the ways this feature can be quietly wrong in production, each one assigned to whichever of the tests, the alerts, or a human control is responsible for catching it. This document is the one that most repays the effort. It is also the one an agent cannot write for you, because it requires knowing what has actually gone wrong in this business before.

Specification is where the leverage moved to. It is unglamorous, it produces no running code, and it is now the highest-value hour in the process — because it is the last point at which a wrong decision costs nothing to change.

What the agent actually does

Given the above, the agent implements: the Go services and handlers, the TypeScript and React front-end, the SQL and the migrations, the test suites, the observability wiring. It is fast, it is consistent with the surrounding conventions, and its first attempt is usually structurally sound.

It is also, reliably, wrong in a small number of characteristic ways that are worth naming because they are the ways it will be wrong for you too:

  • It resolves ambiguity silently. Given an underspecified requirement it picks an interpretation and proceeds. It rarely announces that it picked one. Every silent resolution is a product decision that was made by something with no access to the contract.
  • It optimizes for the code compiling and the tests passing, which are the signals available to it, and neither of which is the goal.
  • It writes tests that agree with the implementation. Tests generated alongside code tend to encode what the code does rather than what the requirement says. They pass, they raise coverage, and they will happily lock in a defect.
  • It is confident about system state it has not checked. “The migration has been applied” is a claim about a database, and models are much better at producing that sentence than at verifying it.
  • It fixes the symptom you pointed at. Given a failing case, the shortest path is a special-case branch. It takes explicit instruction to get a root cause instead.

None of these is a reason not to use coding agents. All of them are reasons the review step cannot be a formality.

Adversarial review

The default posture on generated work is that it does not yet do what the specification says, and the burden is on the work to demonstrate otherwise. Four practices carry most of the weight.

Second-agent review. A fresh agent, given the specification and the diff but not the conversation that produced it, is a genuinely useful critic — largely because it has not been socialized into the first agent’s framing. The original session accumulates a shared story about why the code is right. A reviewer with no memory of that story asks the obvious question the story had already answered.

Root-cause investigation as the default. When something is wrong, the question is not “what makes this case pass” but “what class of input does this fail on, and what is the underlying reason.” Special-casing is the failure mode of both humans and agents under deadline; it is faster and it converts one visible bug into several invisible ones.

No premature success claims. “Fixed” is not a report. What was run, against what, with what output, is a report. This turns out to be as much a discipline for me as for the agent, and it is the single habit I would transplant into any team adopting this way of working.

Verification against actual system state. Did the migration run against this database. Does the row exist. Did the endpoint return that. Does the deployed build contain that commit. Most expensive incidents in AI-assisted delivery are not bad code — they are correct code operating on an assumption about the environment that nobody checked.

Testing is about business failure risk, not coverage

  • 16,212 Top-level Go test functions Authored by coding agents under a testing doctrine David defined; he did not hand-write these tests.
  • 181 Production alert rules

Those are measured figures from one production platform. They are here to make a point about doctrine, not about heroism, and the distinction matters: I established the testing and reliability doctrine and directed and validated the implementation. I did not personally author those tests. A coverage number is trivially inflatable, and a portfolio claim to have hand-written sixteen thousand test functions would be both false and, if true, evidence of questionable judgement.

What the doctrine actually says is that tests are allocated against business consequence rather than spread evenly across the codebase:

  • Money paths get real infrastructure. Settlement, payout, and invoicing run against a real PostgreSQL instance in a container, not a mock, because the defects that matter there are transactional and a mock cannot express them.
  • Every production bug becomes a regression test before it is closed. The test is the receipt. It is also the only mechanism that reliably prevents the same defect from returning through a future refactor performed by an agent that never knew about it.
  • Idempotency is tested explicitly wherever a webhook, a retry, or a queue can deliver twice — because “at least once” is what the network guarantees and “exactly once” is what the business assumed.
  • Boundaries are tested at the boundary, on the thresholds that carry commercial meaning.
  • Known bug classes are tested as classes, not as instances.

The failure-mode document written during specification is what this allocation is derived from. Coverage as a target answers the question “how much of the code did we execute.” The question worth answering is “which of the ways this can hurt the business are we actually defended against,” and those two questions have very different answers on the same codebase.

Production feedback becomes a permanent control

The loop that makes this model compound is short and it is the reason to operate what you build:

Operate → discover a failure → document it → encode an invariant → implement a control → monitor.

The step people skip is the fourth. A fix protects against the incident you had. An invariant — a property stated as something that must always be true of the system, rather than a task somebody must remember — protects against every future instance of the class, including the ones introduced by an agent that has never read the postmortem.

The step after it is what makes the invariant real. An invariant in a document decays; an invariant that runs as a production control against live data does not. And these have to be controls rather than tests, because the interesting production failures are usually not logic defects at all. The code does exactly what it was told. The defect is in the state of a running system — a configuration value, a stale mapping, a superseded setting that still parses — and tests do not look at that.

This is also the answer to the most common objection to AI-generated code, which is that nobody on the team deeply understands it. That is a real risk, and the mitigation is not to read every line. It is to know precisely what must be true of the system, and to have that continuously checked by something other than the confidence of whoever generated the code.

Where autonomy stops

Two rules, both drawn from operating these systems rather than from a position on AI safety.

Agents may act only where the action is reversible, or where a human has authorized this specific instance. In DCE’s operations model, the AI reads freely and analyses freely, and its most privileged action is creating a specifically permitted draft or open record that a human must still act on. It may not send external communications and it may not move money — not at any confidence level, not under any approval workflow. The boundary is drawn at irreversibility, not at task difficulty, because difficulty is a property of the task and irreversibility is a property of the consequence.

Where the evidence does not support a conclusion, the system escalates rather than estimating. A confident wrong number is worse than no number, because a number in a cell acquires the authority of every other number around it and its provenance disappears immediately.

Both rules cost something. Escalation costs a human’s attention; the reversibility boundary costs throughput on exactly the workflows where automation would feel most impressive. Both are cheap relative to the alternative, and the alternative’s cost does not arrive at the moment the mistake is made — it arrives weeks later, in someone else’s report.

What it actually costs

An honest close, because the version of this story where the agent does the work and the human sips coffee is not the one I recognize.

The specification work is substantial and it front-loads effort into a phase with no visible output. Review is slower than reading a diff, because the diff is not the artifact under review — the specification is, and the diff is the claim. Verification against real system state is tedious and cannot be delegated to the thing making the claim. Operating the result is what generates the knowledge that makes the next specification good, so the model degrades if you hand off operations.

What you get for that is a rate of delivery that a single person could not otherwise reach, on systems that carry real money, with a defect profile that is different from hand-written code rather than uniformly better: fewer typos and mechanical errors, more silently-resolved ambiguities. Which is to say the failure mode moved from the code to the requirements, which is exactly where a person’s attention is worth the most.