Chapter 21 of 30

The Agent Cannot Grade Its Own Homework

Concepts

CHAPTER 21 — The Agent Cannot Grade Its Own Homework

STATUS

Pinned Stage 21 bundle executed 2026-09-14 and independently verified. Full draft from the executed verifier-independence demo, the measured Stage 29B bundle, source inspection at CodeAI 1b3c7a2 (committed), teaching fragments, and the pinned verification-binding run.

EDITORIAL PASS (2026-09-14)

  • Visual-mechanism pass 2026-09-14: added generator-vs-verifier flowchart with binding diamond, PASS/FAIL/ERROR outcomes, and INCONCLUSIVE dashed as enum-only (no shipped producer). Matches the run_check gate.
  • FROZEN-EVIDENCE MISSTATEMENT FIXED: “The preregistered decision favored the ladder on cost per accepted outcome” -> ladder cheaper per accepted outcome but FAILED its frozen adoption rule (one accepted-but-wrong, A05).
  • Prose description of run_check order replaced by reduced real gate excerpt (single resolver read; unavailable/raising/mismatch -> ERROR, 0 verifier calls; raising verifier -> ERROR; runtime overwrites observed_target_state_hash). Duplicative “revision semantics” paragraph tightened to three properties.
  • T07 quote now full corpus text incl. 600-second distractor (verified in preregistration.json); T10/A05 verified.
  • Removed process note (“plan’s earlier wording”); SWE-bench in-text year 2023 -> 2024 to match reference; “working-tree revision” -> a1b562a; forced-wrong “cheaper rung” -> “next rung” (go-cheap after scripted free).
  • Score ~912 -> ~952; evidence dimension capped by owed Stage 21.

CENTRAL QUESTION

What observation can establish that an action met its check?

THESIS

Asserted != executed != passed != established. A generator’s success claim is not verification; a check that ran is an observation with a verdict; a PASS is that verifier’s verdict on that request, state, and environment, not proof of the underlying property. Independence (separate from the claim), adequacy (testing the property that matters), and binding (run against the exact state being accepted) fail separately and must each be inspectable.

SECTION OUTLINE

  • The patch that passed: constructed scene, success claim beside a failing check; bridge from Ch20.
  • Asserted, executed, passed, established: the distinction with the three-property table; SWE-bench, Cobbe et al., Zheng et al. woven in with limits; mappings labeled the book’s own.
  • What CodeAI does: CheckRequest/CheckResult/CheckVerdict; LocalCommandVerifier exact PASS/FAIL/ERROR behavior and non-hermetic env; run_check order with the single-observation revision vs the 7a0d43b triple-read gate; observed_target_state_hash; legacy _apply_check_to_claims naming conflict; _InProcessCheck; frozen ttl-check-v1 semantics.
  • Constructed executed teaching fragments: claim-vs-FAIL; stale/missing/matching binding.
  • The executed demo read exactly: cases A–E with the narrowed case-B limitation (different commands, neither bound to patch.txt; no causal repair claim).
  • The measured stage: A05 false acceptance (grounding yes, ambiguity no; accepted wrong); T07/T10 false rejections (correct values, strict parser, sent to a person); forced-wrong control; checker semantics frozen, not tuned.
  • Checking it without trusting it: demo verify_verifier.py predicates + seeded-flip check and its summary-only limit; Stage 29B five seeded corruptions rejected, fresh reprojection; owed Stage 21 design (neutral, seven cases, independent-reader spec).
  • What this is not; seven numbered weaknesses; Do this now + prompt; failure modes; established; Next -> Ch22.

