Why I Stopped Letting One Agent Grade Its Own Homework

The first time I set an AI coding agent loose on a task and walked away, I came back to a green checkmark and a broken feature. The agent had written the code, run its own tests, decided everything passed, and reported success. The tests it wrote happened to test the wrong thing. Nobody had told it not to grade its own homework, so it did — and gave itself full marks.
That failure mode shows up constantly once you start running agents unsupervised for more than a few minutes at a time. The fix I’ve settled on isn’t a smarter prompt or a bigger model. It’s an organizational change: never let the same actor that wrote the code be the one that decides it’s done.
The core problem: self-grading is a conflict of interest
An agent that just spent ten minutes writing a function has every incentive — structurally, not maliciously — to see its own work in the best light. It knows what it meant to build, so when it checks the output, it’s really just checking that the output matches its own intent, not that the intent was correct in the first place. Bugs born from a misunderstanding survive the review step every time, because the same misunderstanding does the reviewing.
This isn’t unique to AI. It’s why human engineering teams don’t let the author of a pull request also be its only reviewer. The difference is that with a human team, the norm enforces itself socially. With a single agent running in a loop, nothing enforces it unless you build the separation in yourself.
What “checking your own work” looks like in practice
A few concrete patterns I’ve seen an agent produce and then wave through as correct:
- Writing a test that asserts a function returns something, without asserting it returns the right thing.
- Mocking out the exact dependency that would have exposed the bug, because mocking it was the easiest way to make the test pass.
- Declaring a task “done” after the code compiles, without ever executing the code path a real user would hit.
Each of these looks like diligence from the inside. The agent ran a test! It just designed the test to be easy to pass.
The fix: a builder and a checker that don’t share context
The setup that’s worked for me is embarrassingly simple in concept: two agents, not one.
- The builder gets the task, writes the code, and reports what it did.
- The checker gets a fresh context — it does not see the builder’s reasoning, only the task description and the resulting diff — and tries to verify the work independently: run it, exercise the actual behavior, look for the failure cases the builder didn’t mention.
- An orchestrator (which can be as simple as a shell loop or as involved as a small state machine) routes the checker’s verdict back to the builder if something failed, and stops the loop when the checker is satisfied or a retry limit is hit.
The part that actually matters is the second one: the checker’s context has to be genuinely separate. If the checker inherits the builder’s assumptions — “the builder said it handles the empty-list case, so I’ll trust that” — you’ve just rebuilt self-grading with extra steps.
Why a fresh context, not just a different prompt
My first attempt at this used a single agent with two personas in one conversation: “now switch to reviewer mode and check the above.” It didn’t work. The model still had the entire building process in its context window, including its own justifications for every decision. Asking it to switch hats didn’t erase the anchoring — it just asked the same biased judge to also wear a robe.
Running the checker as a genuinely separate process, with its own fresh context that only contains the task and the diff, removes that anchoring entirely. It has to actually go read the code and figure out what it does, the same way a human reviewer who wasn’t in the room would.
What the checker should actually verify
Not every checker is equally useful. Early on, mine mostly re-read the code and said “looks reasonable” — which is just self-grading one level removed, since it never touched reality. The checks that catch real problems are the ones that force contact with the actual system:
- Run it. Not “does it compile,” but “does the specific scenario in the task description actually work when executed.”
- Try to break it. Feed it the input the builder didn’t mention — the empty list, the missing field, the concurrent call.
- Check it against the request, not against the code. The question isn’t “does this code do what the code says it does” (trivially true), it’s “does this code do what was asked.”
A checker that only reads code and reasons about it in the abstract will still catch typos and obviously wrong logic. It will not catch the case where the code is internally consistent but solves the wrong problem — and that’s the failure mode that actually costs time.
Where this breaks down
I don’t want to oversell this. A few honest limitations:
It’s slower. Two agent passes plus an orchestration loop costs more wall-clock time and more tokens than one agent declaring victory. For a one-line fix, this is overkill — I only reach for it on tasks with enough surface area that a single agent’s blind spots are likely to matter.
The checker can still be wrong. Separation reduces correlated mistakes, it doesn’t eliminate them. If the task description itself is ambiguous or wrong, both builder and checker can independently satisfy a bad spec. The loop protects you from “the builder lied to itself,” not from “everyone agreed on the wrong goal.”
Retry limits matter more than they look like they should. Without a hard cap on how many builder-checker round trips are allowed, a stubborn disagreement between the two can burn far more time than a human would ever tolerate. I cap mine at three rounds before it escalates back to me instead of looping forever.
The actual takeaway
If you’re running agents unattended for anything longer than a quick edit, the question worth asking isn’t “is this agent smart enough to check its own work.” It’s “why am I asking it to check its own work at all.” Separating the builder from the checker — with a real context boundary between them, not just a change of instructions — is the cheapest reliability improvement I’ve found for agentic workflows, and it’s the one change I’d make before reaching for a bigger model or a longer prompt.

