Agents From First Principles 09: AI Agent Says It Worked When It Didn’t? Verify the Result Outside the LLM

Page content

An AI agent says:

Done. The task is complete.

That sentence is almost worthless.

The agent may have:

  • edited the wrong file,
  • changed the right file incorrectly,
  • skipped part of the request,
  • broken another subsystem,
  • failed to save its work,
  • misread a tool result,
  • passed a stale test,
  • inspected the wrong environment,
  • or simply decided that its own answer looked convincing.

The central problem is simple:

The system that produced the answer should not be the only system deciding whether the answer is correct.

This is one of the most important ideas in agent engineering.

A useful agent does not merely act.

It acts, observes, and then attempts to prove that the intended outcome actually occurred.

request
agent action
environment changes
verification
pass / fail / unknown

The difference between a demo agent and a production agent is often not the sophistication of the planner.

It is the quality of the verification boundary.

This post builds that boundary from first principles.

We will cover:

  • why model self-evaluation is weak evidence,
  • how to define success before execution,
  • deterministic verification,
  • behavioral verification,
  • invariant checking,
  • differential checks,
  • multi-layer verification,
  • stale evidence,
  • partial success,
  • false positives and false negatives,
  • verification for coding agents,
  • browser agents,
  • research agents,
  • data agents,
  • support agents,
  • DevOps agents,
  • and how to measure whether a verifier is actually useful.

The goal is not to make agents pessimistic.

The goal is to stop confusing confidence with evidence.


The failure mode people actually encounter

A large class of agent bugs has the same shape:

agent performs action
agent sees plausible output
agent infers success
agent stops
real task is still broken

Typical searches look like:

  • AI agent says task complete but it is not
  • coding agent says tests pass but code is broken
  • LLM agent hallucinated successful tool execution
  • agent claims browser form submitted but nothing happened
  • AI agent evaluates its own answer incorrectly
  • agent says deployment succeeded but service is down
  • agent completed only part of task
  • LLM verifier agrees with wrong answer

The common mistake is treating the model’s statement about the world as if it were the world.

It is not.

The model only has observations.

Those observations may be:

  • incomplete,
  • stale,
  • ambiguous,
  • summarized incorrectly,
  • or interpreted incorrectly.

So we need a stronger architecture.


1. Separate action from evidence

Suppose a coding agent edits a function.

The model might say:

I fixed the bug.

That is a claim.

A test result is evidence.

A diff is evidence.

A type checker result is evidence.

A reproduced failing case that now passes is evidence.

A model’s explanation of why the code should work is not the same category.

This gives us our first rule:

Agent output is a proposal or claim until external evidence supports it.

We can represent this explicitly.

from dataclasses import dataclass
from enum import Enum


class VerificationStatus(str, Enum):
    PASS = "pass"
    FAIL = "fail"
    UNKNOWN = "unknown"


@dataclass
class VerificationResult:
    status: VerificationStatus
    evidence: list[str]
    reason: str

Already this is better than:

success = model_says_done

Now success is an object with evidence.


2. Define success before the agent starts

A verifier cannot verify an undefined goal.

Consider:

Fix the checkout bug.

What counts as success?

Possible interpretations:

  • the failing unit test passes,
  • all checkout tests pass,
  • all repository tests pass,
  • the checkout page loads,
  • the payment API returns success,
  • no new exceptions appear,
  • the original reproduction no longer fails.

These are not equivalent.

A useful agent runtime should turn the request into explicit success criteria.

For example:

@dataclass
class SuccessCriterion:
    name: str
    required: bool
    verifier: str


criteria = [
    SuccessCriterion(
        name="original reproduction passes",
        required=True,
        verifier="reproduction_test",
    ),
    SuccessCriterion(
        name="checkout test suite passes",
        required=True,
        verifier="pytest_checkout",
    ),
    SuccessCriterion(
        name="no new type errors",
        required=True,
        verifier="mypy_checkout",
    ),
]

This changes the task from:

make something that looks fixed

into:

produce a state that satisfies these conditions

That is much closer to ordinary software engineering.


3. Completion is a predicate, not a sentence

A useful mental model is:

success = predicate(environment_state)

For a coding task:

success = tests_pass and lint_pass and reproduction_fixed

For a browser task:

success = order_status == "confirmed"

For a data task:

success = row_count_expected and schema_valid and reconciliation_error == 0

For a deployment:

success = deploy_exit_code == 0 and health_check_ok and error_rate_normal

This is much stronger than:

success = agent_says_success

4. The simplest useful verifier

Let us build one.

from dataclasses import dataclass
from enum import Enum
from typing import Callable


class VerificationStatus(str, Enum):
    PASS = "pass"
    FAIL = "fail"
    UNKNOWN = "unknown"


@dataclass
class VerificationResult:
    name: str
    status: VerificationStatus
    evidence: str


Verifier = Callable[[], VerificationResult]


def run_verifiers(verifiers: list[Verifier]) -> list[VerificationResult]:
    return [verify() for verify in verifiers]


def overall_status(results: list[VerificationResult]) -> VerificationStatus:
    if any(r.status == VerificationStatus.FAIL for r in results):
        return VerificationStatus.FAIL

    if any(r.status == VerificationStatus.UNKNOWN for r in results):
        return VerificationStatus.UNKNOWN

    return VerificationStatus.PASS

