DAY 54 / PHASE 6 · ENGINEERING

Oracle Inventory

Test Oracle · The Independence Trap · Reorder Automation by Verifiability

2026-07-06 · BigCat

Whether you can let AI run on its own hinges not on how smart it is, but on whether there's a judge independent of it.

// WHY THIS MATTERS

Behind every "automated step" in your AI workflow hides one question: who decides this step was done right? In software testing this is the test oracle problem—the judge that determines whether observed behavior is correct. When the judge is a compiler, a type checker, a test, a property check, it is independent of the model that produced the code: pass means pass. When the judge is "let the AI look again," "have agents debate," or "self-consistency voting," it shares the same failure modes as the generator—it's one brain grading its own exam. This issue isn't about writing better evals. It's something more fundamental: do an oracle inventory of your entire AI pipeline, mark which steps have an independent judge and which are AI-checking-AI, then redraw the automation boundary by verifiability, not by task difficulty. That decides where you can truly let go, and where letting go just scales up a system with no judge.

// 01

What an Oracle Is—and Why Most People Use a Fake One

Claim: the value of a verification step depends not on how strict it is, but on whether it is independent of the thing it verifies.

Background

Barr et al.'s much-cited 2015 oracle survey reduces "verification" to a clean question: a test oracle is a source that answers "is this output correct." It sorts them into four classes by independence and automatability, and that classification decides whether you can let go:

Here's the key insight: compilers, type checkers, unit tests, property-based tests are strong independent oracles; "have the LLM check again," LLM-as-judge, and self-consistency are weak or fake oracles—because the verifier and the generator are the same (or same-family) model, and their blind spots overlap heavily. You think you added a check; you just had the same brain copy its answer over. Step one of the inventory: for every step in the pipeline, ask—which class is this judge?

Example

Build an oracle-inventory table for your workflow—one row per automated step, tagged with judge type and independence:

# step                  judge              class        independent?
generate a function     typecheck+pytest   implicit/spec  yes ✅  → full auto
equivalence refactor    regression snapshot derived       yes ✅  → full auto
SQL generation          EXPLAIN on read-DB  implicit      yes ✅  → full auto
summary quality         LLM-as-judge       fake oracle    no ❌  → human sample
"is this code safe?"    another LLM says ok fake oracle    no ❌  → indep scan/human
"did it understand?"    — (none)           no oracle      no ❌  → must be human

Pin it to your project root. The rule is simple: only rows where "independent of the model" is ✅ earn full automation; ❌ rows either need a real oracle or keep a human in the loop.

Failure mode: treating "passed LLM review" as "verified." LLM review catches low-level mistakes (real signal), but it systematically waves through the class of errors it shares with the generator—exactly the most dangerous, most-need-to-catch class. The harm of a fake oracle isn't uselessness; it's manufacturing the illusion of "verified," tricking you into letting go.
Going deeper · Barr, Harman, McMinn, Shahbaz, Yoo The Oracle Problem in Software Testing: A Survey (IEEE TSE 2015), discovery.ucl.ac.uk (PDF) · Anthropic Building Effective Agents, anthropic.com/engineering
// 02

The Same-Source Independence Trap: Why AI-Checking-AI Doesn't Count

Claim: generate and verify with the same model family and errors are correlated; self-preference makes it systematically pass its own mistakes—double-check ≈ single check.

Background

Why is "independent" the load-bearing word? A rough probability intuition: if the generator's miss rate is p, adding a fully independent verifier (miss rate q) drops the joint miss to p·q—a quadratic fall, and that's the whole value of a "double check." But if the verifier's errors are highly correlated with the generator's (correlation ρ→1), the joint miss reverts to ≈ p—you bought nothing. A model self-checking, or same-family cross-checking, is exactly the high-ρ case: they're confidently wrong in the same places.