LOAD-BEARING CLAIMS

  1. Generator success claim beside an exit-1 check -> FAIL with stdout preserved; stale target_state_hash -> ERROR with 0 verifier invocations; timeout + missing binary -> ERROR; syntax-only check PASSes semantically wrong JSON. [demo]
  2. Demo case B is one failed check plus one later different passing check; neither command read or bound patch.txt, so the bundle does not establish repair-caused-pass. [demo]
  3. A05 (staging 1 min / production 10 min): free model answered 600 quoting “10 minutes”; grounding check passed; accepted wrong vs frozen gold; ambiguity untested. [measured]
  4. T07 (gold 7200) and T10 (gold 5400): correct values returned, parser could not verify the quotes (0 / 2 durations found); sent to a person; preregistered as kept check properties, not tuned. [measured]
  5. Forced-wrong 45000-seconds answer failed seconds_do_not_follow_from_quote and escalated; a checker earns its PASS after its FAIL is seen on a seeded defect. [measured]
  6. Baseline run_check read the resolver up to 3 times and ran the verifier with no binding when no resolver existed; the revision reads once, ERRORs on mismatch/unavailable/ resolver-failure with 0 calls, records observed_target_state_hash, overwrites verifier forgery, and normalizes a raising verifier into durable ERROR (exception preserved, exactly 1 call, check.completed appended, no claim promoted). [source, revision]
  7. LocalCommandVerifier emits PASS/FAIL/ERROR only; INCONCLUSIVE is unreachable there and in _InProcessCheck; env is inherited + overrides, not hermetic. [source]
  8. Legacy _apply_check_to_claims promotes/refutes every named claim on PASS/FAIL without testing adequacy; naming is not testing; left in place as a named conflict. [source]
  9. A raising verifier no longer escapes: normalized ERROR is recorded as distinct from the thing checked failed. Open: the hash is an observation, not a lock; the resolver is trusted configuration. [source]
  10. Independence/adequacy/binding fail separately (A05 bound+independent but inadequate; T07/T10 bound+independent but strictly inadequate; forced-wrong adequate+bound); SWE-bench supports execution-graded evaluation bounded by tests; Cobbe supports role separation with a fallible learned verifier; Zheng bounds judge agreement to preference, not correctness. [argued + papers; mappings ours]
  11. Pinned Stage 21 verification-binding run executed 2026-09-14: ten checks in their correct epistemic states (PASS/FAIL/ERROR), claim effects directional and ordered (REPRODUCED then REFUTED), binding refusals with zero invocations, INCONCLUSIVE recorded as unproducible rather than fabricated. Independent stdlib-only verifier; five seeded corruptions rejected. [measured: experiments/applied-ai/evidence/verification-binding/2026-09-14-1b3c7a2]

EVIDENCE (verified in this pass)

  • Measured: experiments/applied-ai/evidence/verification-binding/2026-09-14-1b3c7a2; preregistration.json (frozen before execution), run_verification_binding.py (real local commands, disposable fixture), verify_verification_binding.py (stdlib-only; –check-corruptions), ledger.sqlite, fixture snapshots, invocation receipts, chapter-evidence-report.md. Ten checks as above; verifier findings pass and 5 seeded corruptions rejected. CodeAI 1b3c7a2 committed; no architecture changes.
  • CodeAI 7a0d43b baseline vs working tree: adapters.py observed_target_state_hash on CheckResult; runtime.py single-observation run_check gate + forgery overwrite.
  • tests/test_check_binding.py 8 (match/mismatch/missing/raises; moving-state single read; unbound takes no reading; raising verifier bound + unbound: durable ERROR, 1 call, reopen, no promotion); kept green: tests/test_action_observation.py 8, tests/test_directive_registration.py 7; fragment probe executed 2026-09-14 (FAIL beside claim; mismatch ERROR 0 calls; unavailable ERROR 0 calls; match binds state-A).
  • Demo verifier-independence/: A FAIL exit 1 stdout kept; B PASS (different command, unbound — limitation); C syntax PASS on threshold 9999; D timeout ERROR 0.5 s + missing-binary ERROR; E mismatch ERROR 0 calls; 6 requested / 6 completed / 5 runs.
  • Stage 29B 2026-09-14-7a0d43b: ladder 32 correct + 1 wrong (A05) vs top-first 31 + 0; T07/T10 to a person (ladder: 3 calls each); forced-wrong escalated then resolved at 45; 5 seeded corruptions rejected; 4 ledgers reprojected identically. TTL checker untouched.
  • Demo verifier: summary predicates + flipped-A mutation caught; checks the summary, does not re-run commands (stated limit).

PAPERS (read from primary sources 2026-09-14, arXiv abstracts opened)

  • Jimenez et al. SWE-bench. ICLR 2024 (abstract): 2,294 real-issue problems, 12 repos, model edits code, tests grade; best early model 1.96%. Supports execution over assertion; limit: bounded by test coverage/environment. Paraphrased.
  • Cobbe et al. Training Verifiers. 2021 (abstract): GSM8K 8.5K; rank candidates by learned verifier; verification scales with data. Supports role separation; limit: learned judge, not proof. Paraphrased.
  • Zheng et al. LLM-as-a-Judge. NeurIPS 2023 (abstract): strong judges ~80% human agreement (= human-human rate) with position/verbosity/self-enhancement biases. Boundary case: agreement != functional correctness. Paraphrased.
  • Dropped from plan: BrowseComp, PaperBench, SWE-Lancer, SWE-Bench Pro Verified reward-hacking report, AI Scientist (all remain [verify-source]/hypothesis; not cited).

CONTROLS / LIMITATIONS