Notice something important.

UNKNOWN exists.

Production systems often force verification into:

true / false

But sometimes the system genuinely cannot tell.

Examples:

  • a third-party API timed out,
  • a browser confirmation element never appeared,
  • monitoring data has not arrived yet,
  • a test environment is unavailable,
  • a source cannot be accessed,
  • the verifier itself crashed.

Treating UNKNOWN as PASS is dangerous.

Treating UNKNOWN as FAIL may also be wrong.

The runtime should preserve uncertainty.


5. Model self-evaluation is not independent verification

Consider this pattern:

answer = model.generate(task)

verdict = model.generate(
    f"Is this answer correct?\n\n{answer}"
)

This can still be useful.

But it is not independent evidence.

The same model family may share:

  • the same blind spots,
  • the same assumptions,
  • the same missing context,
  • the same preference for fluent explanations,
  • and the same false belief that produced the answer.

If the generator hallucinates that a function exists, the evaluator may happily reason from the same hallucinated function.

A better evidence hierarchy is often:

real environment signal
deterministic verifier
independent external source
specialized learned evaluator
separate LLM judge
same-model self-critique

This is not an absolute ranking for every domain.

But it is a useful default.


6. Verification should observe the result, not the explanation

Suppose an agent modifies code.

Weak verification:

Explain why your patch fixes the bug.

Stronger verification:

Run the previously failing test.

Even stronger:

Run the reproduction.
Run adjacent tests.
Run static checks.
Inspect the actual diff.

This principle generalizes.

For a browser agent:

Weak:

Did you submit the form?

Stronger:

Does the server-side order exist?

For a support agent:

Weak:

Did you refund the customer?

Stronger:

Does the payment system show the refund transaction?

For a deployment agent:

Weak:

Did deployment complete?

Stronger:

Is the new version live and healthy?

7. Verify the intended effect, not merely the action

This is subtle.

Suppose a tool call returns:

{
  "success": true,
  "message": "deployment command completed"
}

That verifies the command.

It does not verify the service.

Likewise:

git push succeeded

does not prove:

CI passed

And:

CI passed

does not prove:

production is healthy

We need layers.

action succeeded
state changed
expected behavior observed
user goal satisfied

Each arrow may require a different verifier.


8. Local success vs global success

One of the easiest agent bugs to miss is local success.

Example:

step 1: update file      PASS
step 2: run test         PASS
step 3: commit change    PASS

The agent concludes:

task PASS

But maybe the actual requirement was:

fix bug without changing API behavior

The patch could pass the targeted test while breaking an external contract.

This is the same distinction we saw earlier with planning:

Step completion is not goal completion.

Verification should therefore happen at multiple levels.

step-level verification
component-level verification
goal-level verification

9. Verification layers

A practical agent can use several layers.

Layer 1: syntactic verification

Does the output parse?

Examples:

  • JSON parses,
  • Python compiles,
  • SQL parses,
  • configuration is valid YAML.

Layer 2: structural verification

Does it match the expected shape?

Examples:

  • required fields exist,
  • schema matches,
  • function signature preserved,
  • table columns present.

Layer 3: semantic verification

Does the result mean what we need it to mean?

Examples:

  • order status is confirmed,
  • target test passes,
  • research claim is supported by source,
  • reconciliation equals expected total.

Layer 4: behavioral verification

Does the system behave correctly when exercised?

Examples:

  • integration tests,
  • browser flow,
  • API request,
  • replayed production incident.

Layer 5: invariant verification

Did we preserve constraints that must remain true?

Examples:

  • no unauthorized file access,
  • account balance preserved,
  • latency within bound,
  • schema backward compatibility maintained.

Layer 6: regression verification

Did the change fix one thing while breaking something else?

Examples:

  • broader test suite,
  • old benchmark cases,
  • historical tasks,
  • golden outputs.

No single verifier gives every guarantee.


10. A verification pipeline

We can represent verification as a sequence of gates.

from dataclasses import dataclass
from typing import Callable


@dataclass
class Gate:
    name: str
    run: Callable[[], VerificationResult]
    required: bool = True


def verify_goal(gates: list[Gate]):
    results = []

    for gate in gates:
        result = gate.run()
        results.append(result)

        if gate.required and result.status == VerificationStatus.FAIL:
            break

    return results

This makes the evidence path visible.

A coding agent might define:

gates = [
    Gate("syntax", verify_syntax),
    Gate("targeted_test", run_targeted_test),
    Gate("type_check", run_type_check),
    Gate("regression_suite", run_regression_tests),
]

A browser agent might define:

gates = [
    Gate("page_loaded", verify_page_state),
    Gate("form_values", verify_form_values),
    Gate("submission", verify_submission),
    Gate("server_record", verify_server_record),
]

The runtime can now explain exactly where verification failed.


11. Don’t let the agent choose the easiest verifier

If the model controls both action and verification strategy, it may accidentally optimize for easy evidence.

Suppose the task is:

fix the API bug

The model could choose:

verify by reading the edited function

instead of:

verify by reproducing the API request

The first is cheaper and more likely to look good.

The second is more relevant.

So verification policy should often live in runtime code.

verification_policy = {
    "bug_fix": [
        "reproduction",
        "targeted_tests",
        "regression_tests",
    ],
    "data_migration": [
        "row_counts",
        "schema_checks",
        "reconciliation",
    ],
}

