Refute, Don't Review
Assume this is broken and prove it.
If you can't, we're good.
Refute vs. Review: What's the difference?
We all know a coding agent's first pass is flawed. The question is, how do you uncover the flaws? Today's typical answer is AI code review – and whole companies are built on that.
I've actually had far more success with Refute than Review.
The problems with agent review and especially agent self-review are well known. Moving review out of the main context has helped, but most review agents still start by assuming the code is correct.
Tell an agent that the code is broken, and it will go looking for the problems it assumes exist. That's good.
By contrast, tell an agent that the code is broken and to prove it, and it will go looking for the problems it assumes exist. If it comes up empty-handed, you're far more likely to have clean code than with code review alone.
Anecdotally, on anything but the simplest change, I have never had the first refuter come back empty-handed. That's frightening for what was getting through before, and reassuring for what it catches now.
Power in the loop
An adversarial stance for review isn't new of course. Where refutation really pays off is when you run it in a loop.
When the refutation agent finds issues, I have my main agent fix them and then repeat the refutation until it converges towards full resolution.
Human attention is the scarce resource
By the time an agent tells me something is ready for review, it should have survived several rounds of trying to break it. All I want then is a short list of what only a human can check.
None of this replaces architectural review and it doesn't fully replace code review. But refutation comes first and gets the higher priority in my workflow.
The Refutation prompt
The core of the prompt is simple:
Your job is to REFUTE a change, not review it. Default to: this is broken, and prove it.
A finding counts only with evidence: a failing test, or a command plus its output, or a line-numbered path from input to wrong result. "Could not break it" is valid if you list what you tried.
That guidance is accompanied by the requirement, the commit range, the test command, and a few angles to attack. Here's the full prompt template and here's an example from real life.
Two things matter most:
- A fresh sub-agent. The refuter gets the brief -- requirement, commit range, test command -- and not the author's reasoning. If the sub-agent shares the author's context, it shares the author's blind spots.
- Evidence or it doesn't count. A finding without a failing test or a repro gets dropped. That kills the plausible-sounding false positives.
Loop and converge
- Fix every confirmed defect. Keep the refuter's failing test.
- Dispatch a new refuter with the same prompt and the new commit range.
- Stop when a round confirms nothing, or only minor defects outside the requirement.
- Escalate to me when it stops converging: a round finds as many defects as the last, or worse ones; the same invariant breaks again in a new form; or five rounds have passed.
When the refutation rounds don't converge, the problem usually isn't the code; it's the design. That's when I step in.
I've packaged the prompt and the loop as an agent skill you can install in Claude Code, Codex, Cursor, Gemini CLI and others: cmdctrlai/refute.
I hope you find this useful. I sure have.
-- Michael