Worse, there's self-preference bias. Panickssery et al. (NeurIPS 2024) showed LLM evaluators can recognize their own generations and systematically score them higher—even when humans rate them equal in quality. Add the backward rationalization and confidence inflation from Day 5, and "AI checking AI" is not just correlated but positively biased. So multi-agent debate and self-reflection, which look a lot like "independent verification," are still an internal argument among same-family brains sharing one set of blind spots.

Example

There's one main way out: swap the judge for a non-LLM, or for an oracle bound to the math of the problem. Metamorphic testing is the classic move—it doesn't judge a single output's correctness, it verifies relations across multiple runs, and those relations come from the problem itself, not the model:

# LLM generated a sort; you have no "correct answer" as an oracle
# → use properties + metamorphic relations as an independent judge (hypothesis)
from hypothesis import given, strategies as st

@given(st.lists(st.integers()))
def test_idempotent(xs):
    assert my_sort(my_sort(xs)) == my_sort(xs)      # MR: sort is idempotent

@given(st.lists(st.integers()))
def test_permutation(xs):
    assert sorted(my_sort(xs)) == sorted(xs)         # MR: output is a permutation

@given(st.lists(st.integers()), st.integers())
def test_shift_invariant(xs, k):
    assert my_sort([x+k for x in xs]) == [x+k for x in my_sort(xs)]

None of these three properties is a "correct answer," but together they're a filter net independent of the LLM—the model can't produce an implementation that satisfies all relations yet is wrong. That's the craft of turning a "no-oracle task" into a "has-oracle task." A weaker fallback: at least have the verifier use a different-family model (Claude generates → cross-check with another family + an explicit scanner), which lowers ρ but still trails a genuine non-LLM oracle.

Failure mode: writing multi-agent debate or a "reflection loop" into the pipeline as independent verification, then auto-approving on top of it. Same-family agents debating will reach an agreed-upon wrong answer in their shared blind spots—consensus isn't correctness, and it hands you an even stronger "verified" illusion.
Going deeper · Panickssery et al. LLM Evaluators Recognize and Favor Their Own Generations (NeurIPS 2024), neurips.cc · Segura et al. Metamorphic Testing: A Review (ACM Computing Surveys), dl.acm.org/10.1145/3143561
// 03

Reorder the Automation Boundary by Verifiability

Claim: draw the automation boundary not by "how hard is the task" but by "is there a cheap independent oracle."

Background

Most people's instinct for drawing the boundary is "give the easy stuff to AI, keep the hard stuff." Wrong axis. The right axis is verifiability—does a step have a cheap, independent oracle. Arrange it as a ladder:

Verifiability Ladder oracle strength ▲ independence ▲ → automation call ┌──────────────────────────────────────────────────────┐ │ ① strong compile / types / unit tests / formal │ │ independent, ~0 marginal cost → auto + fail-closed │ ├──────────────────────────────────────────────────────┤ │ ② derived metamorphic / property / regression snap │ │ indep of single-output truth → auto + human sample │ ├──────────────────────────────────────────────────────┤ │ ③ weak LLM-judge / heuristics / cross-model │ │ correlated w/ generator → auto pre-filter + human gate │ ├──────────────────────────────────────────────────────┤ │ ④ none requirements / taste / strategy │ │ only a human → human-in-the-loop │ └──────────────────────────────────────────────────────┘ Let-go zone (full auto): ① ② Gate zone (human in loop): ③ ④

The rule: the higher up, the more you should let AI iterate at full speed—the generate-and-check loop is cheap and trustworthy, and if AI errs the oracle catches it instantly. The lower down, the more you keep a human in the loop. The most dangerous are tasks with a large generate-verify gap: one second to generate, half an hour to verify. In METR's 2025 randomized controlled trial, experienced developers using AI took 19% longer—a major driver was that verification cost is systematically underestimated: AI generates fast, but the work of verifying "looks right" into "is right" gets pushed back onto the human, and that stretch has no oracle.

Example

Reorder your agentic loop: enable auto-retry only on sub-tasks that have an oracle; checkpoint every no-oracle sub-task to a human.