The model may help instantiate the checks.

But the runtime should control the minimum evidence bar.


12. Verification should be tied to the original request

Agents often drift.

A user asks:

Add pagination without changing the existing response schema.

The agent may successfully add pagination.

But it may also change the schema.

If the verifier only checks:

pagination works

it will declare success.

The success criteria should therefore contain both:

required effect

and:

preservation constraints

For example:

criteria = {
    "must_change": [
        "pagination_supported",
    ],
    "must_preserve": [
        "response_schema",
        "authentication_behavior",
    ],
}

This is especially important for coding agents.


13. Verification for coding agents

Coding agents are one of the clearest applications because software already has many external verifiers.

A useful hierarchy might be:

patch
syntax / compile
static analysis
targeted test
reproduction case
related regression tests
full suite if justified
benchmark / performance checks

Common failure:

agent edits code
agent reads code
agent says fix looks correct

Better:

agent edits code
runtime runs reproduction
runtime runs tests
runtime checks diff
runtime reports evidence

A simple verifier:

import subprocess


def run_command(name: str, command: list[str]) -> VerificationResult:
    completed = subprocess.run(
        command,
        capture_output=True,
        text=True,
    )

    if completed.returncode == 0:
        return VerificationResult(
            name=name,
            status=VerificationStatus.PASS,
            evidence=completed.stdout[-2000:],
        )

    return VerificationResult(
        name=name,
        status=VerificationStatus.FAIL,
        evidence=(completed.stdout + completed.stderr)[-2000:],
    )

Then:

result = run_command(
    "targeted_test",
    ["pytest", "tests/test_checkout.py", "-q"],
)

The agent cannot talk its way around the return code.


14. But tests can also lie

External verification is stronger than self-evaluation.

It is not infallible.

Tests may be:

  • incomplete,
  • stale,
  • incorrectly scoped,
  • skipped,
  • mocked too heavily,
  • testing the wrong behavior,
  • or accidentally bypassed.

So:

Passing a verifier means passing that verifier, not proving universal correctness.

This is why verification provenance matters.

Store:

  • what ran,
  • against which state,
  • in which environment,
  • with what inputs,
  • at what time,
  • and what output it produced.

15. Stale verification is a real agent bug

Imagine:

1. tests pass
2. agent edits file again
3. agent reports tests passed

Those tests no longer verify the final state.

This is a surprisingly easy bug to build into agent runtimes.

The fix is to bind evidence to state identity.

For code, use things like:

  • commit SHA,
  • tree hash,
  • file hashes,
  • patch ID,
  • workspace snapshot ID.

For data:

  • dataset version,
  • table snapshot,
  • row checksum,
  • partition ID.

For deployments:

  • artifact version,
  • image digest,
  • deployment ID.

For browser workflows:

  • transaction ID,
  • order ID,
  • server-side record version.

Verification should answer:

What exact state did this evidence verify?


16. Bind evidence to state

A simple model:

@dataclass
class Evidence:
    verifier: str
    state_id: str
    status: VerificationStatus
    details: str

Before accepting success:

if evidence.state_id != current_state_id:
    status = VerificationStatus.UNKNOWN

This prevents stale evidence from being silently reused.


17. Verification for browser agents

Browser agents frequently confuse action completion with task completion.

Example:

click submit

is not the same as:

submission succeeded

A good browser verifier may inspect:

DOM state
network response
server-side state
confirmation identifier

For example:

clicked “Place order”
HTTP 200 received
order confirmation page visible
order ID present
backend lookup confirms order exists

The backend record is usually stronger evidence than the model saying:

The confirmation page looks correct.

18. Verification for research agents

Research tasks are harder because many claims do not have a deterministic test suite.

Still, we can externalize evidence.

Suppose the agent claims:

Company X acquired Company Y in 2025.

Weak verification:

another LLM says this sounds correct

Stronger:

primary source supports claim

A research verification pipeline may check:

claim
source exists
source is accessible
source text supports claim
source date is compatible
source authority is acceptable
claim phrasing does not exceed evidence

The important design principle is:

Verification should trace the claim back to evidence, not merely score how plausible the prose sounds.


19. Research claims need claim-level verification

An answer containing ten claims should not receive one global score.

Represent claims separately.

@dataclass
class Claim:
    text: str
    source_ids: list[str]


@dataclass
class ClaimVerification:
    claim: Claim
    status: VerificationStatus
    evidence: list[str]

Now the runtime can detect:

8 claims supported
1 claim weakly supported
1 claim unsupported

instead of:

answer score = 8.6 / 10

The first is operationally useful.


20. Verification for data agents

Data agents have many deterministic signals available.

Useful checks include:

  • row counts,
  • uniqueness,
  • null rates,
  • schema constraints,
  • foreign-key consistency,
  • reconciliation totals,
  • distribution drift,
  • checksum comparisons,
  • idempotence.

Suppose an agent transforms financial records.

A strong verification pipeline might be:

input rows = 1,250,000
transformation runs
output rows = expected rows
schema valid
required keys unique
total debit = total credit
reconciliation error = 0

The model does not need to decide whether these invariants matter.

The runtime already knows.


21. Verification for support agents

Support agents often perform actions with business consequences.

