

simp: the rewriting engine you have been using — how to use it well.
grind: congruence closure, E-matching, and theory solvers,
native to dependent type theory.
bv_decide: SAT-based decisions for bit-level code, kernel-checked.
AI: what it already does with Lean, and why trust still comes from the kernel.

"I thought AI would prove all theorems for us now."
Tactics are the moves of the game — for humans and for AI.
Better tactics = shorter proofs.
Shorter proofs = smaller search trees = more capable AI.
Compact proofs = better training data.


simp: Rewriting with a Databaseexample (xs : List Nat) : (xs ++ []).map (· + 0) = xs := byxs:List Nat⊢ List.map (fun x => x + 0) (xs ++ []) = xs
simpAll goals completed! 🐙
simp rewrites with @[simp] lemmas, left to right, until no rule
applies.
Thousands of library lemmas are annotated; your own join with
@[simp] or simp [myLemma].
simp at h rewrites a hypothesis; simp_all saturates hypotheses
and goal together.

simp Lemmadef double (n : Nat) : Nat := 2 * n
@[simp] theorem double_eq (n : Nat) : double n = 2 * n := rfl
theorem double_add (a b : Nat) :
double (a + b) = double a + double b := bya:Natb:Nat⊢ double (a + b) = double a + double b
simp +arithAll goals completed! 🐙
Left side more complex than the right: rewriting must make progress toward a normal form.
The set must be confluent enough: order should not matter.
Recall Lecture 2: one lemma per helper function, stated in the normal form you want.

def safeDiv (a b : Nat) : Nat := if b = 0 then 0 else a / b
@[simp] theorem safeDiv_self (h : b ≠ 0) : safeDiv b b = 1 := byb:Nath:b ≠ 0⊢ safeDiv b b = 1
simp [safeDiv, h]b:Nath:b ≠ 0⊢ b / b = 1
exact Nat.div_self (Nat.pos_of_ne_zero h)All goals completed! 🐙
example (n : Nat) (h : n ≠ 0) : safeDiv n n + 1 = 2 := byn:Nath:n ≠ 0⊢ safeDiv n n + 1 = 2
simp [h]All goals completed! 🐙
example (n : Nat) (h : n > 5) : safeDiv n n + 1 = 2 := byn:Nath:n > 5⊢ safeDiv n n + 1 = 2
simp (disch := grind)All goals completed! 🐙
simp lemmas may have hypotheses; simp discharges them recursively
(with itself, and with the facts you pass).
This is where rewriting starts needing search — the boundary where
grind takes over.

simp?, apply?, exact?
simp? — closes the goal, then prints the minimal simp only [...]
call. Use it, then paste the result: faster and more robust.
apply? / exact? — search the library for lemmas matching the goal.
Loogle — search by type shape from the browser or editor.
Automation is also for finding the lemma, not only for closing goals.

simp Stopsexample (x y : Int) (h₁ : 2 * x + 3 * y = 7) (h₂ : 3 * x - y = 5) :
x = 2 := byx:Inty:Inth₁:2 * x + 3 * y = 7h₂:3 * x - y = 5⊢ x = 2
grindAll goals completed! 🐙
No rewrite rule solves a linear system: this needs arithmetic reasoning, not normalization.
Equalities must also flow between hypotheses, through function applications, into case splits.
That combination — rewriting + theories + propagation — is grind.

grind?New proof automation, shipped in Lean v4.22. Kim Morrison and me.
A virtual whiteboard, inspired by modern SMT solvers.
Writes facts on the board. Merges equivalent terms.
Cooperating engines: congruence closure, E-matching, constraint propagation, guided case analysis.
Satellite theory solvers: cutsat (linear integer arithmetic), commutative rings (Gröbner bases), linarith, AC.
Native to dependent type theory. No translation to first-order logic.
Produces ordinary Lean proof terms. Kernel-checkable.

example (f : Nat → Nat) (a b c : Nat)
(h₁ : a = b) (h₂ : f b = c) : f a = c := byf:Nat → Nata:Natb:Natc:Nath₁:a = bh₂:f b = c⊢ f a = c
grindAll goals completed! 🐙
The E-graph maintains equivalence classes of terms: a = b merges the
classes of a and b.
Congruence: equal arguments give equal applications, so f a and
f b merge too. No rewriting happens — this is union-find.
Every other engine reads from and writes to this board.