def step(task):
    out = llm_generate(task)
    oracle = ORACLE_FOR[task.kind]          # judge from the inventory table
    if oracle.independent:                   # upper rungs ① ②
        for _ in range(MAX_RETRY):
            if oracle.check(out): return out   # release only on pass (fail-closed)
            out = llm_fix(task, oracle.feedback)   # independent feedback drives self-repair
        raise NeedsHuman(task)                          # retries exhausted, still failing → human
    else:                                     # lower rungs ③ ④: no auto-release
        return checkpoint_to_human(task, out)

The soul of this is that if oracle.independent: it moves the "can AI spin on its own" decision from "does the task look simple" to "is there an independent judge." Day 3 named one of the three conditions for graduating to an agent as "you can verify whether the final output is right"—here's the operational test for it.

Failure mode: opening an auto-loop where there's no oracle. The agent "flies off the rails without knowing it"—with no independent judge, retry just amplifies the same error and burns tokens. Before you enable MAX_RETRY anywhere, confirm what hangs above it is ①②, not ③④.
Going deeper · METR Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity (arXiv:2507.09089), arxiv.org/abs/2507.09089 · Simon Willison on reviewing AI-generated code, simonwillison.net
// 04

Personal Guardrails: Inventory Oracles Before You Scale

Claim: upgrading an AI workflow from "run by hand" to "automatic/at scale" without an oracle inventory is scaling up a system that has no judge.

Background

Scaling amplifies not just output but the errors no oracle caught. Run it by hand ten times and your eyes are the (expensive but independent) oracle; automate it to ten thousand runs and your eyes leave the loop—if nothing independent takes their place, errors flow downstream at 10,000×. So the essence of "personal guardrails" is: at your own harness layer, freeze independent oracles into automatic gates, before you let go. A checklist to answer before upgrading:

  1. Does this step have an independent oracle? (compile/types/tests/property, or only an LLM?)
  2. Is that oracle independent of the model that generated it? (same-family self-check doesn't count)
  3. Is the oracle fail-closed? (block on fail, not log a warning and pass)
  4. For steps with no oracle, is there a human checkpoint?

Only when all four are ✅ does a pipeline earn an automatic trigger. Any ❌ is where scaling will bite you back.

Example

In Claude Code the guardrails are hooks—hang the real oracle as a fail-closed gate that physically blocks on failure, not relying on the model's goodwill:

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{"type":"command",
        "command":"tsc --noEmit && pytest -q || exit 2"}]  # real oracle, nonzero blocks
    }],
    "Stop": [{"hooks":[{"type":"command",
        "command":"pytest tests/property -q || exit 2"}]}]  # derived oracle
  }
}

exit 2 is the point: a Claude Code hook uses a nonzero exit to feed the result back to the model as an error and block the action—that's how you turn an independent oracle into a fail-closed gate. Don't mistake "formatting" hooks (prettier / lint --fix) for correctness verification: they make code look right, not run right. Guardrails must separate "it runs" from "it's correct."

Failure mode: treating lint / format / "no errors" as a correctness oracle. A format oracle ≠ a correctness oracle ≠ a semantic oracle—the three are independent. Code that compiles, is beautifully formatted, and passes every lint can be flat-out logically wrong. When inventorying, mark precisely which layer each gate verifies; don't let a lower-layer oracle impersonate an upper layer's coverage.
Going deeper · Claude Code Hooks docs, docs.claude.com/.../hooks · Anthropic Building Effective Agents (criteria for graduating to an agent), anthropic.com/engineering

// PUT IT TOGETHER · Inventory the oracles of one pipeline

