A green build has never meant the software works. Every QA engineer knows this in their bones — it is why the test pyramid exists, why we put integration tests above unit tests and end-to-end tests above those, and why “all tests passed” has always been a claim about coverage rather than about correctness.
So when the current wave of writing about AI-generated code announces that a passing build no longer proves anything, the correct reaction is not alarm. It is recognition. This is the oldest problem in testing, arriving at a scale that makes it impossible to ignore.
What has actually changed is worth isolating precisely, because most of the discussion mixes the familiar part with the genuinely new part, and only one of them needs new thinking.
Why an agent cannot mark its own work
Coding agents are good at a specific band of tasks: writing unit tests, reviewing diffs, tidying code. The trap is assuming that competence extends to judging whether the result works. It does not, for three reasons that are worth separating because they have different fixes.
It cannot hold the knowledge. Real testing carries context that does not fit in a window: years of application-specific behaviour, team conventions, which flaky tests are known and skipped and why, historical failure patterns. You cannot paste an organisation’s testing memory into a prompt, and pushing harder degrades rather than improves the output. This is the same context-budget problem behind the four ways agent context breaks: an agent handed too much starts reasoning worse, not better.
It has no hands and no eyes. This is the decisive one. An agent emits text. It cannot click, scroll, type, or look at what rendered. It cannot tell you the modal is broken, the layout collapsed, or the button is behind an overlay. A language model is a text engine; a running browser is a visual, stateful, event-driven system. Nothing inside the model bridges those.
Its tests are brittle by construction. When agents do write automated tests, they tend to produce code welded to the current implementation — hard-coded selectors, fixed waits. The UI shifts and everything breaks, and regeneration is the only repair. Worse, those tests encode what the code is meant to do rather than what the user is meant to be able to do. That is maintenance debt compounding on every deploy, and it is a familiar way for a test automation programme to die quietly.
Notice that only the second reason is specific to AI. The first is a scaling problem and the third is the oldest failure mode in test automation. What is new is that they now arrive together, continuously, from a generator that never gets tired.
The referee cannot be one of the players
The structural answer is the part worth keeping from the current discussion: put a validation layer between generation and shipping, and make it independent of the thing that generated the code.
The reasoning is clean. A coding agent is probabilistic by design, which is exactly what makes it useful for generation and exactly what disqualifies it as a judge of its own output. Asking it to grade itself is asking one player to referee the match they are playing in. The validator’s job is different: take an intent, execute it against the real running system, and return a verdict plus the evidence behind that verdict.
For a QA engineer this is not a new architecture. It is the reason we do not let unit tests be the last word, and the reason a deployment gate lives outside the build that triggers it. The agentic version just makes independence non-negotiable, because there is no longer a human in the middle whose intuition would have caught the obviously-wrong thing.
The one genuinely new constraint
Here is the part that is new, and it is a tension rather than a solution.
The validator needs to be deterministic (same intent, same application state, same verdict) or it is not a gate, it is a second opinion. But the work it is being asked to do is open-ended: judge whether a flow a human described in a sentence actually works. Open-ended judgement is what language models are for, and language models are not deterministic.
So you get a fork, and both branches have a cost:
| Deterministic validator | Model-based validator | |
|---|---|---|
| Judges | Exactly what you specified | Open-ended intent |
| Verdict stability | Same input, same answer | Varies run to run |
| Breaks when | The spec was wrong or incomplete | The judge is wrong |
| The ceiling is | Your ability to specify | The judge’s own accuracy |
That last row is the one teams skip. If your validator is a model, its accuracy is the ceiling on everything behind it, and you will not know that ceiling unless you measure it separately. In the RAG build I looked at recently, the faithfulness judge scored AUROC 0.702 — clearly useful, clearly not reliable, and the honest limit on every number downstream. A judge you have not scored is not a gate; it is an opinion with a green tick attached.
The practical resolution is usually a split rather than a choice: deterministic execution driving a real browser, with model judgement confined to the parts that genuinely need interpretation — and measured where it is used. Determinism where you can specify, judgement where you cannot, and a number attached to the judgement.
Testing the agent is not testing the app
There is a second problem underneath the first, and it is the one most teams have not started on. When the agent is the product — a support bot, a voice assistant, a workflow agent — you are no longer validating a checkout button. You are validating reasoning.
Two approaches are in use, and they are complementary rather than competing. The QA reader will recognise the shape immediately:
| Scenario evaluation | End-to-end simulation | |
|---|---|---|
| Shape | A few turns, responses checked in isolation | Full multi-turn interaction, goal checked |
| Cost | Fast, cheap | Slow, expensive |
| Catches | Wrong answer to a specific prompt | Failures that only emerge across a conversation |
| Equivalent to | Unit test | Integration test |
That is the test pyramid again. Same economics — many cheap ones at the bottom, few expensive ones at the top — and the same failure mode: teams build the cheap layer, watch it go green, and never build the expensive layer that catches the failures that matter.
The reason it matters more here is that a conversational agent’s worst failures are almost all multi-turn. It contradicts itself on turn eight. It loses a constraint it accepted on turn two. It agrees to something on turn five that it refused on turn three. No amount of single-turn scenario evaluation surfaces any of that, exactly as no amount of unit testing surfaces a broken checkout flow.
If you are building agents and your evaluation is entirely single-turn, you have a unit-test suite and you are calling it a test strategy.
Humans move up, not out
“Agents test agents” reads like an argument for removing people. In practice it is the opposite.
The question a reviewer answers has changed. It used to be does this code work. It is now can I trust this reasoning, and a reasoning system needs a reasoning reviewer. That is not a script; that is judgement, and it is the one thing in the loop that does not scale by adding compute.
Architecturally, that means explicit human gates at the points where judgement rather than throughput is the binding constraint. And it means the tooling’s real job is to make those gates cheap: hand a person a ranked, evidenced, contextualised decision instead of a wall of logs. A gate a human dreads is a gate that gets rubber-stamped.
The role does not shrink. It moves from writing scripts to specifying intent, from executing tests to validating reasoning, from repairing brittle selectors to deciding what “good” means for this product. That is the argument I made when generating code stopped being the hard part, and nothing in the last year has weakened it.
What to do about it
Four things, in the order they pay off:
- Put the validator outside the generator. Whatever else you build, the thing that judges cannot be the thing that produced the work. This is free and most setups violate it.
- Drive a real browser for anything user-facing. No amount of code-level checking substitutes for something that clicked the button and looked at the result. This is the gap agents structurally cannot close on their own.
- If your judge is a model, score the judge. On its own test set, reported as a number, before you trust anything behind it. Otherwise you have moved the failure rather than removed it.
- Build the top of the pyramid for your agents too. If they are the product, single-turn evaluation is a unit test. The failures worth catching live in the multi-turn runs you have not written yet.
None of this is a new discipline. It is the discipline QA already had, applied to a generator that produces more change per day than a team used to produce per quarter — with one honest addition, which is that when you delegate judgement to a model, you now owe everyone a number for how good that judgement is.