@[grind =] theorem fg {x} : f (g x) = x := byx:Nat⊢ f (g x) = x
unfold f gx:Nat⊢ 2 * x / 2 = x; grindAll goals completed! 🐙
example {a b c} : f a = b → a = g c → b = c := bya:Natb:Natc:Nat⊢ f a = b → a = g c → b = c
grindAll goals completed! 🐙
Library authors annotate theorems; grind instantiates them by
E-matching: pattern matching modulo the board's equalities.
[grind =] uses the left-hand side as the pattern. Note f (g c)
appears nowhere in the goal: the board knows a = g c, so f a
matches f (g ?x), the instance f (g c) = c lands on the board, and
congruence chains b = f a = f (g c) = c.
This is what makes annotation-driven automation robust: lemmas fire when the equalities match, not the syntax.

@[grind =] theorem State.get_set_same (σ : State) (x : String) (v : Int) :
(σ.set x v).get x = v := byσ:Statex:Stringv:Int⊢ (σ.set x v).get x = v
simp [State.get, State.set]All goals completed! 🐙
@[grind =] theorem State.get_set_ne (σ : State) (x y : String) (v : Int)
(h : y ≠ x) : (σ.set x v).get y = σ.get y := byσ:Statex:Stringy:Stringv:Inth:y ≠ x⊢ (σ.set x v).get y = σ.get y
simp [State.get, State.set, h]All goals completed! 🐙
example (σ : State) (h : σ.get "y" = 5) :
((σ.set "x" 1).set "z" 2).get "y" = 5 := byσ:Stateh:σ.get "y" = 5⊢ ((σ.set "x" 1).set "z" 2).get "y" = 5
grindAll goals completed! 🐙
The State API is Lecture 2's; the two annotations make grind an
expert in it — Lecture 4 runs on exactly these two lemmas.

[grind →]def divides (a b : Nat) : Prop := ∃ k, b = a * k
@[grind →] theorem divides_trans (h₁ : divides a b) (h₂ : divides b c) :
divides a c := bya:Natb:Natc:Nath₁:divides a bh₂:divides b c⊢ divides a c
obtain ⟨k₁, rfl⟩ := h₁a:Natc:Natk₁:Nath₂:divides (a * k₁) c⊢ divides a c
obtain ⟨k₂, rfl⟩ := h₂a:Natk₁:Natk₂:Nat⊢ divides a (a * k₁ * k₂)
exact ⟨k₁ * k₂, Nat.mul_assoc a k₁ k₂⟩All goals completed! 🐙
example (h₁ : divides a b) (h₂ : divides b c) (h₃ : divides c d) :
divides a d := bya:Natb:Natc:Natd:Nath₁:divides a bh₂:divides b ch₃:divides c d⊢ divides a d
grindAll goals completed! 🐙
[grind →] marks a forward rule: the patterns come from the
premises — when matching facts are on the board, the conclusion is
added. ([grind ←] is the dual: patterns from the conclusion.)
Transitivity is the canonical case: two firings chain the three
hypotheses into divides a d.
Forward chains can grow; grind bounds instantiation rounds, and its
diagnostics list every instance created.

Theory solvers engage automatically when the type classes are present:
cutsat — linear integer arithmetic; Int, Nat, Int32,
BitVec n, Fin n.
Ring — CommRing, Field, IsCharP: Gröbner-basis reasoning.
linarith — ordered modules: linear arithmetic over ordered fields.
AC — any associative-commutative operator.

example (x y : Int)
: 27 ≤ 11 * x + 13 * y →
11 * x + 13 * y ≤ 45 →
-10 ≤ 7 * x - 9 * y →
7 * x - 9 * y ≤ 4 → False := byx:Inty:Int⊢ 27 ≤ 11 * x + 13 * y → 11 * x + 13 * y ≤ 45 → -10 ≤ 7 * x - 9 * y → 7 * x - 9 * y ≤ 4 → False
grindAll goals completed! 🐙
cutsat: a complete decision procedure for linear integer arithmetic —
the workhorse for indices, sizes, and bounds in program verification.
lia (Lecture 2) is cutsat without the rest of grind.

