Outlier  ›  learn

How an AI agent checks its own work — self-verification without cheating

Quick answer
  • When a test fails, the agent doesn't just trust the test or the code. It computes the true answer itself, then fixes whichever side is actually wrong — the source or a test that asserts a wrong value.
  • An anti-gaming guard blocks the classic cheat: weakening a good test so a buggy function turns the bar green. You can't "pass" by rewriting a correct assertion to match broken output.
  • The decision is made by running the tests — an execution oracle — not by the model grading itself. On a 4-bit local model, self-approval is not trustworthy; execution is.
  • Measured on Outlier's local 27B "compact" agent (MLX, on an M1 Ultra): ~45% blind on SWE-bench Verified (18/40), and autonomous builds it verified by running tests — a bank Account class (13/13 tests passing), a 5-file library system, a precedence-correct expression evaluator.
  • The honest limit: a 4-bit small model has a per-token slip floor on exact counting and arithmetic. Self-verification is precisely the mechanism that catches those slips instead of shipping them.

A small model will miscount. Ask a 4-bit 27B to count the vowels in a string and, some fraction of the time, it will confidently return the wrong number. That's a structural property of a quantized model doing exact enumeration — not a bug you patch away. The interesting engineering question isn't "how do we make it never miscount," it's "how does the agent catch itself when it does?" This is how self-verification works in a local coding agent, and why the answer turns out to be a test-vs-source arbitration loop with a guard against cheating.

The problem: a failing test has two possible culprits

An autonomous coding agent writes a function and writes tests for it. Then it runs the tests. When one goes red, a naive agent does one of two dumb things:

Both are wrong because both assume you already know which side is correct. You don't. A red test is a disagreement between two things the same fallible model wrote. To resolve it you need a third source of truth.

The count_vowels story

Here's the concrete case that shaped the design. The agent is asked to implement a vowel counter and test it. It writes a reasonable function. It also writes a test — and, because exact enumeration is exactly the operation a 4-bit model is weakest at, it hand-computes the expected value wrong:

def count_vowels(s):
    return sum(1 for c in s.lower() if c in "aeiou")

# model-authored test, with a miscounted expected value
assert count_vowels("aeiou") == 4   # WRONG — the answer is 5

The test goes red. Now: the function is actually correct. The test is wrong. An "always trust the test" agent would mangle a perfectly good function chasing a phantom bug. An "always trust the code" agent would happily rewrite the assertion to == 5 — which here is the right move, but only by luck, and the same reflex would rubber-stamp a genuinely broken function.

The correct behavior is to stop and ask: what is the true answer? Count the vowels in "aeiou" independently. It's 5. Now the agent knows the assertion was the wrong side, and it fixes the test — not the code. Same machinery, when the source is the wrong side, fixes the source instead.

The arbitration loop

When a test fails, the agent runs a small arbitration procedure rather than blindly editing one side:

  1. Compute the ground truth independently. For a checkable case, the agent derives the true expected value itself, separately from both the function under test and the failing assertion.
  2. Locate the disagreement. Compare true value vs. what the code produced vs. what the test asserted.
  3. Fix the wrong side. If the code disagrees with truth, fix the code. If the test asserts a value that disagrees with truth, fix the test. Only one side moves.
  4. Re-run. Selection is on the test result, not the model's opinion of its own work.

The point is that "fix the test" and "fix the code" are both legitimate outcomes — but only after an independent answer says which one is lying.

The anti-gaming guard

Step 3 has an obvious failure mode: what if "fix the test" becomes the agent's escape hatch for every hard function? Weaken the assertions, weaken the assertions again, and eventually the bar is green and the code is still broken. That's reward-hacking your own test suite.

So the arbitration is fenced by a guard: the agent may not weaken a test that already asserts the correct value. If the independent ground-truth check says the original assertion was right, editing that assertion is off the table — the only remaining move is to fix the source. You can correct a test that was genuinely wrong (the count_vowels("aeiou") == 4 case). You cannot "correct" a test that was right just because your code can't pass it.

This is the difference between a self-verifying agent and a self-deceiving one. Without the guard, self-editing tests is a cheat code. With it, editing a test is only allowed when an external check has already certified the old assertion as wrong.

Why the oracle has to be execution, not self-approval

There's a tempting shortcut: skip all this and just ask the model "is your answer correct?" It's cheaper and it feels like verification. It isn't. A model asked to grade its own output — especially a 4-bit small model — is unreliable in exactly the situations that matter, because the same weakness that produced the wrong answer produces the wrong self-assessment.

An execution oracle — running the tests — is external and objective. It doesn't care how confident the model is. In our measurements across the local coding stack, selecting on run-the-tests rather than "it compiles" or "the model approves itself" is the single biggest lever on accuracy. Everything else — best-of-N sampling, whole-file edits, grammar-constrained tool emission — is amplified by, or dependent on, having a real oracle to select against.

Verification signalWhat it actually provesTrustworthy?
Model approves itselfThe model is confidentNo — confidence and correctness diverge on the hard cases
It compilesSyntax is validWeak — a compiling function can be dead wrong
Tests pass (execution oracle)Behavior matches the asserted specYes — external, objective, and the basis for selection
Tests pass + anti-gaming guardBehavior matches a spec the agent wasn't allowed to weakenYes — closes the reward-hacking hole

What this buys, measured

None of this is theoretical polish. It's what let a local 27B agent build real, working software autonomously in a single session — each result verified by running the tests, not by the model claiming success:

On the number people actually care about, the same agent measured ~45% blind on SWE-bench Verified (18/40). That is a measured, blind figure on a hand-picked subset — not a leaderboard claim, and we won't dress it up as one. What matters here is the mechanism: on the bounded, testable tasks where a local model competes, self-verification is what turns "usually right" into "right, and it knew when it wasn't."

The honest limit

I'm not going to pretend self-verification erases the gap to a frontier cloud model. It doesn't, and it can't, for a specific reason: a 4-bit 27B has a per-token slip floor on exact enumeration, counting, and arithmetic. That floor is a structural property of the quantized model. Self-verification doesn't lower it.

What self-verification does is change what happens when the model slips. Without an oracle, a miscount ships. With the arbitration loop and the anti-gaming guard, the miscount produces a red test, the agent computes the true value, and the wrong side gets fixed. The model still miscounts sometimes. It just doesn't get to be wrong and confident and unchecked at the same time. On a local model, that's most of the game — and it's why the agent reports failure honestly instead of claiming a false success.

Frequently asked questions

How does a local AI agent check its own work?

When a test fails, the agent computes the true answer itself and arbitrates: it decides whether the code is wrong or the test asserts a wrong value, then fixes whichever side is actually incorrect. Selection is based on running the tests, not on the model approving itself.

What stops the agent from cheating a failing test?

An anti-gaming guard blocks the agent from weakening a test that already asserts the correct value. It cannot "fix" a red test by editing the assertion to match buggy output when the original assertion was right.

Why does an execution oracle beat self-approval?

A model asked to grade its own answer is unreliable, especially a 4-bit small model. Running the tests is an external, objective signal. In our measurements, selecting on run-the-tests rather than "it compiles" or "the model approves itself" is the single biggest lever on local coding accuracy.

Try Outlier free

Free Nano + Lite — local, private, no account. Pro $20/mo or $149/yr adds everything (Plus 397B, Marathon mode, Computer use, Deep Research v3, long context to 128K). Lifetime Pro from $99 (Founding 200, first 200 seats) or $200 (Founders 500). Apple Silicon only.

Download for Mac