Examples:

  • refund,
  • cancel order,
  • update address,
  • issue credit,
  • escalate case,
  • change subscription.

A tool returning success=true may still be insufficient.

A refund task might require:

refund tool accepted
refund transaction ID exists
amount matches requested amount
order state updated
case note recorded

This is also where authorization verification matters.

A technically successful action can still be invalid if the agent lacked authority to perform it.


22. Verification for DevOps agents

DevOps is another domain where action success and outcome success differ sharply.

Example:

kubectl rollout restart deployment/api

Return code 0 means the command was accepted.

It does not mean:

API healthy

A better chain:

command accepted
new pods scheduled
pods ready
health endpoint passes
error rate normal
latency acceptable

For a deployment agent, the verifier may be more important than the planner.


23. Application matrix

Software type Agent action Weak success signal Better verification
Coding agent edit code model says patch looks right reproduction + tests + static checks
Browser agent submit form button clicked server-side record / confirmation ID
Research agent write claim prose sounds plausible source-backed claim verification
Data agent transform table job exited 0 schema + reconciliation + invariants
Support agent refund order API accepted request transaction exists + correct amount
DevOps agent deploy service deploy command succeeded health + metrics + version check
Migration agent migrate data script completed counts + constraints + reconciliation
QA agent report bug fixed page looks okay replay failing scenario

The pattern is always similar:

verify the state that matters to the user

not:

verify that the agent performed an action

24. Verification should be adversarial enough to matter

Suppose a coding agent writes a test that simply reproduces its implementation assumptions.

Then it writes code that passes that test.

This can create circular validation.

One solution is to preserve some verification logic outside the agent’s control.

Examples:

  • hidden tests,
  • pre-existing regression suites,
  • externally specified invariants,
  • independent fixtures,
  • immutable acceptance criteria,
  • production telemetry.

The verifier should not always be something the agent can redefine after seeing the result.


25. Don’t let the agent rewrite the success criteria after failure

A dangerous loop looks like:

criterion fails
agent decides criterion was unnecessary
criterion removed
agent declares success

Sometimes requirements genuinely need revision.

But that should be an explicit event.

For example:

@dataclass
class CriterionChange:
    criterion_id: str
    old_value: str
    new_value: str
    reason: str
    authorized_by: str

Changing the definition of success should be visible and auditable.


26. Verification can fail too

Suppose the agent is correct but the verifier is broken.

Examples:

  • test fixture corrupt,
  • browser selector stale,
  • metrics endpoint unavailable,
  • database replica lagging,
  • evaluator model biased,
  • source fetch failed.

That is why we need three statuses:

PASS
FAIL
UNKNOWN

And sometimes richer failure metadata:

@dataclass
class VerificationResult:
    name: str
    status: VerificationStatus
    evidence: str
    verifier_error: str | None = None

A verifier crash is not proof that the task failed.


27. False positives and false negatives

A verifier has its own error profile.

False positive

Verifier says:

PASS

but task is actually wrong.

This is especially dangerous because it terminates the agent incorrectly.

False negative

Verifier says:

FAIL

but task is actually correct.

This can cause:

  • unnecessary retries,
  • destructive rewrites,
  • cost blowups,
  • oscillation,
  • abandonment of good solutions.

So verifier quality must be measured.


28. Measure the verifier, not just the agent

Useful metrics include:

  • verification precision,
  • verification recall,
  • false-pass rate,
  • false-fail rate,
  • unknown rate,
  • stale-evidence rate,
  • average verification latency,
  • verification cost,
  • verification coverage,
  • percentage of claims with evidence,
  • percentage of required criteria checked,
  • regressions caught after local success.

For example:

agent success rate:        78%
false success declarations: 14%

with verifier:
verified success rate:     75%
false success declarations: 2%

That could be a very worthwhile trade.


29. Verification coverage matters

Suppose a task has five required criteria.

The runtime checks two.

Both pass.

Reporting:

verified

would be misleading.

Better:

2 / 5 required criteria verified
3 / 5 unknown

A simple metric:

def coverage(results, required_names):
    observed = {r.name for r in results}
    return len(observed & set(required_names)) / len(required_names)

Completion can require both:

all required checks pass

and:

coverage == 100%

30. Verification provenance

Every verification result should answer:

  • who or what produced it,
  • what state it verified,
  • which inputs were used,
  • when it ran,
  • which verifier version ran,
  • and what evidence was returned.

For example:

@dataclass
class VerificationRecord:
    verifier_name: str
    verifier_version: str
    state_id: str
    input_hash: str
    status: VerificationStatus
    evidence: str
    timestamp: str

This matters when debugging:

Why did the agent think this was successful?

Without provenance, the answer may be impossible to recover.


31. Verification should influence the control loop

Verification is not merely a final report.

It should alter what the agent does next.

act
verify
 ├→ PASS → stop
 ├→ FAIL → diagnose / recover
 └→ UNKNOWN → gather evidence / retry verifier / escalate

This creates a verification-driven agent.

A minimal loop:

for step in range(max_steps):
    action = policy.choose(state)
    observation = executor.execute(action)
    state.update(observation)

    verification = verifier.verify(state)

    if verification.status == VerificationStatus.PASS:
        return "success"

    if verification.status == VerificationStatus.FAIL:
        state.add_failure(verification)
        continue

    state.add_uncertainty(verification)