example (x : BitVec 8) : (x - 16) * (x + 16) = x ^ 2 := byx:BitVec 8⊢ (x - 16) * (x + 16) = x ^ 2
grindAll goals completed! 🐙
BitVec 8 is a commutative ring of characteristic 256 — the ring
solver proves this by normalization; no bitvector theory needed.
The same solver handles polynomial identities over Int, Rat,
and any CommRing.

example [CommRing α] [NoNatZeroDivisors α]
(a b c : α) (f : α → Nat)
: a + b + c = 3 →
a ^ 2 + b ^ 2 + c ^ 2 = 5 →
a ^ 3 + b ^ 3 + c ^ 3 = 7 →
f (a ^ 4 + b ^ 4) + f (9 - c ^ 4) ≠ 1 := byα:Type u_1inst✝¹:CommRing αinst✝:NoNatZeroDivisors αa:αb:αc:αf:α → Nat⊢ a + b + c = 3 → a ^ 2 + b ^ 2 + c ^ 2 = 5 → a ^ 3 + b ^ 3 + c ^ 3 = 7 → f (a ^ 4 + b ^ 4) + f (9 - c ^ 4) ≠ 1
grindAll goals completed! 🐙
Three solvers meet at the E-graph:
Ring solver derives a^4 + b^4 = 9 - c^4.
Congruence closure lifts it to f (a^4 + b^4) = f (9 - c^4).
Linear integer arithmetic closes 2 * f (9 - c^4) ≠ 1.
The Nelson–Oppen playbook, inside dependent type theory — no SMT translation layer.

grind splits on match expressions, ifs, and annotated
disjunctions — with heuristics to avoid explosion.
Recall the ladder from Lecture 2: mkAdd.eq_def handed grind the
match equations, and it did the case analysis itself.
Case analysis is where automation costs blow up; controlling it is half the engineering.

grind in Practice
5,000+ grind calls in Mathlib.
Used daily on goals from algebra, program verification, and combinatorics.


grind Is NotNot designed for combinatorially explosive search spaces.
Not a translation to an external SMT solver: no encoding gap, no reconstruction gap.
Not a nonlinear-arithmetic oracle: the ring solver normalizes polynomial equalities; hard nonlinear inequalities remain hard.
A workhorse with a legible design — when it fails, you can see why.
For massive case-analysis, use bv_decide.

