An AI Agent with Verus
Formal verification and LLM agents look like a natural pair: the agent proposes, the verifier decides. I wanted to know what that pairing buys in practice, so I tried it on 14 open-source Rust crates, most of them widely used, with Verus as the verifier. The full list is at the end.
I measured two jobs separately: proving that code is correct, and finding bugs in it.
All agents ran Claude Opus 5.5 with the same settings in every arm, and the verifier was Verus 0.2026.10.26. These results are for one model; a weaker model might gain more from the verifier.
Proving correctness: worth it
The Verus decider (the setup is below) ported each library function into Verus faithfully, turned each property into a Verus spec, and proved 136 of them for every possible input. Examples: formatting and re-parsing a duration gives back the same value for every 64-bit seconds count, and a hex encoder leaves its output buffer untouched whenever it fails. Tests can only sample inputs, so for these properties the test-only agent could never say more than “probably fine”.
The catch is that a verifier will also happily “prove” nonsense. In three cases a result reported as verified was not: a theorem whose premise could never hold, a set of axioms that contradicted each other, and a proof that leaned on a guarantee the verifier had itself rejected. A separate skeptic agent caught all three. The checks I now treat as mandatory:
- a negative control for every proof (
ensures falseunder the same assumptions must fail); - every axiom checked against the real code, exhaustively where the domain is finite;
- an independent review that the ported model behaves like the original.
Finding bugs: a tie, once the baseline was fair
Getting a bug-hunting setup worth measuring took six iterations. The first versions were elaborate: a spec writer that never saw the code, auditors attacking the spec and the ported model, five independent specs merged by vote, even the git history as a source of intent. Each version changed one thing so I could see what helped. Most of the machinery didn’t. The specs grew, the auditors trimmed most of the growth back, and recall stayed low. The design that worked was the simplest one.
In that design, one agent proposed suspicious properties. Two agents then decided each one: one with Verus, one with tests only. The bugs were real (dozens, each with a failing test, about a third already reported upstream), but Verus rarely made the difference. The bugs came from the agent reading the code.
Once, it looked like Verus had won. It confirmed a subtle bug in a random number generator that the test-only agent gave up on. Before believing it, I preregistered a rerun and told the test-only agent to reason mathematically and not stop early. It found the bug three times out of three. The Verus agent found it two times out of three. The “win” came from a weak baseline.
What I take from it
- Use the verifier for proof, not search. Its value is a guarantee over all inputs, which nothing else gives you.
- Check the proofs. “Verified” means little until a negative control fails and the axioms are checked.
- Make the baseline try hard. A tool can look great against an agent that gives up early.
Crates tested
- Parsers and formatters: humantime, parse-size, glob, benchfmt.rs
- Encoders: hex, bs58, base-x, integer-encoding
- Random numbers: fastrand
- Hashing: rustc-hash, foldhash, siphasher, twox-hash, xxhash-rust