Pick an AI workflow you already run (say "Claude Code fixes a bug → opens a PR" or "RAG Q&A") and spend half an hour walking it through:

  1. Split into steps: break the pipeline into 5–8 atomic steps, one per row.
  2. Tag the judge: for each step fill in "who's the judge"—compile / types / tests / property / LLM-judge / human / none.
  3. Rate independence: mark each judge ✅/❌—is it independent of the model that generated that step? Same-family self-check is always ❌.
  4. Place on the ladder: assign each step to ① strong / ② derived / ③ weak / ④ no oracle.
  5. Redraw the boundary: ①② go into the "let-go zone" with an automatic gate (fail-closed); ③④ go into the "gate zone" with a human checkpoint. If a step you wanted to automate lands in ③④, ask: can I manufacture a real oracle for it? (make the output compilable / executable / citation-checkable / schema'd—turn no-oracle into has-oracle.)
  6. Install guardrails: write the ①② oracles as Claude Code hooks or CI gates and run it.

You'll find what actually blocks your scaling was never "the model isn't strong enough," but those few steps with no independent judge. Find them, and either build an oracle or keep a human—that's the watershed between "usable" and "safe to let go" for a personal AI workflow.

// DEEP THINKING

LLM-as-judge is everywhere. If it isn't an independent oracle, why is it "useful" in many evals?
Because it has real signal on coarse preference and none on the generator's own blind spots—don't conflate the two. When the judge is a different family and rates "A is clearly better than B," its correlation with human judgment is decent, good enough. When the judge is same-family and rates fine-grained "is this logic actually correct," self-preference plus shared blind spots break it. So LLM-judge fits ranking/pre-filtering, not the last correctness gate before you let go: use it to drop the obviously bad, then a real oracle or a human for the details.
A compiler is a strong oracle but only covers "types right," not "logic right." Strong oracles have narrow coverage, weak ones wide—how to trade off?
An oracle has two orthogonal dimensions: strength (pass really means correct) and coverage (how many error kinds it catches). Don't expect one oracle to have both. The fix is stacking several narrow-but-strong oracles to approximate wide coverage: types cover interfaces, unit tests cover examples, property/metamorphic cover invariants, fuzzing covers crashes. Each strong layer adds a union of coverage, all independent. The anti-pattern is using one "wide but weak" oracle (LLM-judge) to fake full coverage—wide, yes, but no cell is trustworthy. Prefer many trustworthy narrows over one dubious wide.
Metamorphic testing claims it "needs no oracle." Does that contradict "you need an independent oracle"?
No contradiction—an MR is itself a derived oracle. It doesn't need "the reference answer for a single input," but it does need a relation from the math of the problem (sort(shift(x))==shift(sort(x))). That relation is independent of the model: it comes from the definition of sorting, not from any generation. So "needs no oracle" means no point-to-point reference answer, not no judge—the judge became "the relation must hold." That's the most practical trick: when you can't build a reference answer, find a relation that naturally holds in the problem.
If this step only has an LLM in hand and no other oracle, what do I do?
Reshape the task into a form verifiable by a non-LLM—that's the engineering essence of grounding. Have the LLM emit not a conclusion but executable code (judge = compiler/tests), checkable citations (judge = retrieval/URL existence), a structured schema (judge = validator), or recomputable intermediates (judge = calculator/SQL). The core move: turn "judge whether a piece of prose is right" (no oracle) into "judge whether a machine-checkable artifact is right" (has oracle). Where it truly reduces to human taste, honestly keep a human checkpoint—don't fool yourself.
How does an oracle's cost structure change at scale, and what does that mean for "inventory before you amplify"?
Strong oracles (compile/test) have ~0 marginal cost—one run or ten thousand is about the same; scaling is nearly free. Weak oracles (human review, LLM-judge) cost linearly or superlinearly with volume: humans tire and drift, LLM-judge burns tokens. So scaling pushes cost onto "the steps still relying on humans/weak oracles," turning them into bottlenecks—the structural reason experienced devs slowed in METR. The implication is direct: before amplifying, migrate steps up the ladder (build strong oracles); where you can't, budget the human effort. A no-oracle step doesn't get cheaper when automated—automation just relocates the verification debt, it doesn't erase it.

// FURTHER READING