stayfresh
field notes // workflows // research

formal verification with agents

references:

the core argument

kleppmann's argument:

llms are bad at formal verification, but they're excellent at writing specifications that humans can verify.

the economics of formal verification have always been brutal:

llms change the equation in a different way. they can:

  1. generate specifications - natural language or semi-formal descriptions of intended behavior
  2. suggest invariants - properties that should always hold
  3. write property tests - executable checks that verify behavior
  4. translate between levels - informal to formal, code to spec, spec to test

property-based testing: the practical middle ground

formal verification proves correctness. property-based testing (pbt) finds incorrectness.

how pbt works

// Example: fast-check property test
fc.assert(
  fc.property(
    fc.string(),                    // Any string as input
    (s) => {
      const encoded = base64Encode(s);
      const decoded = base64Decode(encoded);
      return decoded === s;         // Round-trip invariant
    }
  )
)

the framework generates hundreds or thousands of random inputs automatically. properties (invariants) get defined, not specific test cases.

common property shapes

property typedescriptionexample
round-tripencode/decode returns originalJSON.parse(JSON.stringify(x))
idempotencef(f(x)) == f(x)Math.abs(Math.abs(x))
commutativityf(a, b) == f(b, a)a + b
associativityf(f(a, b), c) == f(a, f(b, c))(a + b) + c
identityf(x, identity) == xx + 0
invarianceproperty p holds before and afterlist length after sort equals before
no exceptionsnever crashes on valid inputparser handles any input

agent patterns for verification

pattern 1: specification generation

Agent Task: Given this code, generate a specification

Input: Source code
Output: Natural-language specification of behavior

Use when: Code exists but documentation is missing

pattern 2: invariant discovery

Agent Task: Identify invariants that should hold for this system

Input: Code + specification
Output: List of properties that should always be true

Use when: You need to understand what to test

pattern 3: property test generation

Agent Task: Generate property-based tests from this specification

Input: Specification
Output: Executable property tests (fast-check, Hypothesis, etc.)

Use when: You have a spec but no tests

the "vericoding" workflow

traditional development: write code, write tests, hope it works.

vericoding: write spec, generate properties, generate tests, write code, verify.

Intention (What should this do?)
    |
    v
Spec (Formal or semi-formal description)
    |
    v
Properties (Invariants that should hold)
    |
    v
Tests (PBT or example-based)
    |
    v
Code (Implementation)
    |
    v
Verify (Run tests, check properties)

tools and frameworks

property-based testing

languageframeworklink
javascript/typescriptfast-checkgithub.com/dubzzz/fast-check
pythonhypothesishypothesis.works
rustproptestgithub.com/altsysrq/proptest
gogoptergithub.com/leanovate/gopter
javajqwikjqwik.net

what worked in practice

the ceiling

current agents could not:

they could:

human review, agent-generated specs, and property testing covered more ground together than any one of the three did alone.

verification testing specifications agent-design