Demo: local commands only, no models; stub state resolver; B unbound to patch.txt; verifier checks summary, not re-execution. Stage 29B: 40 synthetic items, one run per arm, no variance; strict blind checker; declared prices/human cost; sequential wall latency; time-dependent free tier; one rung configuration; L-D spend defect preserved. Revision: regression tests only, no pinned stage. Resolver quality, env capture, INCONCLUSIVE, concurrent mutation, name-based promotion all open.

DEPENDENCIES

Ch18 (claim/evidence discipline; naming vs testing conflict), Ch19 (reported vs observed vs expected; observation provenance), Ch20 (permission precedes verification; grant checks do not establish correctness). Ch14 acceptance, Ch16 effects interval, Ch17 preserved observation (cited where load-bearing).

FORWARD BRIDGE

Refused/failed work returns for another attempt through effects that must be controlled; Ch22 owns retry/replay, idempotency reuse, and the observation-verification-acceptance interval including concurrent mutation.

OPEN ITEMS

  • Stage 21 verification-binding: EXECUTED 2026-09-14. Follow-ups that remain open: adequacy enforcement, hermetic environments, INCONCLUSIVE production, mutation-proof binding across the observation–verification interval.
  • INCONCLUSIVE producer only if a legitimate seam exists; not fabricated.
  • _apply_check_to_claims adequacy conflict left for the claims system, not the binding fix.
  • Chapter 19/20 needed no correction in this pass (their defect/gap accounts match source).
  • Hashed *.log-style artifacts need git add -f if ever committed; everything stays uncommitted pending the Ch19–22 consolidation.

Explain this chapter with AI

Copy this prompt into ChatGPT, Claude, Gemini, a local model, or another AI.

Apply this chapter with AI

Copy this prompt into ChatGPT, Claude, Gemini, a local model, or another AI.

Part 4 — Make It Safe and Verifiable

The patch that passed

Constructed scene. A patch arrives with a note: “all tests pass.” The note is confident and unchanged while you read it. Then you run one small check against the actual file, and it fails.

Keep both in front of you. The sentence says success. The command says failure. One of them examined the artifact; the other one is a report about it. The reader who can point to which is which already understands this chapter.

Chapter 20 ended with permission: who may invoke the worker. Permission does not establish that the resulting state satisfies the property the task cares about. A worker that was allowed to act can still produce the wrong bytes, and a worker that claims success can still be wrong about what it did.

What observation can establish that an action met its check?

Asserted, executed, passed, established

The distinction is asserted ≠ executed ≠ passed ≠ established.

Stage What happened What remains open
Asserted Someone, or some model, said the work succeeds Whether anything examined the artifact
Executed A check ran against a target What verdict it returned
Passed This verifier returned PASS for this request, state, and environment Whether it tested the property you care about, on the state you are accepting
Established A bounded property holds for an identified state, within the check’s scope Everything outside that scope

Three properties make the last row precise. They are independent axes, not three names for care:

Property Question
Independence Is the check separate from the generator’s own success claim?
Adequacy Does the check test the property we actually care about?
Binding Did the check run against the exact state, artifact, and claim now being accepted?

A verifier can be independent but inadequate: it runs separately and tests the wrong thing. It can be adequate but unbound: it tests the right property against stale state. It can be correctly bound but irrelevant: the right artifact, the wrong question. This chapter keeps one measured and one executed body of evidence on the page to show each failure with its companions held constant.

The evaluation literature earns its place here in exactly this shape. SWE-bench tasks a model with editing a real codebase to resolve real GitHub issues and grades the result with the repository’s own tests: 2,294 problems across 12 Python repositories, with the best early model resolving under 2% (Jimenez et al., 2024). That supports checking changes by execution rather than accepting a completion statement. It does not establish that arbitrary tests prove correctness; the verdict is bounded by the tests’ coverage and their environment. The mapping to CodeAI’s check boundary is the book’s own.

Cobbe and colleagues separate candidate generation from verifier-based selection on grade-school math: they generate many candidate solutions and keep the one a trained verifier ranks highest, and verification improves with data better than the fine-tuning baseline (Cobbe et al., 2021). The relevant lesson is role separation, not infallibility. Their verifier is learned, not a deterministic proof checker; a learned judge ranking candidates is not ground truth about any single answer. The application of that separation to CodeAI’s local checks is the book’s mapping.

Zheng and colleagues measure LLM-as-judge agreement against human preferences and find strong judges matching humans at roughly the human–human rate, alongside position, verbosity, and self-enhancement biases with limited reasoning ability (Zheng et al., 2023). Preference agreement is a boundary case for this chapter: useful where exact checks are unavailable, but agreement about which answer reads better must not be presented as proof that an artifact behaves correctly. That boundary is the book’s use of their result, not theirs.