grind Failsexample (as bs cs : Array α) (v : α) (i : Nat)
(h₁ : i < as.size)
(h₂ : bs = as.set i v)
(h₅ : j < bs.size)
(h₆ : j < as.size)
: bs[j] = as[j] := byα:Type u_1j:Natas:Array αbs:Array αcs:Array αv:αi:Nath₁:i < as.sizeh₂:bs = as.set i v h₁h₅:j < bs.sizeh₆:j < as.size⊢ bs[j] = as[j]
grindAll goals completed! 🐙`grind` failed
α:Type u_1j:Natas bs cs:Array αv:αi:Nath₁:i < as.sizeh₂:bs = as.set i v h₁h₅:j < bs.sizeh₆:j < as.sizeh:¬bs[j] = as[j]h_1:i = j⊢ False
[grind] Goal diagnostics
[facts] Asserted facts
- [prop] i + 1 ≤ as.size
- [prop] bs = as.set i v ⋯
- [prop] j + 1 ≤ bs.size
- [prop] j + 1 ≤ as.size
- [prop] ¬bs[j] = as[j]
- [prop] (as.set i v ⋯).size = as.size
- [prop] (as.set i v ⋯)[j] = if i = j then v else as[j]
- [prop] i = j
[eqc] True propositions
- [prop] j + 1 ≤ as.size
- [prop] j + 1 ≤ bs.size
- [prop] i + 1 ≤ as.size
- [prop] j < as.size
- [prop] j < bs.size
- [prop] i < as.size
- [prop] i = j
- [prop] j < (as.set i v ⋯).size
[eqc] False propositions
- [prop] bs[j] = as[j]
[eqc] Equivalence classes
- [eqc] {j, i}
- [eqc] {bs, as.set i v ⋯}
[eqc] {v, bs[j], (as.set i v ⋯)[j]}
- [eqc] {if i = j then v else as[j]}
- [eqc] {as.size, bs.size, (as.set i v ⋯).size}
- [eqc] {j + 1, i + 1}
- [eqc] {as.size = 0, bs.size = 0}
[eqc] others
- [eqc] {↑j, ↑i}
- [eqc] {↑as.size, ↑bs.size, ↑(as.set i v ⋯).size}
[cases] Case analyses
[cases] [1/2]: if i = j then v else as[j]
- [cases] source: E-matching `Array.getElem_set`
[ematch] E-matching patterns
- [thm] Array.eq_empty_of_size_eq_zero: [@Array.size #2 #1]
- [thm] Array.size_set: [@Array.size #4 (@Array.set _ #3 #2 #1 #0)]
- [thm] Array.getElem_set: [@getElem (Array #6) `[Nat] _ _ _ (@Array.set _ #5 #4 #2 #3) #1 #0]
[cutsat] Assignment satisfying linear constraints
- [assign] j := 0
- [assign] i := 0
- [assign] as.size := 1
- [assign] bs.size := 1
- [assign] (as.set i v ⋯).size := 1
[ring] Ring `Int`
[basis] Basis
- [_] ↑i + -1 * ↑j = 0
- [_] ↑as.size + -1 * ↑bs.size = 0
- [_] ↑bs.size + -1 * ↑(as.set i v ⋯).size = 0
[grind] Diagnostics
[thm] E-Matching instances
- [thm] Array.getElem_set ↦ 1
- [thm] Array.size_set ↦ 1
On failure, grind prints its board: the facts it knew, the classes it
built, the instances it tried.
Read the board, find what is missing — a fact, an annotation, or,
as here, a hypothesis: nothing rules out j = i, where the claim is
false. The loop is: fail, read, fix, succeed.
Automation you can inspect and extend — not a black box.
Hardware, cryptography, systems code.

BitVec and the Problems That Need It#check255 : BitVec 8 (0xFF : BitVec 8)
255 : BitVec 8#eval44#8 (200 : BitVec 8) + 100
44#8#eval128#8 (1 : BitVec 8) <<< 7
128#8
Fixed-width machine arithmetic: overflow, shifts, masks — where hand proofs are least pleasant and bugs are most common.
Compilers, crypto kernels, hardware models: all live here.
grind's ring solver handles some of it; complete answers need a
decision procedure.

bv_decide: SAT with a Verified Checkerexample (x y : BitVec 32) : x ^^^ y ^^^ x = y := byx:BitVec 32y:BitVec 32⊢ x ^^^ y ^^^ x = y
bv_decideAll goals completed! 🐙
example (x : BitVec 32) : x &&& (x - 1) = x - (x &&& -x) := byx:BitVec 32⊢ x &&& x - 1 = x - (x &&& -x)
bv_decideAll goals completed! 🐙
Bit-blasts the goal to SAT, runs CaDiCaL, and replays the solver's LRAT certificate through a checker verified in Lean.
The SAT solver is not trusted; the kernel still checks everything.

example (x : BitVec 8) : x &&& (x - 1) = x - 1 := byx:BitVec 8⊢ x &&& x - 1 = x - 1
bv_decideAll goals completed! 🐙The prover found a counterexample, consider the following assignment:
x = 0#8
A decision procedure answers both ways: proof, or counterexample.
x = 0: 0 &&& 255 = 0, but 0 - 1 = 255. The "identity" is false.
Cheaper than a failed proof attempt: try the theorem before investing in it. (Shown as output — a failing tactic would, correctly, fail this slide's build.)

popcount, from Hacker's Delightdef popSpec (x : BitVec 32) : BitVec 32 :=
go 32 x
where
go : Nat → BitVec 32 → BitVec 32
| 0, _ => 0
| n + 1, x => (x &&& 1) + go n (x >>> 1)
def popcount (x : BitVec 32) : BitVec 32 :=
let x := x - ((x >>> 1) &&& 0x55555555)
let x := (x &&& 0x33333333) + ((x >>> 2) &&& 0x33333333)
let x := (x + (x >>> 4)) &&& 0x0F0F0F0F
let x := x + (x >>> 8)
let x := x + (x >>> 16)
x &&& 0x0000003F
theorem popcount_correct (x : BitVec 32) : popcount x = popSpec x := byx:BitVec 32⊢ popcount x = popSpec x
simp [popcount, popSpec, popSpec.go]x:BitVec 32⊢ ((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) + ((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32)) >>>
4 &&&
252645135#32) +
((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32)) >>>
4 &&&
252645135#32) >>>
8 +
(((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32)) >>>
4 &&&
252645135#32) +
((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32) &&& 858993459#32) +
((x - (x >>> 1 &&& 1431655765#32)) >>> 2 &&& 858993459#32)) >>>
4 &&&
252645135#32) >>>
8) >>>
16 &&&
63#32 =
(x &&& 1#32) +
((x >>> 1 &&& 1#32) +
((x >>> 2 &&& 1#32) +
((x >>> 3 &&& 1#32) +
((x >>> 4 &&& 1#32) +
((x >>> 5 &&& 1#32) +
((x >>> 6 &&& 1#32) +
((x >>> 7 &&& 1#32) +
((x >>> 8 &&& 1#32) +
((x >>> 9 &&& 1#32) +
((x >>> 10 &&& 1#32) +
((x >>> 11 &&& 1#32) +
((x >>> 12 &&& 1#32) +
((x >>> 13 &&& 1#32) +
((x >>> 14 &&& 1#32) +
((x >>> 15 &&& 1#32) +
((x >>> 16 &&& 1#32) +
((x >>> 17 &&& 1#32) +
((x >>> 18 &&& 1#32) +
((x >>> 19 &&& 1#32) +
((x >>> 20 &&& 1#32) +
((x >>> 21 &&& 1#32) +
((x >>> 22 &&& 1#32) +
((x >>> 23 &&& 1#32) +
((x >>> 24 &&& 1#32) +
((x >>> 25 &&& 1#32) +
((x >>> 26 &&& 1#32) +
((x >>> 27 &&& 1#32) +
((x >>> 28 &&& 1#32) +
((x >>> 29 &&& 1#32) +
((x >>> 30 &&& 1#32) +
(x >>> 31 &&& 1#32)))))))))))))))))))))))))))))))
bv_decideAll goals completed! 🐙
The bit-twiddling classic, verified against the obvious specification in two tactic lines.

Benchmarked against Bitwuzla on 46,191 SMT-LIB bit-vector problems:
bv_decide solves 45,046 (97.5%) — kernel checking included.
Bitwuzla solves 45,817.
Identical sat/unsat verdicts on every problem both solved.
Total CPU time within 2.3× of Bitwuzla.
A verified pipeline, competitive with an unverified state-of-the-art solver.

Every medal-level IMO AI with formal proofs uses Lean.
AlphaProof (Google DeepMind) — silver medal, IMO 2024
Aristotle (Harmonic) — gold medal, IMO 2025
Seed Prover (ByteDance) — silver medal, IMO 2025
In 2019, the IMO was posed as a Grand Challenge for AI. Six years later, three independent systems have medal-level results.
Moves played by AlphaProof:
simp_all [Finset.sum_range_id]
zify [*] at *
norm_num at *
nlinarith [(by norm_cast : (c:ℝ) ≥ A*(l-⌊_⌋)+⌊_⌋+1),
Int.floor_lex, Int.lt_floor_add_one x]
The most advanced AI relies on the same tactics we use every day.
When grind closes a goal in one step instead of fifty, the AI search
tree shrinks accordingly.
Better tactics make more capable AI — automation did not become obsolete; it became infrastructure.

Alexeev & Mixon resolved a $1000 Erdős prize problem.
"We used ChatGPT to vibe code a Lean proof." — Alexeev & Mixon
The proof is machine-checked. Paper


Public results on a benchmark of hard Lean formalization problems.
Submission-based leaderboard.
Erdős's unit-distance conjecture is one of the problems.
OpenAI showed this 80-year-old conjecture to be false.
Boris Alexeev submitted a ~1.2M-line Lean formal proof.
"Human mathematicians are being outcounterexampled" — Kevin Buzzard
Feit–Thompson odd-order theorem — 4 submissions
Jacobian of a compact Riemann surface (Buzzard challenge) — 4 submissions


AI-authored Lean mathematics, directed by a human-owned roadmap and gated by open, adversarial review.
Humans own the roadmap: mathematicians choose the targets.
AIs write and review the code.
On the roadmap: universal covers, the Jacobian challenge, reductive algebraic groups, PDEs.



To turn any computer into a Tau Ceti contributor:
uv tool install git+https://github.com/kim-em/TauCetiWorker tauceti work --loop

Formal Conjectures (Google DeepMind): curated, human-verified formal statements of open problems.


Hex: computational algebra for Lean — LLL lattice reduction, and now integer polynomial factorization.
example : Irreducible (X ^ 4 + 8 * X + 12 : Polynomial ℤ) := by irreducibility
Berlekamp, Hensel lifting, Berlekamp–Zassenhaus, with van Hoeij's knapsack reconstruction.
The van Hoeij algorithm had never been formally verified before.
Consistently faster than Isabelle's verified factorization; about 5× slower than FLINT (unverified state of the art).
Kim Morrison, August 2026.

lean-zip (Lecture 1): AI agents optimized the code autonomously — the round-trip theorem stood in for human review of each change.
Radix: 10 AI agents built a verified DSL in a weekend — 52 theorems,
~7,400 lines, 0 sorry, 5 verified optimization passes.
github.com/leodemoura/RadixExperiment
The pattern: humans own specifications; agents iterate on code and proofs.

Good, today:
writing complex formal proofs
explaining formal proofs
metaprogramming
isolating and diagnosing bugs; optimizing code; translating code
Not good, today:
system-level design; novel abstractions
long-running context; knowing when a specification is wrong.

"It's really important with these formal proof assistants that there are no backdoors or exploits you can use to somehow get your certified proof without actually proving it, because reinforcement learning is just so good at finding these backdoors." — Terence Tao
RL systems optimize the checker, not the mathematics.
Tools with a large trusted base are a liability in the AI era.
This is why the kernel architecture from Lecture 1 matters more now, not less.

Everyday: the file checks; #print axioms shows dependencies.
Replay: lean4checker re-runs the kernel on compiled output.
Gold standard: export the proof; recheck with independent kernels (Rust, Lean, C++ implementations) at arena.lean-lang.org.
Comparator: sandboxed judging for AI-submitted proofs — defeats metaprogramming exploits.

A challenge asks for a proof of False.
A candidate tries to smuggle one past the kernel with a metaprogramming trick that exploits a missing check in the official kernel — a real GitHub issue.
Comparator rejects it. The proof is exported and rechecked independently; Nanoda (Rust) and Lean4Lean reject it too.


On July 25, an AI managed to construct a proof of False that exploited a bug in the official kernel, and completely different bug in nanoda, the main external kernel.
Our postmortem for additional details.
More kernels, verified kernels:
lake commands to check Lean developments using comparator, nanoda, etc.
We are collaborating with AI teams that have access to AI with cybersecurity capabilities.
We are actively hardening kernel invariants.
We are considering using the Lean 3 approach as a sanity check. In Lean 3, we compiled nested/mutual inductives into simple ones, provided definitions for the constructors and recursors, and proved that the reduction rules were propositionally true.
We are approaching people to implement new kernels, and we are willing to fund them.
We want to see lean4lean fully verified.

Leanstral (Mistral): open-source code agent designed for Lean 4.
Axiom: solved 12/12 problems on Putnam 2025.
DeepSeek Prover-V2: 88.9% on miniF2F. Open source, 671B parameters.
Harmonic: built Aristotle — gold medal, IMO 2025.


Exercises/Lecture3.lean — the proofs should be short:
simp lemma design.
grind: congruence, arithmetic, and theory cooperation — prove each
by hand first, then replace with grind.
[grind =] annotation: make E-matching fire modulo equalities.
The ladder, on the optimizer from Lecture 2.
bv_decide: xor tricks, two's complement, a buggy xor-swap to fix,
branch-free abs.

simp: rewriting to normal forms; design your lemmas.
grind: an E-graph where congruence, E-matching, and theory solvers
cooperate — extensible with two-line annotations.
bv_decide: complete for bitvectors, certificate-checked, competitive.
AI uses these same tools, at scale; the kernel remains the arbiter.
Next lecture: Hoare logic, a verified verification condition generator, and the engineering that makes verification scale — because once AI writes proofs, the limit is platform throughput.