The key is that the verifier observes the environment, not just the model’s explanation.


32. Diagnosis after failure

A verifier saying FAIL is useful.

A verifier saying why is much more useful.

Compare:

FAIL

with:

FAIL
checkout_total expected 49.99
checkout_total observed 59.99

The second gives the agent a concrete recovery signal.

So verification output should be structured when possible.

@dataclass
class FailureDetail:
    criterion: str
    expected: str
    observed: str
    evidence: str

Then the recovery policy can act on actual mismatches.


33. Verification is not the same as critique

We already built critique-and-revision earlier in this series.

They solve different problems.

Critique asks:

What seems weak about this candidate?

Verification asks:

Did the required condition actually hold?

Critique is often interpretive.

Verification should be evidential.

critique
possible defect

verification
observed pass/fail condition

Use critique when correctness is hard to measure directly.

Use verification whenever the environment exposes a measurable condition.


34. Verification is not the same as scoring

A scorer might say:

candidate quality = 0.87

A verifier might say:

required API test failed

The scorer helps rank alternatives.

The verifier helps determine whether the task is complete.

In search agents, both can be useful.

partial branch
score
choose branch
complete solution
verify

Do not confuse a high score with a passing acceptance test.


35. Verification and Best-of-N

Best-of-N becomes stronger when selection uses external evidence.

Instead of:

generate 5 answers
LLM judge picks one

we can do:

generate 5 candidates
run verifier on each
discard failing candidates
rank survivors

For coding tasks:

5 patches
compile
targeted tests
regression tests
rank survivors by simplicity / performance / diff size

Verification converts search from aesthetic selection into evidence-driven selection.


36. Verification and planning

Planning agents should verify both:

step outcomes

and:

final goal

A plan can contain:

@dataclass
class PlanStep:
    id: str
    action: str
    expected_result: str
    verifier: str

After each step:

execute
verify expected result
mark complete only if evidence supports it

This prevents the planner from treating a merely attempted action as a completed dependency.


37. Verification and memory

Memory should not store unverified success as fact.

Bad episodic memory:

2026-08-08: fixed checkout bug successfully

Better:

2026-08-08:
patch applied
reproduction PASS
checkout tests PASS
full suite UNKNOWN

Now later agents can reason from evidence quality.

The memory system should distinguish:

claimed success

from:

verified success

38. Verification and search

Search becomes much stronger when partial or complete branches can be tested against the environment.

Coding example:

branch A → 8 tests fail
branch B → 3 tests fail
branch C → 0 targeted tests fail

That is a much better search signal than:

LLM thinks branch B sounds elegant

Verification therefore provides value signals for search.

But remember:

0 targeted tests fail

still may not equal full task success.

The final acceptance gate should remain explicit.


39. Verification should sometimes happen before expensive actions

Verification is not only post-condition checking.

We can also verify preconditions.

Before a destructive action:

Is target environment correct?
Is backup available?
Is user authorized?
Is version expected?
Is operation reversible?

For example:

preconditions = [
    verify_target_environment,
    verify_backup_exists,
    verify_current_version,
]

Then execute only if all required checks pass.

This turns verification into a safety boundary.


40. Two-sided verification

A robust action often has:

preconditions
execute
postconditions

Example: database migration.

pre:
  schema version == 41
  backup exists
  migration lock acquired

execute:
  apply migration 42

post:
  schema version == 42
  row counts consistent
  constraints valid
  application health good

The model can propose the migration.

The runtime owns the gates.


41. Verification budgets

Verification has a cost.

Running an entire test suite after every tiny edit may be wasteful.

A useful strategy is staged verification.

cheap checks first
if pass
more expensive checks
if still pass
full acceptance checks

For coding:

syntax
targeted test
related module tests
full suite

For data:

schema
sample checks
partition reconciliation
full reconciliation

This reduces cost while preserving a high final evidence bar.


42. Cheap verifier first

Suppose:

  • syntax check costs 0.1 seconds,
  • targeted tests cost 3 seconds,
  • full suite costs 15 minutes.

Run them in that order.

Do not spend 15 minutes proving a patch fails to parse.

This seems obvious in normal software engineering.

Agent systems should preserve the same discipline.


43. Verification cascades

We can formalize staged verification.

@dataclass
class VerificationStage:
    name: str
    verifiers: list[Verifier]
    stop_on_failure: bool = True


def run_verification_cascade(stages):
    all_results = []

    for stage in stages:
        results = run_verifiers(stage.verifiers)
        all_results.extend(results)

        if stage.stop_on_failure and any(
            r.status == VerificationStatus.FAIL
            for r in results
        ):
            break

    return all_results

This also makes verification cost measurable by stage.


44. What if correctness cannot be directly measured?

Some tasks do not expose clean acceptance tests.

Examples:

  • writing quality,
  • strategic recommendations,
  • architecture design,
  • novel research synthesis,
  • ambiguous planning.

Then we need weaker forms of evidence.

Possible approaches:

  • rubric-based evaluation,
  • independent judges,
  • adversarial review,
  • multiple evaluators,
  • consistency checks,
  • source support,
  • constraint verification,
  • human review.

The important thing is to preserve the evidence hierarchy.

Do not pretend a subjective judge is equivalent to a deterministic invariant.