What CodeAI does

The verification types are small. CheckVerdict has four members: PASS, FAIL, INCONCLUSIVE, and ERROR. CheckRequest carries the check’s identity, its task, the claim IDs it targets, the command with working directory, timeout, and environment overrides, plus target, target_state_hash, and environment_hash. CheckResult carries the verdict with exit code, preserved stdout and stderr, details, error, and — since the Chapter 21 revision, now in CodeAI a1b562a — the runtime’s pre-check state reading. 1

LocalCommandVerifier.run has exactly three reachable outcomes. An empty command is ERROR. A timeout is ERROR with partial output preserved where available. An operating-system failure to launch is ERROR. Otherwise the exit code decides: zero is PASS, nonzero is FAIL. There is no path in the inspected code that returns INCONCLUSIVE. The subprocess inherits a copy of the process environment with the request’s overrides applied; a short override list is not a hermetic environment. Standard output and standard error are captured and returned on the result. 2

The verifier itself receives only the command it was given. Which target state the acceptance decision concerns is decided elsewhere. Binding, if it happens, happens in the runtime before the verifier is invoked.

Current run_check appends check.requested, then runs this gate, reduced from the source:

observed_state, binding_error = None, None
if request.target_state_hash is not None:
    try:
        observed_state = self._current_state_hash()           # exactly one reading
    except Exception as exc:
        binding_error = f"target state observation failed: {type(exc).__name__}: {exc}"
    else:
        if observed_state is None:
            binding_error = "target state unavailable: requested binding cannot be checked"
        elif request.target_state_hash != observed_state:
            binding_error = f"target state mismatch: expected {...}, observed {...}"

if binding_error is not None:
    result = CheckResult(check_id=..., verdict=CheckVerdict.ERROR, error=binding_error)
else:
    try:
        result = verifier.run(request)
    except Exception as exc:                                   # a raising verifier is ERROR, never PASS
        result = CheckResult(check_id=..., verdict=CheckVerdict.ERROR,
                             error=f"verifier raised {type(exc).__name__}: {exc}")
    result = self._finalize_check_result(result)

# Never let a verifier supply the runtime's pre-check observation.
result = replace(result, observed_target_state_hash=observed_state)

It then appends check.completed, causally linked to the request, and applies the result to any claims the request names. 3

The single read matters. At baseline 7a0d43b, the gate called _current_state_hash() up to three times — once per comparison operand and once more for the error message — and when no resolver was configured, the gate’s condition short-circuited to false and the verifier ran with no binding at all. A requested state that could not be established fell through as if no state had been requested. 3

The revision gives three properties, each visible in the gate above. The resolver is read once per check decision. A binding that cannot be established — unavailable, raising, or mismatched — produces ERROR with zero verifier invocations. And the reading that allowed the verifier to run is stored as observed_target_state_hash on the result and its check.completed event, overwriting anything the verifier supplied. That field records the pre-check observation, not what the verifier consumed, and it is not a lock against concurrent mutation. These are source-inspected properties of the revision, not results of the older bundles. 4

A binding ERROR leaves claim standing untouched. _apply_check_to_claims acts only on PASS and FAIL for the request’s named claims, so a check that never ran leaves claim standing untouched. The same holds for a verifier that raised: the normalized ERROR hits neither the promotion nor the refutation branch. That is the revision’s inspected behavior. 5

The generator’s claim and the verifier’s observation travel independent paths to separate verdicts:

    flowchart TD
    subgraph GEN["generator side"]
        M(["model<br/><i>says done</i>"])
    end
    subgraph VER["verifier side — independent path"]
        direction TB
        B{"bound to a<br/>fresh observation?"}
        B -->|"stale / unreadable"| E["ERROR<br/><i>verifier never runs</i>"]
        B -->|"bound"| V["verifier runs<br/><i>against the bytes</i>"]
        V --> P["PASS"]
        V --> F["FAIL"]
        V -.->|"procedure breaks"| E
        V -.->|"no shipped path produces it"| I["INCONCLUSIVE<br/><i>exists in the enum only</i>"]
    end
    M -.->|"a success claim<br/>is not verification"| VER
    style I stroke-dasharray: 4 4
  

