Skip to content

Verification-Driven Agentic Coding

Agentic coding works best when the agent has a target it can check by itself.

Without a verification signal, the agent stops when the code looks plausible. With a signal, it can run the check, read the failure, change the code, and repeat until the output is actually better.

Why Verification Changes Agent Performance

Section titled “Why Verification Changes Agent Performance”

Many software tasks are difficult to solve but comparatively cheap to check.

A model may need several attempts to repair a type mismatch across unfamiliar modules. A compiler can reject each invalid attempt in seconds. The compiler does not make the model smarter, but it gives the agent an objective boundary and a useful next observation.

flowchart LR
    G["Generate a candidate"] --> V["Run an external check"]
    V --> R{"Accepted?"}
    R -->|No| E["Return a specific failure"]
    E --> G
    R -->|Yes| W["Run wider verification<br/>and human review"]

This works best where the check is reliable and the failure is informative. It works poorly when the test is flaky, the build takes too long, or success measures only a weak proxy for the required behavior.

Verification reduces uncertainty. It does not prove that arbitrary software is fully correct.

  1. Ask the agent to inspect the relevant code path.
  2. Define the smallest check that proves the behavior.
  3. Write or identify that check before changing the implementation.
  4. Let the agent implement the fix.
  5. Make the agent run the check and show evidence.

For code, the check might be a failing unit test. For UI, it might be a screenshot comparison. For data work, it might be a fixture diff. For infrastructure, it might be a dry-run plan.

SignalBest forExample
Unit testPure functions, edge cases, regressionsnpm test -- auth-refresh.test.ts
Integration testAPI, database, service boundariespytest tests/api/test_login.py
Build/typecheckCompile-time and contract errorsnpm run build, tsc --noEmit
Lint/formatMechanical consistencynpm run lint
ScreenshotVisual UI changesBrowser or Playwright screenshot after the edit
Fixture diffParsers, serializers, generated outputCompare generated JSON against expected output
Log replayDebugging production-like failuresPipe an error log into the task context
Dry runCloud, migration, deployment workterraform plan, migration preview, --dry-run

Properties Of A Useful Verification Signal

Section titled “Properties Of A Useful Verification Signal”
PropertyMeaningWarning sign
ObjectiveThe result follows from executable rules or observable evidenceSuccess depends only on the agent saying the result looks right
FastThe smallest relevant check returns quicklyEvery edit waits for a complete CI pipeline
Low-noiseThe same state normally produces the same resultUnrelated tests fail intermittently
DiagnosticFailure output identifies what condition was violatedThe check reports only failed or a large undifferentiated log
RepresentativeThe check exercises behavior users or dependent systems rely onThe test passes while the public workflow remains broken
Difficult to gamePassing requires satisfying the intended conditionThe agent can delete an assertion, update a snapshot, or silence the error
GraduatedPartial or layered feedback helps identify progressThe only available result is one late pass/fail gate

A check does not need every property to be useful. The table helps explain why two tests with the same pass/fail interface can provide very different agent performance.

Run the cheapest relevant signal first.

flowchart TB
    C["Changed code"] --> F["Format"]
    F --> L["Lint and type check"]
    L --> U["Focused unit test"]
    U --> I["Relevant integration test"]
    I --> B["Build and wider suite"]
    B --> S["Staging, visual,<br/>or production evidence"]

This ordering shortens the repair loop:

  1. formatting catches mechanical drift
  2. linting and types catch local contract errors
  3. a focused test checks the changed behavior
  4. integration checks expose boundary effects
  5. wider checks detect regressions
  6. staging and runtime evidence test the deployed system

Not every task needs every layer. A documentation change may need only a docs build and rendered-page inspection. A database migration may need schema validation, migration tests, a dry run, rollback evidence, and staging observation.

Feedback Latency Is Part Of The Agent Environment

Section titled “Feedback Latency Is Part Of The Agent Environment”