45. Use objective islands inside subjective tasks

Even subjective tasks often contain objectively checkable subproblems.

Architecture review:

Subjective:

Is this architecture good?

Objective islands:

  • does referenced component exist?
  • does dependency direction match repository?
  • do tests cover claimed behavior?
  • does benchmark output support latency claim?
  • is interface actually public?

Research synthesis:

Subjective:

Is this interpretation persuasive?

Objective islands:

  • are citations real?
  • do sources support claims?
  • are dates correct?
  • are quotations accurate?

Use deterministic checks wherever possible, then reserve model judgment for the residue.


46. Human verification is still verification

For some high-impact tasks, the correct verifier is a person.

Examples:

  • sending legally consequential messages,
  • irreversible infrastructure changes,
  • financial approvals,
  • medical decisions,
  • public publication,
  • deleting important data.

The runtime can represent this explicitly:

status = AWAITING_HUMAN_APPROVAL

rather than pretending autonomy is always the goal.

An agent that knows when it lacks sufficient verification is more useful than one that always produces a confident DONE.


47. Don’t confuse autonomy with reliability

A highly autonomous agent may:

  • make more decisions,
  • use more tools,
  • execute more steps,
  • require less human interaction.

None of those imply that it is more reliable.

A less autonomous system with strong verification may outperform it operationally.

more autonomy
more correctness

This is one of the recurring themes of this entire series.


48. Verification-first architecture

We can now describe a stronger agent architecture.

              request
          success criteria
               plan
          proposed action
          precondition checks
              execute
            observation
          postcondition checks
           goal verification
           /       |        \
        PASS      FAIL     UNKNOWN
         ↓          ↓          ↓
        stop     recover   gather evidence

The LLM participates in this system.

It is not the system of record.


49. A complete minimal verification-driven agent

Here is a compact implementation skeleton.

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable


class Status(str, Enum):
    PASS = "pass"
    FAIL = "fail"
    UNKNOWN = "unknown"


@dataclass
class CheckResult:
    name: str
    status: Status
    evidence: str


@dataclass
class AgentState:
    task: str
    step: int = 0
    observations: list[Any] = field(default_factory=list)
    failures: list[CheckResult] = field(default_factory=list)


class VerificationDrivenAgent:
    def __init__(
        self,
        policy,
        executor,
        verifier,
        max_steps: int = 8,
    ):
        self.policy = policy
        self.executor = executor
        self.verifier = verifier
        self.max_steps = max_steps

    def run(self, task: str):
        state = AgentState(task=task)

        for step in range(self.max_steps):
            state.step = step

            verification = self.verifier.verify_goal(state)

            if verification.status == Status.PASS:
                return {
                    "status": "success",
                    "verification": verification,
                    "state": state,
                }

            action = self.policy.choose_action(
                state=state,
                verification=verification,
            )

            precondition = self.verifier.verify_action(
                state,
                action,
            )

            if precondition.status == Status.FAIL:
                state.failures.append(precondition)
                continue

            observation = self.executor.execute(action)
            state.observations.append(observation)

            postcondition = self.verifier.verify_observation(
                state,
                action,
                observation,
            )

            if postcondition.status == Status.FAIL:
                state.failures.append(postcondition)

        final = self.verifier.verify_goal(state)

        return {
            "status": "success"
            if final.status == Status.PASS
            else "incomplete",
            "verification": final,
            "state": state,
        }

The implementation is deliberately ordinary.

The interesting part is not the class hierarchy.

It is the contract:

The agent is not allowed to declare success without the verifier.


50. The verifier can be domain-specific

A generic interface is enough.

class Verifier:
    def verify_goal(self, state):
        ...

    def verify_action(self, state, action):
        ...

    def verify_observation(self, state, action, observation):
        ...

Then implementations differ by software type.

CodingVerifier
BrowserVerifier
ResearchVerifier
DataVerifier
SupportVerifier
DeploymentVerifier

The agent loop stays the same.

The evidence changes.

This mirrors an earlier lesson from loop control:

The control structure is generic; the progress and verification signals are domain-specific.


51. Coding verifier example

class CodingVerifier:
    def verify_goal(self, state):
        tests = run_tests()

        if tests.returncode != 0:
            return CheckResult(
                name="goal",
                status=Status.FAIL,
                evidence=tests.stderr,
            )

        reproduction = run_reproduction()

        if not reproduction.fixed:
            return CheckResult(
                name="goal",
                status=Status.FAIL,
                evidence=reproduction.details,
            )

        return CheckResult(
            name="goal",
            status=Status.PASS,
            evidence="targeted tests and reproduction pass",
        )

In a real system, you would probably also bind this to a workspace hash.


52. Research verifier example

class ResearchVerifier:
    def verify_claim(self, claim, sources):
        if not sources:
            return CheckResult(
                name="claim_support",
                status=Status.FAIL,
                evidence="no supporting source",
            )

        support = find_supporting_passages(claim, sources)

        if not support:
            return CheckResult(
                name="claim_support",
                status=Status.UNKNOWN,
                evidence="sources retrieved but no clear support found",
            )

        return CheckResult(
            name="claim_support",
            status=Status.PASS,
            evidence=str(support),
        )

Again, the important thing is that the evidence comes from the source set rather than the model’s confidence.