The legacy promotion itself is the chapter’s named conflict. On PASS, every claim the request names receives evidence class E3_REPRODUCED; on FAIL, every named claim is marked refuted. The runtime does not ask whether the command tested anything about those claims. Chapter 18 showed the shape of this failure: a claim promoted by a check whose command printed “nothing tested.” The newer claim API in that chapter requires a check to name the claim before its verdict counts as evidence, but naming is still not testing. A PASS that names a claim establishes support only if the check was adequate to the claim — which no field on the request can certify. This revision leaves that path in place and documents it; redesigning claim semantics belongs to the claims system, not to a binding fix. 5

The in-process check used by the execution ladder follows the same verdict discipline: the caller’s deterministic check runs, its boolean becomes PASS or FAIL, and a crashing check becomes ERROR, never PASS. 6

The Stage 29B TTL checker, ttl-check-v1, is deliberately narrow and stays that way for this chapter. It requires the output to parse as one JSON object holding an integer seconds and a non-empty quote; the quote must appear verbatim in the input; the quote must contain exactly one duration; and the seconds must equal that duration. Anything else fails with a named reason: output_not_json, no_value_to_verify, seconds_not_a_whole_number, quote_missing, quote_not_in_input, quote_has_0_durations, quote_has_2_durations, or seconds_do_not_follow_from_quote. Those semantics are frozen historical evidence about that stage, not a component this chapter tunes. 7

One constructed check fails beside a success claim

Constructed example using actual APIs. The generator’s assertion and the check below are illustrative; this fragment was executed against the working tree as teaching code, not as a pinned stage run. 3

generator_claim = {
    "status": "success",
    "tests_passed": True,
}

result = runtime.run_check(
    CheckRequest("check-1", "task-1", claim_ids=("claim-1",)),
    verifier=verifier,
)
assert result.verdict == CheckVerdict.FAIL
assert "real stdout" in (result.stdout or "")

The assertion is ignored while the target is examined, so the generator’s sentence and the verifier’s observation sit beside one another for the reader to inspect the contradiction without judging the model’s tone. The FAIL verdict with preserved stdout is what independence buys: not correctness, just a second observer that refuses to take the first one’s word.

The executed demo, read exactly

The preserved verifier-independence demo runs six local checks with no network and no models, and its verifier wrapper counts invocations. 8

Case What ran Verdict What it establishes
A Generator JSON claims success; the check command exits 1 FAIL, real stdout preserved Assertion ≠ observation
B A later, different check command exits 0 PASS A later check passed; see the caveat below
C A JSON-syntax check on semantically wrong {"threshold": 9999} PASS The oracle certifies syntax only
D Sleep-30 with a 0.5 s timeout; a missing binary ERROR / ERROR Procedure failure, not a verdict on the claim
E Requested state-B against resolver state-A ERROR, 0 added verifier invocations Stale targets never reach the command

Case B needs its limitation stated plainly. The failing and passing commands were different commands, and neither command read or bound itself to the patch.txt file whose “repair” the narrative discussed. What the bundle preserves is: one constructed check failed, and another later check passed. It does not by itself establish that a target repair caused the change. Repair happened, the later check changed, but the same check observing the repaired target and passing is not in the evidence. The chapter’s teaching point survives the narrowing: passing results do not travel across revisions, and a state hash identifies a target without making concurrent mutation impossible. 8

Case C is the demo’s adequacy exhibit in miniature: a syntactically valid but semantically wrong artifact passes a syntax-only checker. Independence held — the checker ran separately from whatever produced the file — and adequacy failed. The PASS certifies JSON syntax and nothing about the threshold. 8

The measured stage: grounding verified, ambiguity not

Stage 29B ran a 40-item TTL corpus through two arms at CodeAI 7a0d43b: a ladder of rule, free, cheap, and strong rungs against sending every item to the strong model first. The ladder was cheaper per accepted outcome, but it failed its frozen adoption rule because it accepted one wrong answer where the other arm accepted none. That wrong answer is this chapter’s business. 9

A05 is the chapter’s central adequacy case, and the checker is left exactly as it was. The input states two TTLs: 1 minute for staging, 10 minutes for production, with no single correct answer. The free rung answered {"seconds": 600, "quote": "10 minutes"}. The quote appears verbatim in the input, it contains exactly one duration, and 600 follows from it — so the check passed, and the answer was accepted and wrong against the frozen gold. Grounding and derivability were verified; ambiguity and relevance were not. The check answered “does this value follow from a quoted passage?” while acceptance needed “does this input determine one value?” Those are different properties, and no tuning of the duration parser would close the gap without changing what the check claims to test. 10

