Basal John

Writing

Nobody Was Looking

When agents write more code than people can inspect, quality has to move from universal review to independent evidence and risk-based decisions.

Quality engineering rests on three questions. Does this code follow the way we write code here? Does the change do what the ticket asked, and did it break anything else? Is it safe to release?

Today people answer all three, one change at a time. A developer writes the code, a second developer reads it and applies their own judgement, a quality engineer tests against the ticket, and someone decides it can ship. This works for exactly as long as people can look at every change, and that is the part that is ending.

Agents break the arithmetic. They write more changes, faster, with less human involvement at each step, and no hiring plan closes that gap. The three questions survive. The eyes per change do not.

There are two lazy answers to this, and I hear both every week. Slow the agents down, or trust them. The first gives up the reason you adopted agents in the first place, and the second is a hope dressed up as a strategy.

The self-certification trap

The first mistake teams make here is always the same. They let the loop that wrote the code also write the checks that grade it.

An agent that produces a change and then produces the tests for that change has graded its own homework. If the agent misunderstood the ticket, it will misunderstand it the same way in the test. If it introduced a bug, the test it writes will certify the bug as intended behaviour. A check generated inside the creator loop measures whether the agent agrees with itself, and it always does. This is not hypothetical: when CrowdStrike shipped its Channel File 291 update in July 2024, the content validator built to catch exactly that kind of mismatch passed it, and roughly 8.5 million Windows machines crashed (their root-cause analysis).

Let me be clear about what this is not, because the argument usually gets misread here. Agent-written tests are useful the same way a developer’s unit tests are useful: they show the person who built the thing checked their own work. The mistake is letting them count as assurance, which has to come from somewhere the builder does not control.

So the first rule is structural. The checks that decide whether a change is safe live outside the loop that wrote the change, with the suites, the test data and the thresholds sitting somewhere the coding agent cannot reach. It must not be able to rewrite a check, its data or its threshold to make its own change pass.

The second rule follows from the first: agents return evidence, they never make release decisions.

Move the first signal left

The cheapest quality failure is the one named before a line of code exists.

A ticket with thin or missing acceptance criteria is a defect that has not happened yet. Today nobody checks the ticket. The gap surfaces mid-build, when a developer or an agent hits it and guesses, and the guess ships. A check that reads the ticket during refinement and names what nobody could act on costs almost nothing, and it removes a whole class of rework that teams currently accept as normal.

The industry has watched a gap like this ship at full scale. In August 2012, Knight Capital deployed new code to seven of its eight trading servers, and the eighth still carried a function that had been dead for years until a repurposed flag switched it back on. The system pushed millions of unintended orders into the market and lost $440 million in about 45 minutes, because the check everyone assumed would catch a bad deployment did not exist (the SEC’s enforcement order). Nobody at Knight was careless. The gap simply lived where nobody was looking, and it shipped.

The same logic applies one step later, while the developer still has the ticket open. Before the change leaves the machine, checks can name important behaviour that no test covers, flag behaviour the ticket never asked for, and map every acceptance criterion to the evidence that covers it. Running these locally is about timing: the developer is still holding the context, so a finding costs minutes instead of a review round-trip.

Local checks inform, they do not block. A check that blocks on a developer’s machine is a check developers learn to skip, and then you have paid for the tooling and lost the signal.

Enforcement belongs in the pipeline, because enforcement cannot depend on who remembered to run what. The same three checks run again in CI, and there they do block. That split, inform early and enforce later, is what makes developers read the signals instead of routing around them.

Evidence, not opinion

When a change moves to review, the question “is this safe” should already have an answer written down.

The shape that works is one orchestrator that receives the change, with its acceptance criteria, its diff and the existing test context, and dispatches independent checks in parallel. Visual regressions. Critical user journeys. Protected regression coverage. Coverage gaps. Acceptance criteria mapped to evidence. Behaviour nobody asked for, and drift from the stated intent. Security exposure in the change.

Each check returns evidence, and the evidence lands next to the work it belongs to rather than in a dashboard nobody opens. A developer reading the ticket sees the whole record, and their own agent can read it too and fix against it without a human relay in the middle.

This changes what review is. Today a reviewer opens a blank diff and reconstructs the change’s intent from the code itself. With an evidence record attached, they start from what is already known, and they read code where the risk or the thin evidence warrants it. “Did anyone actually look at this” stops being a question, because the record is the answer.

Risk decides who touches the change

Evidence only matters if a written rule acts on it. “Is this safe to release” decided change by change, by whoever happened to look, is not a rule. It is the same hope as before, with better tooling.

The rule classifies every change on four things, and each one is ordinary to state. Blast radius is how much of the product the change can touch. Reversibility is how cleanly you can undo it, and a feature flag is a different animal from a rollback, which is a different animal from a data migration. Impact is who notices when it goes wrong and what it costs them. Detectability is how fast you would know, because a silent failure is worth more worry than a loud one. Together they answer the only question that matters: if this change is bad, how bad, for how long, and would we even notice?

A small, reversible, well-detected change merges and deploys on green evidence, with no person in the path. A bounded, customer-visible change merges automatically and deploys under observation, which is only honest where the service has real health signals and a rollback that actually works. Everything else waits for a person, and that person starts from the evidence record instead of a blank diff.

Two principles carry the weight here. A change is high risk until evidence proves otherwise, and anything unclassified is high. And merging and deploying are separate decisions, so a person stays in the path for whichever one the evidence cannot carry.

Prove the machinery before you trust it

None of this earns trust on day one. A risk model you have not tested is just another opinion, and an automated opinion is more dangerous than a human one because it fails consistently and at scale.

So the model runs in shadow first. Shadow means it evaluates every real change and writes down what it would have decided, while nothing in the pipeline obeys it. People review, approve and merge exactly as they do today, and the existing rules stay in force. Nothing about delivery changes except that you are now collecting the answer to one question: would the model have been right?

After enough changes, you compare what the model would have allowed against what reviewers actually caught and what production actually revealed. Where the model was stricter than reality, the levels are drawn wrong. Where it was looser than the bug that reached production, the evidence is missing something, and that is the finding that matters most. The boundaries move on that evidence, and only then does anything ship without a person.

The first incident after go-live is not the moment to discover what your model never checked.

Where the humans go

The fear underneath most resistance to this model is that it removes people from quality. It does the opposite.

Today every change waits for a person, whatever it is and whatever the evidence says. Human attention gets spent uniformly, most of it on changes that were never going to fail review, while the genuinely risky change gets the same thirty seconds as the typo fix. That is the current system, and it is the one people call safe.

The model I have described spends attention where it is scarce. Routine changes move at machine speed on machine evidence, and risky changes get a person who starts informed, with the evidence assembled and the specific concerns already named.

This is also the honest description of what quality leadership becomes. The quality engineer stops being a queue that teams wait behind. The job becomes designing the allocation: which evidence is required, where the risk levels are drawn, when they move and on what proof. It is a harder job than running a test queue, and it is the one worth hiring for.

The goal is not fewer humans. The goal is to spend human judgement only where it changes the outcome.

Related reading: Code got cheap. The organisation didn’t.

Back to writing