53. Verification should produce inspectable traces

A production trace might look like:

{
  "task": "Fix checkout total bug",
  "state_id": "tree:91ae...",
  "checks": [
    {
      "name": "syntax",
      "status": "pass"
    },
    {
      "name": "reproduction",
      "status": "pass"
    },
    {
      "name": "checkout_tests",
      "status": "pass"
    },
    {
      "name": "full_suite",
      "status": "unknown",
      "reason": "suite exceeded budget"
    }
  ]
}

Then the final status might be:

PARTIALLY VERIFIED

rather than falsely claiming universal success.


54. Named completion states are better than done=True

Useful states include:

VERIFIED_SUCCESS
VERIFIED_FAILURE
PARTIAL_SUCCESS
UNKNOWN
BUDGET_EXHAUSTED
VERIFIER_ERROR
AWAITING_APPROVAL
ENVIRONMENT_UNAVAILABLE

This gives downstream systems a much clearer contract.


55. Verification and retries

If verification fails, do not blindly retry the original action.

The failure should become evidence for recovery.

Example:

verification failure:
expected status code 200
observed status code 500

The next action should probably be diagnostic.

inspect logs

not:

repeat deployment

This connects verification directly to the earlier loop-control lesson.


56. Verification-driven recovery

A useful sequence is:

FAIL
classify failure
choose recovery strategy
act
verify again

Failure classes might include:

implementation defect
environment defect
missing prerequisite
stale state
permission problem
verification failure
unknown

Now recovery becomes targeted rather than repetitive.


57. Avoid verification loops

Verification itself can loop.

Example:

verify
unknown
retry verifier
unknown
retry verifier

So verifier retries need budgets too.

max_verification_attempts = 3

After that:

UNKNOWN

should remain unknown.

Do not manufacture certainty through repeated sampling.


58. A verifier should sometimes disagree with the agent

If your verifier almost never disagrees with the agent, ask why.

Possibilities:

  • the agent is exceptionally good,
  • the verifier is too weak,
  • the verifier sees the same evidence and makes the same assumptions,
  • the verification criteria are trivial,
  • or the verifier is effectively rubber-stamping.

Measure disagreement.

agent claims success: 100 tasks
verifier agrees:       94
verifier fails:         4
verifier unknown:       2

Those six disagreements are extremely valuable debugging cases.


59. Verification disagreements are training data

Every mismatch creates evidence.

agent: success
verifier: fail

This is a high-value case for:

  • prompt improvement,
  • policy training,
  • tool design,
  • scorer calibration,
  • memory updates,
  • failure taxonomy,
  • benchmark expansion.

Verification therefore improves not only runtime reliability but also the learning loop around the agent.


60. Benchmark verification separately

Suppose you improve the agent and the verifier simultaneously.

You may no longer know why results improved.

Keep benchmark cases where ground truth is independently known.

Then test:

agent without verifier
agent + verifier A
agent + verifier B

And also:

verifier A against known outcomes
verifier B against known outcomes

This prevents verifier quality from becoming invisible.


61. Controlled experiment

For a coding agent, compare:

A: one-shot patch + self-report
B: one-shot patch + targeted test
C: agent loop + targeted test
D: agent loop + targeted + regression verification

Measure:

  • verified success,
  • false success declarations,
  • false failures,
  • average model calls,
  • average verifier calls,
  • latency,
  • cost,
  • regression rate,
  • recovery success after failed verification.

A sophisticated loop that still produces many false success declarations is not production-ready.


62. Failure injection

Verification systems should be tested against deliberately bad outcomes.

Inject:

  • stale test results,
  • partial writes,
  • malformed outputs,
  • successful command with failed service,
  • wrong environment,
  • missing source,
  • contradictory source,
  • failed backend update despite UI success,
  • schema-valid but semantically wrong data,
  • correct local result with broken regression.

Then ask:

Did the verifier catch it?

This is much stronger than only testing successful cases.


63. Evidence before confidence

Agents often produce confidence-like language:

This should work.
I'm confident the issue is resolved.
Everything looks correct.

Those statements can be useful conversationally.

They should not control system state.

System state should come from evidence.

confidence
verification

64. The user’s goal is the highest-level verifier

A system can satisfy many internal metrics and still fail the user.

Example:

all tests pass

but the user asked:

make this faster

If latency did not improve, the goal failed.

Or:

all content claims cited

but the user asked:

write a concise executive summary

If the result is 5,000 words, technical correctness alone is insufficient.

Success criteria must include the actual requested outcome.


65. What should be deterministic?

A useful rule:

Make verification deterministic whenever the domain permits it.

Good deterministic checks:

  • exact schema,
  • unit tests,
  • checksums,
  • counts,
  • permissions,
  • file existence,
  • command exit codes,
  • HTTP status,
  • API state,
  • database invariants,
  • version identity.

Use learned or model-based verification for things deterministic checks cannot capture.

Do not invert that order.


66. What should still use an LLM verifier?

LLM evaluators are useful for:

  • semantic coverage,
  • nuanced instruction following,
  • open-ended critique,
  • style alignment,
  • ambiguous classification,
  • comparing multiple acceptable solutions.

But even then, improve independence where possible.

For example:

generator model
independent rubric judge
source / invariant checks

rather than:

generator asks itself whether it is correct

67. Multiple verifiers can disagree