T07 and T10 are the complementary false rejections, also preserved untuned. T07 reads “Cache TTL: 7200 (seconds). Connection pool idle timeout: 600 seconds.” with gold 7200; the parser finds no duration expression in a quote of the form 7200 (seconds), so correct answers were declined as quote_has_0_durations. T10 reads “Entries are cached for 1 hour 30 minutes” with gold 5400; the quote holds two durations, declined as quote_has_2_durations. Both items went to a person after repeated model calls in the ladder arm. Correct answer ≠ answer verifiable by this checker. The preregistration had already recorded both as properties of the check, kept rather than tuned away. 10

The forced-wrong control shows the check doing its actual job: a scripted 45000-seconds answer failed seconds_do_not_follow_from_quote, the run escalated, and the next rung resolved the item correctly. A checker earns its PASS only after its FAIL has been seen on a seeded defect. 10

Together the three cases separate the axes. A05 was independent and bound but inadequate. T07 and T10 were independent and bound with an inadequate parser in the other direction — strict where the property needed leniency. The forced-wrong case was adequate to its narrow property and correctly bound. None of these sentences can be shortened to “the verifier worked” or “the verifier failed” without losing which property held.

Binding, shown at the API

Constructed examples using actual APIs. Each fragment below was executed against the working tree as teaching code; the assertions held. They are not pinned stage evidence. 3

Requested state state-B, observed state-A: the verifier never runs.

result = runtime.run_check(
    CheckRequest("check-2", "task-1", target_state_hash="state-B"),
    verifier=verifier,
)
assert result.verdict == CheckVerdict.ERROR
assert verifier.calls == 0

A request that requires binding with no resolver configured returns ERROR, with no observation invented and no verifier call.

result = Runtime(SQLiteLedger()).run_check(
    CheckRequest("check-3", "task-1", target_state_hash="state-A"),
    verifier=verifier,
)
assert result.verdict == CheckVerdict.ERROR
assert result.error == "target state unavailable: requested binding cannot be checked"
assert verifier.calls == 0

Matching binding lets the verifier run, and the reading that permitted it stays on the result:

result = runtime.run_check(
    CheckRequest("check-4", "task-1", target_state_hash="state-A"),
    verifier=verifier,
)
assert result.observed_target_state_hash == "state-A"

The regression tests behind the revision cover the further cases: a resolver that raises (observation failure, ERROR, zero calls), a resolver read exactly once even when the state moves between reads, an unbound check that takes no reading at all, a verifier-supplied observation overwritten by the runtime’s own, and a verifier that raises (durable ERROR with the exception preserved, exactly one invocation, completion surviving ledger reopen, no claim promoted). 11

Checking it without trusting it

The demo’s independent verifier imports no CodeAI code for its predicates. It reads the preserved summary and requires A to FAIL with stdout, B to PASS, C to PASS as a bounded oracle, D to be ERROR/ERROR, and E to short-circuit with zero added invocations; it also mutates a copy with a flipped A verdict and requires rejection. 12

That independence has a stated limit. The verifier reads the producer’s summary fields without re-running the commands or re-reading the targets. It would catch a mislabeled verdict, not a command that never examined its target — which is exactly the case-B limitation above, found by reading the producer script rather than by any automated predicate. An independent check of a summary is not an independent repetition of the work. 8

The measured stage checked harder. Five seeded corruptions — a flipped check verdict, zeroed spend, a removed rung, inflated acceptances, a dropped receipt — were each rejected with new problems beyond the baseline failure, and fresh reprojection of all four ledgers matched. The flipped-verdict corruption is the one this chapter leans on: T07’s recorded PASS re-checked as quote_has_0_durations, so a tampered verdict fails re-derivation. 10

The pinned Stage 21 bundle has now run, under a protocol frozen before execution in experiments/applied-ai/evidence/verification-binding/2026-09-14-1b3c7a2/, with real local commands against a disposable fixture and no network. An independent stdlib-only verifier reconstructs every row — requested binding, recorded observation, verdict, invocation count, and claim effects — from the ledger, the fixture bytes, and the receipts. The success criterion was not “all PASS”: the table holds PASS, FAIL, and ERROR side by side, each correct.

Case Epistemic state
Genuine claim contradicted by an independent check asserted claim.recorded sits beside a FAIL; the ledger then carries claim.status REFUTED — said and established stay separable
Weak vs strong oracle, same artifact presence check PASSes (with claim.evidence REPRODUCED) while the exact-bytes check FAILs (with REFUTED after REPRODUCED, in ledger order)
Stale binding ERROR naming the mismatch, the observation preserved as the mutated-bytes hash, zero verifier invocations
Unavailable resolver, two ways ERROR saying unavailable (None) and ERROR naming RuntimeError (raising), both with zero invocations
Raising verifier durable ERROR with the verifier raised prefix and the type, distinct from a target failure — and the one invocation that produced it
Procedure failures 30-second sleep on a 1-second budget times out; a nonexistent binary fails at spawn with no exit code — both ERROR, never PASS
Negative controls the exact-bytes checker PASSes on correct bytes and FAILs on the seeded defect before its PASS counts
INCONCLUSIVE absent from all ten completions: the shipped verifier returns only PASS/FAIL by exit code and ERROR otherwise, so the stage records the gap instead of fabricating a case