An agent that receives a useful failure in ten seconds can make several informed attempts while another agent is still waiting for CI.

Track:

  • time to first useful failure
  • p50 and p95 duration for focused checks
  • time spent waiting for unavailable environments
  • percentage of checks that can run locally
  • number of retries caused by infrastructure rather than code

Do not optimize by deleting meaningful checks. Split large suites, improve fixtures, cache safe dependencies, and expose smaller commands that preserve the same guarantees.

A human may recognize a known flaky test and rerun it. An agent can interpret the same noise as evidence that its correct change is wrong.

It may then:

  • modify unrelated code
  • weaken or remove the test
  • consume repeated tokens and CI runs
  • stop without knowing which result to trust

Quarantine a flaky check only with an owner, visibility, and a repair plan. Measure flakiness as an agent-readiness problem, not only a CI inconvenience.

Investigate this bug first. Do not change implementation yet.
Write the smallest failing test that reproduces the reported behavior.
Run only that test and confirm it fails for the expected reason.
Then implement the fix, rerun the test, and show the final command output.

This works because the agent gets a concrete loop:

flowchart LR
    A["Inspect code"] --> B["Create failing check"]
    B --> C["Implement change"]
    C --> D["Run check"]
    D --> E{"Passes?"}
    E -->|No| C
    E -->|Yes| F["Return evidence"]
Use the existing app locally.
Reproduce the UI issue, take a screenshot, apply the smallest fix,
take another screenshot, and compare the result against the expected state.
Do not call the task done unless the visual difference is resolved.

For UI work, a text description is often not enough. Screenshots give the agent a readable result, especially when spacing, overflow, contrast, and layout are involved.

A generated interface can look complete while important behavior behind it is missing.

Examples include:

  • a button with no working event handler
  • a form that reports success without saving data
  • a dashboard that displays mock values instead of an API response
  • a reset-password screen that never sends an email
  • a checkout page that updates visually but never creates an order

These are sometimes called painted doors: the entrance is visible, but it does not lead anywhere.

Static analysis, unit tests, and a successful build may all pass because each checks only part of the system. Browser testing adds evidence at the level where the user encounters the product.

A useful browser check begins with a user journey and an observable result:

Journey: create a customer-feedback entry
Given:
- the application is running with an empty test database
Actions:
- open the feedback page
- enter a name and message
- submit the form
Expected evidence:
- a success state is visible
- the network request succeeds
- one matching record exists in the test database
- the entry appears after the page is refreshed

The visible success message is not enough. The database record alone is not enough. Together, they establish that the interface, API, and persistence path agree.

EvidenceWhat it can revealWhat it may miss
Accessibility tree or DOMControls, labels, roles, and visible stateBackend side effects
Browser actionsWhether a user can complete the interactionHidden data corruption
Network requestsEndpoint, status, payload, and timingIncorrect downstream persistence
Test database stateWhether data was saved correctlyBroken presentation or navigation
Console and service logsRuntime errors and failed integrationsSilent business-rule errors
ScreenshotLayout, overflow, contrast, and visual resultInvisible semantics and side effects
TraceSequence of actions, requests, logs, and page statesRequirements omitted from the scenario

No single channel proves the whole application. Select evidence based on the behavior being changed.

flowchart LR
    J["Critical user journey"] --> B["Browser interaction"]
    B --> U["Visible UI state"]
    B --> N["Network and API evidence"]
    B --> D["Database or durable state"]
    B --> L["Console and service logs"]
    U --> A["Evaluate acceptance criteria"]
    N --> A
    D --> A
    L --> A
    A -->|"Fail"| F["Return focused repair evidence"]
    A -->|"Pass"| R["Save a reusable regression check"]

Generic browser tools versus executable tests

Section titled “Generic browser tools versus executable tests”

An agent can interact with a browser through generic tools such as:

  • open a page
  • click an element
  • fill an input
  • inspect accessibility information
  • take a screenshot