Suppose:

unit tests: PASS
integration test: FAIL
LLM judge: PASS

Do not average these into:

score = 0.67

They represent different evidence.

Required failures should usually dominate.

if integration_test.status == FAIL:
    overall = FAIL

Verification composition should reflect semantics, not just arithmetic.


68. Required vs advisory checks

Some checks are gates.

Others are signals.

@dataclass
class CheckSpec:
    name: str
    required: bool

Example:

required:
  targeted tests
  API contract test

advisory:
  style score
  complexity score
  benchmark estimate

A poor style score should not override a hard correctness pass unless style is part of the user requirement.


69. Verification quality is task-dependent

There is no universal verifier.

The right verifier depends on:

  • the task,
  • the environment,
  • the cost of false success,
  • the cost of false failure,
  • the available evidence,
  • the reversibility of actions,
  • and the latency budget.

This is why agent architecture should be built around domain contracts, not generic “agent intelligence.”


70. Where verification adds the most value

Verification is especially valuable when:

  • actions have side effects,
  • mistakes are expensive,
  • tasks are multi-step,
  • the model cannot directly observe the final state,
  • environments are noisy,
  • tools may partially fail,
  • user requirements contain preservation constraints,
  • or the agent frequently declares success prematurely.

It may add less value when:

  • the task is purely generative,
  • the user will immediately inspect the result,
  • failure is cheap,
  • or there is no meaningful external signal.

Again:

Do not add a verifier because “agents need verifiers.” Add one where external evidence can materially reduce false success.


71. Do you actually need a verification agent?

Often you do not need a separate agent.

You need ordinary code.

Can correctness be checked deterministically?
       yes
use deterministic verifier

Only if the residual question remains semantic or ambiguous should you escalate to a learned evaluator or LLM judge.

The strongest verifier may be a ten-line Python function.


72. Verification agent vs verification runtime

There is an important distinction.

A verification runtime can be deterministic infrastructure.

A verification agent can reason about what evidence to gather when fixed checks are insufficient.

Example:

runtime:
  run tests
  check schema
  inspect status codes

verification agent:
  determine which additional evidence is needed
  when current evidence is incomplete

The agent should supplement the runtime, not replace deterministic guarantees.


73. Verification can be adaptive

Sometimes the first check fails and reveals which check to run next.

Example:

health check FAIL
inspect pod status
pods restarting
inspect logs
configuration error found

That is a good use of agent reasoning.

But the checks themselves remain external observations.


74. Verification-first debugging checklist

If your agent says it succeeded when it did not, inspect these in order.

1. What exact condition defines success?

If you cannot write it down, verification will be vague.

2. Who decides success?

If the answer is “the same model that produced the result,” independence is weak.

3. What external evidence is available?

Tests, API state, database state, metrics, sources, invariants, files, transactions.

4. Is the evidence tied to the final state?

Check for stale results.

5. Are all required criteria covered?

Partial coverage should not become full success.

6. Can the agent redefine the criteria?

If yes, success can drift.

7. What happens on UNKNOWN?

Do not silently convert it to pass.

8. What happens after verification failure?

Use the failure as diagnostic evidence, not merely as a retry trigger.

9. How often is the verifier wrong?

Measure false passes and false failures.

10. Is verification itself too expensive?

Use staged gates and cheap checks first.


75. The larger architecture we have built

At the beginning of this series, an “agent” could be little more than:

prompt
model
action

We progressively added the machinery needed for reliable software.

structured actions
Best-of-N
critique + revision
planning
loop control
tool routing
memory
search
verification

But notice what happened.

The system did not become useful because the model became more magical.

It became useful because we added ordinary engineering around uncertainty.


76. The first-principles agent stack

We can now summarize the architecture as:

USER GOAL
SUCCESS CRITERIA
STATE
MODEL / POLICY
STRUCTURED ACTION
VALIDATION
EXECUTION
OBSERVATION
MEMORY / SEARCH / PLANNING
VERIFICATION
CONTINUE / RECOVER / STOP

That is an agent system.

The model is important.

But the model is only one component.


77. Final rule

The central lesson of this post is simple:

Do not ask the model whether reality changed. Check reality.

For coding agents, run the tests.

For browser agents, inspect the resulting state.

For research agents, inspect the source evidence.

For data agents, check invariants and reconciliation.

For support agents, verify the transaction.

For DevOps agents, inspect health and telemetry.

And when you cannot know:

UNKNOWN

is a better answer than invented success.


78. Where the series goes next

This post completes the core Agents From First Principles progression.

We now understand the basic mechanisms needed to build an agent that can:

act
plan
revise
route
remember
search
recover
verify

The next step is not simply “more agent.”

The next step is to study more sophisticated ways of allocating computation and coordinating expertise.

That means a new series:

Advanced Agents From First Principles

There we can explore:

  • chain-of-thought as computation,
  • self-consistency,
  • Tree of Thoughts,
  • beam search in greater depth,
  • Monte Carlo Tree Search,
  • evolutionary search,
  • mixture of experts at the agent level,
  • planner / executor / critic systems,
  • multi-agent debate,
  • adaptive routing,
  • learned agent policies,
  • and agents that improve from previous runs.

But the evidence rule will stay the same.

Every added mechanism must solve a measurable failure well enough to justify its cost.