Seeded corruptions — a flipped verdict, an altered observation, a dropped claim effect, an ERROR rewritten to PASS, an inflated receipt — are each rejected with the failure named. The run’s stated limits are kept: adequacy unenforced, no hermetic execution, observation not a lock, and concurrent mutation between observation and verification still belongs to retry policy.

What this is not

  • Not correctness. A PASS is this verifier’s verdict on this request, state, and environment — never a proof of the property the reader cares about.
  • Oracle design stays elsewhere. The runtime can refuse unbound checks; it cannot tell whether a command tests the right property. Adequacy stays with whoever wrote the checker.
  • Not hermetic execution. The environment is inherited plus overrides, and the state reading is an observation, not a lock.
  • Naming is not support. A passing check that names a claim promotes it under the legacy path whether or not the command tested it.
  • No INCONCLUSIVE implementation. The enum member exists; no inspected verifier returns it.

Where it is still weak

  1. Adequacy is unenforceable at the boundary. Nothing in the request certifies that the command tests the claim it names. 5
  2. INCONCLUSIVE has no producer. A verifier that ran but cannot settle the property has no honest verdict to return except ERROR, which misdescribes a completed procedure. 2
  3. The environment is not captured. Overrides are recorded on the request; the inherited remainder is not. 2
  4. A hash identifies; it does not freeze. Concurrent mutation between observation and verification, or between verification and acceptance, is outside the binding record. Retry policy owns this interval. 3
  5. The resolver is trusted configuration. A constant or partial resolver supplies a poor reading, and the runtime does not validate its coverage. 13
  6. The pinned binding evidence has run. What remains: adequacy enforcement, hermetic environments, INCONCLUSIVE production, and mutation-proof binding across the observation–verification interval.

Do this now

Thirty minutes. Take one success claim and refuse to believe it.

  1. Pick a generated artifact with a confident completion sentence. Keep the sentence unchanged and write a check that examines the artifact’s bytes, not the sentence.
  2. Run the check. Preserve the command, working directory, environment overrides, exit code, stdout, and stderr beside the claim.
  3. Weaken the checker deliberately until it passes something wrong — syntax for semantics, presence for correctness. Write down which property each version tests.
  4. Bind the check: request a target state hash, then change the target and re-run. Confirm the stale request refuses before the command runs, and note what the refusal does and does not prove.

If you are building with an assistant:

Separate the generator's success claim from verification.
Run an independent check against the artifact's bytes and preserve
command, working directory, environment overrides, exit code, stdout,
and stderr. Test the checker against seeded defects before trusting its
PASS. Bind each check to an exact target state with a single runtime
observation; refuse stale or unreadable states before invoking the
verifier, and record the observation used. Do not present PASS as proof
of adequacy, and do not describe a later passing check as caused by a
repair unless the same check observed the repaired target.

Failure modes

  • Grading its own homework. Accepting the generator’s success sentence as the verification.
  • Reading PASS as proof. Treating one verifier’s verdict as establishment of the underlying property.
  • Testing the wrong property well. A deterministic, grounded, perfectly implemented check for something nobody needed.
  • Checking yesterday’s artifact. Running a strong oracle against state that has since moved.
  • Counting the repair twice. Describing a later different check’s PASS as caused by a fix the check never observed.
  • Promoting by naming. Letting a passing check support claims it never tested because the request listed them.
  • Confusing the verifier’s failure with the artifact’s. An ERROR says the checking procedure did not complete; it is not a FAIL, and it still belongs in the ledger.
  • Hiding procedure failure in ERROR. Treating “the checker crashed” as neutral when acceptance still needs an answer.