This is useful for exploration and one-off diagnosis.

The agent can also generate an executable Playwright test:

import { expect, test } from '@playwright/test';
test('submitting feedback persists the entry', async ({ page }) => {
await page.goto('/feedback');
await page.getByLabel('Name').fill('Asha');
await page.getByLabel('Message').fill('Please add CSV export.');
await page.getByRole('button', { name: 'Send feedback' }).click();
await expect(page.getByText('Thanks for your feedback')).toBeVisible();
await page.reload();
await expect(page.getByText('Please add CSV export.')).toBeVisible();
});

Executable tests offer several advantages:

  • they use a general programming language rather than a fixed catalog of browser actions
  • assertions make the expected behavior explicit
  • the exact scenario can be rerun after a repair
  • the test can enter the normal regression suite
  • traces and screenshots can be retained for later debugging

Generic tools remain useful when the agent is discovering the workflow or handling an interaction that has not yet been encoded. A practical system may explore with browser tools, then preserve important behavior as a focused test.

A test written by the same agent that wrote the feature can reproduce the agent’s misunderstanding.

Inspect whether the test:

  • starts from a meaningful state
  • exercises the public user journey
  • checks the important side effect
  • would fail if the implementation were removed or replaced with a fake
  • uses resilient roles and labels instead of brittle layout selectors
  • avoids weakening an existing assertion
  • cleans up test data

For high-risk behavior, derive acceptance scenarios from independently reviewed requirements and run the test in a clean environment.

Keep browser noise out of the coding context

Section titled “Keep browser noise out of the coding context”

Browser runs can produce large accessibility snapshots, screenshots, network traces, and logs. A dedicated verification subagent can perform the journey and return only:

  • the failed acceptance criterion
  • the first useful error
  • references to retained traces or screenshots
  • a minimal reproduction
  • whether the failure is in the UI, API, data, or environment

This preserves the evidence without flooding the main implementation context. See Context Engineering for Long-Running Agents.

Some browser steps should not be automated around:

  • CAPTCHA
  • account consent
  • hardware security confirmation
  • personal login credentials
  • regulated approval
  • destructive production actions

Pause with an explicit takeover request or move the scenario to a safe test environment. Do not ask the model to bypass the control.

Current Replit documentation uses this pattern for App Testing: the agent can test and attempt corrections in a browser, but it requests takeover for interactions such as account login. This is one product example, not proof that every workflow can be verified autonomously.

Ask for evidence, not just a claim:

  • the exact command it ran
  • the relevant passing test output
  • before/after screenshots for visual work
  • the changed files
  • any checks it could not run and why

Passing checks do not establish that:

  • the requirement itself was correct
  • important behavior was included in the suite
  • the implementation is maintainable
  • authorization and privacy boundaries are appropriate
  • a user-facing design is understandable or accessible
  • the change will behave correctly under every production condition

Tests written by the implementation agent are also not independent evidence by themselves. Inspect whether they fail without the fix, exercise public behavior, use representative data, and reject plausible wrong implementations.

Use deterministic checks first, then add human review, security analysis, visual inspection, and production evidence according to risk.

FailureWhat to do instead
Agent updates snapshots without explaining whyAsk it to prove the new output is correct
Agent runs the whole suite too earlyStart with the smallest relevant test
Agent claims success without outputRequire command output or screenshot evidence
Agent suppresses an errorTell it to fix root cause, not silence the check
No runnable tests existStart with a smoke script, fixture, or manual checklist
Test passes only because the agent weakened itReview the assertion and reproduce the original failure
Flaky failure sends the agent into unrelated editsRepair or isolate the flaky check with explicit ownership
Local checks and CI use different commands or versionsMake the environment and entry points reproducible

Do not force a full TDD loop for small documentation edits, typo fixes, or obvious one-line changes. Use the cheapest meaningful check: build the docs, run markdown lint, or read the changed page.

Diagram viewer