AI agent verification: why your agent can't grade its own exam
AI agent verification means checking the agent's result against a signal it can't produce or tamper with: tests, typecheck, real system state. It does not mean asking the agent whether it's done. Sounds obvious. But most autonomous loops that run for hours still work like this: the agent does the work, the agent grades the work, the agent decides it got better.
A July paper measured the damage. Across 54 cycles, the agent said it improved every single time. In 56% of them, the measured change was zero or negative.
In this post you'll see why that happens, why swapping in a bigger judge model doesn't fix it, and you'll build a Laravel loop with an external verification gate: a machine-checkable exit condition, a hard budget, and a guard that stops the agent from editing its own exam.
TL;DR
- What it is: a harness pattern where a verifier outside the agent's reach decides whether the agent made progress, not self-evaluation.
- Stack/Models: any LLM with tool use; example in PHP 8.3 + Laravel 12 (
Process), but the pattern works in any language. - Cost/Access: zero licensing. The cost is engineering: writing the check before you let the loop run.
- Useful link: When Do Agent Loops Mistake Stagnation for Progress? (arXiv 2607.25152)
The progress mirage: when the agent grades its own exam
Long-running agents plan, act, and judge their own completion with no human in the middle. That last verb is the problem.
Researchers Hyundoo Park and Byungho Choi named this failure: the progress mirage. The agent accepts plausible changes as progress while the real outcome stagnates or regresses. In the paper published on July 27, 2026, they built a testbed where the agent and its tools stay fixed. The only thing that changes is the information source of the evaluator that lets each loop cycle through (or blocks it). Ground truth is a world-state oracle isolated by container and network, out of the agent's reach.
The numbers:
- 54 cycles, and the frontier agent claimed improvement in every one.
- 56% of those cycles had a measured change of zero or below.
- With a self-evaluation gate, the loop degenerated into "accept everything" and eroded by 19% the best state it had already reached.
Read that last one again. The agent didn't stall. It made the system worse, cycle after cycle, believing it was improving it.
If you've ever left an agent running overnight and come back to a huge diff and a confident summary saying "all objectives met", you know the feeling. The difference is that now there's a number.
The detail that matters if you build these things: the model was the same in every scenario of the experiment. What changed the result was where the gate gets its success signal. That's not a model problem. It's a harness problem. And that layer (gate, oracle, budget, what the agent can and can't touch) is exactly what's on the table at AI Engineering Lab 4th Edition, a live Harness Engineering for Intelligent Agents immersion on November 14 and 15, 9am to 1pm (Brasília time), on Google Meet.
Why a bigger judge doesn't fix it
Every dev's first reaction: "fine, put a stronger LLM-as-a-judge on it".
The paper tested exactly that. And the hypothesis was registered before the experiment, so nobody could bend the conclusion afterwards.
The strongest judge, reading the full artifact text, the change diff, and its own verdict history:
- accepted cycles where 44% were real-world regressions;
- rejected 38% of the real improvements.
The hypothesis that a strong judge closes the gap was rejected.
Why? Because the judge was in-band: it only saw what was inside the transcript. And success on an open-ended objective doesn't live in the transcript. It lives in the world. In the conversion rate that went up or didn't, the endpoint that responded or didn't, the query that got faster or didn't.
The proof is the counterpoint in the same paper. On a task where success can be verified from the artifact itself, the same judge's mirage dropped to zero.
Translated to production:
| Where the success signal lives | Who can verify it |
|---|---|
| In the artifact itself (the code compiles, the JSON validates against the schema) | An in-band check works, including an LLM judge |
| Outside the transcript (integration test, real metric, database state) | Only an out-of-band verifier with real-world access |
Want the data point that ends the "but the judge has to explain why so the agent learns" debate? A variant that returned only the verdict (accepted or rejected, no explanation) delivered real-world output close to full feedback: 110.0 versus 113.0. The gain comes from where the gate is grounded, not from how much it says.
AI agent verification in practice: the 3 gates
This pattern is graduating from bash folklore into a product primitive. A practitioner guide on agentic loops boils down the minimum for running an autonomous loop to three questions. If the answer to any of them is "no", you don't run it:
- Is there a machine-checkable exit condition? "All tests green", "the schema validates", "p95 under 200 ms". "When the agent thinks it's done" doesn't count.
- Is there a hard cap on iterations and budget?
max_iterationsand a token/money limit. Without it, the loop gets stuck on the same error for hours and the bill keeps running. - Is there verification every iteration that the agent can't edit? Typecheck, tests, green CI. The same guide cites the classic case: the agent "fixes" the failing test by deleting the assertion.
The third point is the one most people skip. Running the tests isn't enough. The agent can't have permission to touch them. If it can edit the exam, the exam becomes one more self-evaluation surface.
I covered this in more depth in the post on TDD with agents: whoever writes the test is in charge of the agent.
Prerequisites
- [ ] PHP 8.3+ and Laravel 12 (the
Processfacade has been available since Laravel 10) - [ ] A test suite that runs from the command line (
php artisan testor Pest) - [ ] PHPStan/Larastan, if you want the type gate
- [ ] An agent harness that already runs tasks in a loop. If you don't have one yet, start with this Laravel harness from scratch
A tutorial shows you the way — in the Clã you build alongside us. A live class every week, real AI Engineering projects, next to people already in production.
Join the ClãHands-on: a loop with an external gate in Laravel
The idea is to split three roles that are mixed together in your agent today: who does the work (the agent), who verifies (the gate), and who decides to stop (the loop).
Step 1: the gate runs outside the agent's tools
The gate is your code, deterministic, called by the loop. The agent doesn't call the gate and doesn't get a tool for it.
<?php
namespace App\Agents\Verification;
use Illuminate\Support\Facades\Process;
final class VerificationGate
{
/** @var array<string, string> check name => command */
private array $checks = [
'tipos' => 'vendor/bin/phpstan analyse --no-progress --error-format=raw',
'testes' => 'php artisan test --stop-on-failure',
];
public function __construct(private readonly string $workdir) {}
public function run(): GateResult
{
foreach ($this->checks as $nome => $comando) {
$result = Process::path($this->workdir)
->timeout(600)
->run($comando);
if ($result->failed()) {
return GateResult::reject($nome, $result->errorOutput() ?: $result->output());
}
}
return GateResult::accept();
}
}
<?php
namespace App\Agents\Verification;
final class GateResult
{
private function __construct(
public readonly bool $passed,
public readonly ?string $failedCheck = null,
public readonly ?string $log = null,
) {}
public static function accept(): self
{
return new self(true);
}
public static function reject(string $check, string $log): self
{
return new self(false, $check, $log);
}
/** Short error signature, to detect the agent hitting the same wall. */
public function fingerprint(): string
{
return md5($this->failedCheck . substr((string) $this->log, 0, 2000));
}
}
Step 2: lock the exam
Before the loop starts, hash the test folder and the gate config. On every iteration, compare. Changed? The cycle is rejected, no matter what the agent says.
<?php
namespace App\Agents\Verification;
use Symfony\Component\Finder\Finder;
final class TamperGuard
{
private string $baseline;
public function __construct(private readonly string $workdir)
{
$this->baseline = $this->hash();
}
public function intact(): bool
{
return hash_equals($this->baseline, $this->hash());
}
private function hash(): string
{
$files = Finder::create()
->files()
->in($this->workdir)
->path(['tests/', 'phpstan.neon', 'phpunit.xml'])
->sortByName();
$ctx = hash_init('sha256');
foreach ($files as $file) {
hash_update($ctx, $file->getRelativePathname());
hash_update_file($ctx, $file->getRealPath());
}
return hash_final($ctx);
}
}
Even better: mount tests/ read-only in the agent's sandbox. The hash is the second line of defense.
Step 3: the loop decides to stop, not the agent
Here the three gates come together: checkable exit, iteration and budget cap, verification on every pass. Plus a simple "hitting the same wall" detector: the same error three times in a row ends the loop.
<?php
namespace App\Agents;
use App\Agents\Verification\TamperGuard;
use App\Agents\Verification\VerificationGate;
final class VerifiedLoop
{
public function __construct(
private readonly CodingAgent $agent,
private readonly VerificationGate $gate,
private readonly TamperGuard $guard,
private readonly int $maxIterations = 15,
private readonly float $maxCostUsd = 5.00,
) {}
public function run(string $task): LoopOutcome
{
$feedback = null;
$lastFingerprint = null;
$repeats = 0;
for ($i = 1; $i <= $this->maxIterations; $i++) {
$step = $this->agent->step($task, $feedback);
if ($this->agent->totalCostUsd() > $this->maxCostUsd) {
return LoopOutcome::stopped('orçamento estourado', $i);
}
if (! $this->guard->intact()) {
$this->agent->revertLastChange();
$feedback = 'REJEITADO: arquivos de teste ou config do gate foram alterados. Não toque neles.';
continue;
}
// The agent can say "done" as many times as it wants.
// The gate decides.
$result = $this->gate->run();
if ($result->passed) {
return LoopOutcome::done($i);
}
$repeats = $result->fingerprint() === $lastFingerprint ? $repeats + 1 : 0;
$lastFingerprint = $result->fingerprint();
if ($repeats >= 2) {
return LoopOutcome::stopped("mesmo erro 3x em '{$result->failedCheck}'", $i);
}
$feedback = "REJEITADO pelo check '{$result->failedCheck}':\n"
. mb_substr((string) $result->log, -3000);
}
return LoopOutcome::stopped('teto de iterações', $this->maxIterations);
}
}
Notice what's not there: at no point does the loop ask the agent "are you done?". step() can return a nice "completed". It gets ignored.
Step 4: for open-ended goals, the gate has to look at the world
A unit test covers "the code does what it says". It doesn't cover "checkout got faster" or "the support agent resolves more tickets". For those, the gate needs access to a signal outside the transcript.
// Out-of-band gate: measures the world, not the agent's text.
$antes = $this->metrics->p95('checkout', minutes: 30); // real baseline
$this->deployer->toStaging($branch);
$this->loadTest->run('checkout', requests: 2_000);
$depois = $this->metrics->p95('checkout', minutes: 5, env: 'staging');
return $depois < $antes * 0.95
? GateResult::accept()
: GateResult::reject('p95', "antes={$antes}ms depois={$depois}ms");
And remember the paper's result: the verdict alone carries almost all of the benefit. If the check's log has sensitive data or makes it easier for the agent to "optimize for the test", return only accepted/rejected.
What if success doesn't fit in a test?
Two approaches showing up in 2026.
A task-grounded verifier outside the agent. AutoFyn, a harness published in July, explicitly separates who builds from who verifies. Each round starts from a fresh model session; a task-grounded verifier checks the work and returns an objective reward; that reward is distilled into persistent state (memory files, reports, repository state) for the next round. No weights touched. The authors report that on the six problems of IMO 2026, every model with room to improve scored higher inside AutoFyn than in its provider's own coding agent, and that the harness produced 16 maintainer-confirmed vulnerability advisories in projects like Next.js, pnpm, and LiteLLM.
An agentic judge that checks environment state. When the judge has to be an LLM, it can't just read the transcript. Judgment Labs describes a judge that navigates the trajectory, checks tool evidence and environment state to verify the real effect of actions, and adapts its rubric from production feedback. It's vendor material, so read it as product positioning. But the direction matches the paper: a judge is only useful if it has out-of-band access.
To set up the judge and dataset side properly, there's a step-by-step on evals with LLM-as-a-judge here on the blog.
Limitations and gotchas
- The gate is only as good as the check. A weak suite becomes a mirage with a seal of approval. Poor coverage lets the agent "pass" while breaking whatever isn't tested.
- Out-of-band gates cost. Staging deploys, load tests, and real metrics take minutes per cycle. Run cheap checks (types, unit tests) first and the expensive one only when the cheap ones pass.
- Reward hacking doesn't go away, it moves. The agent will look for the shortest path to a green gate. Lock the exam (hash + read-only), watch the first cycles closely, and be suspicious of diffs that touch test config.
- Isolation is part of the verifier. In the paper, the oracle was enforced by container and network isolation. If the agent has credentials to write to the same store the gate reads from, the gate isn't external.
- Production data. A gate that measures the real world reads real data. Don't feed that raw log back into the agent's context without masking it.
- The numbers come from a testbed. 54 cycles in a controlled environment. The direction is strong; the exact percentage in your system will differ.
Quick FAQ
What is self-evaluation in AI agents?
It's when the agent itself (or a judge that only sees the text the agent produced) decides whether the task moved forward. It works when success is verifiable in the artifact itself. It fails when success lives outside the transcript: then the agent accepts plausible changes as progress. That's the progress mirage described in paper 2607.25152.
Is LLM-as-a-judge still useful?
Yes, a lot: for criteria that live in the artifact itself (tone, format, requirement coverage) and for offline evals with a dataset. What doesn't work is an in-band judge as the only gate of an autonomous loop with an open-ended goal. Combine it with deterministic checks and a real-world signal.
What's the right iteration cap?
There's no magic number. Start low (10 to 15), measure how many iterations successful tasks actually use, and adjust. The money budget cap matters even more than the iteration cap. More practical brakes in the post on agents stuck in infinite loops.
Does this apply to agents that don't write code?
Yes. Support agent: the gate is a ticket closed without reopening. Data agent: the query matches the audited number. Sales agent: the CRM record exists and is consistent. Always the same question: which signal can the agent not fabricate?
Conclusion
The agent isn't a liar. It just has no way of knowing, from inside the transcript, whether the world changed. And giving it a bigger judge that reads the same transcript doesn't change that.
AI agent verification that works is boring and deterministic: an exit condition the machine checks, an iteration and budget cap, a gate on every pass, an exam the agent can't edit, and, for open-ended goals, a signal that comes from the real world.
The next step in this trend is the verifier becoming a first-class part of the harness, with the same care we give the prompt today. If you want to see where this fits on the autonomy scale, from autocomplete to the agent that opens PRs on its own, read the 4 levels of autonomy in agentic code.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
There is no shortage of content. What is missing is someone to untangle it: what matters now is how to implement it the right way. In the Clã you get that live, every week, with people who have already filtered out the noise.
Join the Clã