What this chapter established

  • Asserted, executed, passed, and established answer different questions; a PASS is one verifier’s verdict on one request, state, and environment.
  • Independence, adequacy, and binding fail separately: the demo shows a FAIL beside a success claim, a syntax PASS on a wrong artifact, and a stale-state refusal with zero invocations. 8
  • The measured stage shows grounding verified with ambiguity missed (A05, accepted wrong), correct answers rejected by a strict parser (T07, T10, sent to a person), and a seeded wrong answer caught — with the checker semantics frozen, not tuned. 10
  • The Chapter 21 revision binds each check decision to one runtime observation, refuses unestablished or mismatched states before invocation, preserves the observation used, and normalizes a raising verifier into durable ERROR evidence — the verifier failed is recorded as distinct from the thing checked failed — with the legacy name-based claim promotion documented as an open conflict. 3
  • INCONCLUSIVE has no producer in the shipped verifier, hermetic environments and adequacy enforcement remain unbuilt, and mutation-proof binding across the interval remains open; the pinned ten-check run in experiments/applied-ai/evidence/verification-binding/2026-09-14-1b3c7a2/ establishes the separable states with an independent reader that rejects five seeded corruptions.

Next

The process can now refuse to check the wrong state, and it knows what its checks do not prove. Refused and failed work will come back for another attempt — and every retry re-enters the world through effects that must be controlled.

Continue with Retries Are Side Effects Too.

References

  • Carlos E. Jimenez, John Yang, Alexander Wettig, Shunyu Yao, Kexin Pei, Ofir Press, and Karthik Narasimhan. SWE-bench: Can Language Models Resolve Real-World GitHub Issues? ICLR, 2024. arXiv:2310.06770.
  • Karl Cobbe, Vineet Kosaraju, Mohammad Bavarian, Mark Chen, Heewoo Jun, Lukasz Kaiser, Matthias Plappert, Jerry Tworek, Jacob Hilton, Reiichiro Nakano, Christopher Hesse, and John Schulman. Training Verifiers to Solve Math Word Problems. arXiv:2110.14168, 2021. Paper.
  • Lianmin Zheng, Wei-Lin Chiang, Ying Sheng, Siyuan Zhuang, Zhanghao Wu, Yonghao Zhuang, Zi Lin, Zhuohan Li, Dacheng Li, Eric P. Xing, Hao Zhang, Joseph E. Gonzalez, and Ion Stoica. Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena. NeurIPS Datasets and Benchmarks, 2023. arXiv:2306.05685.

Implementation sources: CodeAI baseline 7a0d43b for historical behavior and the Stage 29B run; a1b562a, which contains the Chapter 19 observation, Chapter 20 child-registration, and Chapter 21 state-binding and verifier-exception revisions, for current source. The run_check gate is reduced from the source. src/codeai/adapters.py: CheckRequest, CheckResult, CheckVerdict; src/codeai/verifier.py: LocalCommandVerifier.run; src/codeai/runtime.py: run_check, _finalize_check_result, _apply_check_to_claims, _current_state_hash; src/codeai/ladder.py: _InProcessCheck; src/codeai/evidence.py: claim-evidence discipline from Chapter 18. Tests: tests/test_check_binding.py (new), tests/test_action_observation.py and tests/test_directive_registration.py (kept green). Teaching fragments were executed against the working tree as illustrative code, not as pinned stage runs. Evidence: experiments/applied-ai/evidence/verifier-independence/ (README, producer, results, independent verifier) and experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b/ (pinned run at 7a0d43b, especially run.py, chapter-evidence-report.md, preregistration.json), and experiments/applied-ai/evidence/verification-binding/2026-09-14-1b3c7a2/ (pinned ten-check run with independent stdlib verifier, five seeded corruptions rejected). Footnotes mark provenance: source notes refer to inspected code, measurement notes to pinned runs, demo notes to preserved unpinned execution. Open future work is stated in prose, not footnotes. The Stage 29B TTL checker semantics are unchanged by this chapter.


  1. Inspected code: src/codeai/adapters.py (CheckVerdict, CheckRequest, CheckResult). ↩︎

  2. Inspected code: src/codeai/verifier.py (LocalCommandVerifier.run). ↩︎ ↩︎ ↩︎

  3. Inspected code: src/codeai/runtime.py (Runtime.run_check). ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. Inspected code: src/codeai/runtime.py (Runtime.run_check, Runtime._finalize_check_result). ↩︎

  5. Inspected code: src/codeai/runtime.py (Runtime._apply_check_to_claims). ↩︎ ↩︎ ↩︎

  6. Inspected code: src/codeai/ladder.py (_InProcessCheck). ↩︎

  7. Report: experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b/run.py↩︎

  8. Unpinned demonstration: experiments/applied-ai/evidence/verifier-independence↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  9. Report: experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b/chapter-evidence-report.md↩︎

  10. Measured run: experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  11. Binding tests: tests/test_check_binding.py↩︎

  12. Unpinned demonstration: experiments/applied-ai/evidence/verifier-independence/verify_verifier.py↩︎

  13. Inspected code: src/codeai/runtime.py (Runtime._current_state_hash). ↩︎