lean-model
Skill27 starsChanged 49 days agoMITView on GitHub
Use this skill to prove or refute properties of real software in Lean 4. The target is the user's own code, not textbook math. It covers state machines, concurrency and interleavings, async handlers, retries and idempotency, queues, batching pipelines, and graph algorithms such as topological order, acyclicity and DAG scheduling. Trigger it when the user wants a guarantee for every input or size (\"holds for all N\"), beyond what TLC, tests or a model checker showed. Also trigger it for invariant proofs by induction, a concrete counterexample trace, an executable reference model for differential testing, or setting up a Lean/lake project to model code. Also use it to audit a Lean development's trust base: sorry, axioms, native_decide. Do NOT use it for pure mathematics such as analysis, algebra or Mathlib coursework and homework. Do NOT use it to translate code into Lean syntax, or for other provers like Dafny, Coq or Isabelle. Pairs with formal-verify for bug hunts and tlaplus-model for protocol exploration.
In this repository6 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- loreSkill
- questSkill
- formal-verifySkill
- proof-simplifySkill
- tlaplus-modelSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 37
State and steps: Write the component's state as a structure S deriving DecidableEq and Repr.
- Very specific: line 45
Executable twin: Write next : S → List (String × S) with action labels, and prove next_sound (every executable move is a Step) so the twins …
- Very specific: line 62
When grind or simp_all fails on one constructor, the leftover goal is a counterexample to induction: a state that satisfies Inv but is …
- Distinctive: line 115
Do not make read-modify-write atomic in the model unless the code makes it atomic.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
lean-model
Lean gives three things at once, all from one language:
- an executable model that runs as a bounded model checker and a differential-testing oracle,
- theorems that cover every input, size, and interleaving, and
- a kernel that checks the proofs, so the only thing to trust is the statements.
Agents are now good at the proof engineering. The human-level decisions are what to model and which statements to believe. This skill is organized around those decisions.
For a full bug hunt (scoping, the correspondence map, reproduction in real code, the fix, and
the Artifact report), follow formal-verify and use this skill for the model.
Setup
S=${CLAUDE_SKILL_DIR}/scripts
$S/setup_lean.sh # elan (no sudo); projects pin their own toolchain
$S/new_project.sh formal/<concern>/lean JobLease # template: Explore + System + Properties
cd formal/<concern>/lean && lake build
$S/check_trust.sh . # sorry/axiom/native_decide audit; --json for findingsThe template has no Mathlib, only core Lean plus optional plausible. Software models rarely
need Mathlib: core has List, Array, Std.HashMap, omega, grind, decide,
bv_decide, simp, and Relation.TransGen. Mathlib adds about 8 GB of build cache.
See references/setup.md. lake env lean F.lean does not build dependencies, so run
lake build first.
The loop: model, then refute, then prove
State and steps. Write the component's state as a
structure SderivingDecidableEqandRepr. Keep only what the properties read. Write the steps as aninductive Step : S → S → Prop:- one constructor per atomic region of the code (between
awaits, a locked section, one SQL statement, one handler); - a per-actor program counter wherever the code can yield;
- retries, duplicates, and crashes as their own constructors.
Tag each constructor's docstring
src: path:lines.- one constructor per atomic region of the code (between
- Executable twin. Write
next : S → List (String × S)with action labels, and provenext_sound(every executable move is aStep) so the twins cannot drift apart. The template includes this. Refute first. It is cheaper than proving.
#eval Explore.bfs next bad initreturns the shortest labeled trace to a bad state.Explore.validPath_reach … (by decide)turns that trace into a kernel-checked theorem that the bug is reachable.decide +kernelruns exhaustive bounded checks and trusts onlypropext.plausibledoes property-based testing of pure functions.
Most bugs appear here, in seconds. Hand real ones to
formal-verifyfor reproduction.- State the property and freeze it in
Properties.lean, citing its source. Also write a non-vacuity witness: a path, checked bydecide, to a state where the interesting thing happens. A safety theorem over a model where nothing can happen is worthless. - Prove by an inductive invariant.
- Prove
inv_init, theninv_step(cases hs <;> grind [Inv, …]), then lift the result withExplore.Reach.inv. - When
grindorsimp_allfails on one constructor, the leftover goal is a counterexample to induction: a state that satisfiesInvbut is unreachable, whose successor breaksInv. Add the missing fact toInvand repeat. This loop is the heart of the method, and the missing fact is often a real edge case worth checking in the code. - Details and stuck-proof tactics:
references/proving.md.
- Prove
- Audit trust.
check_trust.shmust report 0 problems for any theorem called "proven".- Standard axioms (
propext,Classical.choice,Quot.sound) are fine. sorryAxis not a proof.native_decidetrusts the compiler: allow it only with--allow-native, and disclose it.
- Connect to the code. Mirror fixes in the model and re-prove. Use
lake exe replay(the differential-testing driver inassets/lean-patterns/Replay.lean) to compare model outputs with production traces (references/differential-testing.md).
Patterns: all compile on Lean 4.34.0, with 0 sorry
assets/lean-patterns/ProofPatterns/, one file per pattern. references/patterns.md
explains each one.
| File | Shows | Headline results |
Explore.lean | Reach, invariant induction, BFS with traces, trace → theorem | Reach.inv, validPath_reach |
Counter.lean | lost update vs. mutex; interleavings as a step relation | racy_lost_update (bug as proof term), no_lost_update |
Idempotency.lean | async check-then-act across await; retries | buggy_double_apply, applied_nodup |
Queue.lean | FIFO: no loss, reordering, or duplication | recvd_prefix |
Pipeline.lean | batching stage drops its tail; generalized fold lemma | batch_lossless |
Graph.lean | acyclicity and topological order by certificate checking | acyclic_of_check |
StateMachine.lean | transition table + read-decide-write race; CAS fix | transitions_allowed |
DagScheduler.lean | dependency-respecting dispatch for every graph | deps_respected |
RedundantGuard.lean | proving a defensive check is dead, then deleting it | guard_implied, reach_iff |
When to use Lean vs. TLA+
Use Lean for:
- algorithmic cores,
- data transformations,
- graph properties for all graphs,
- unbounded "for every N" safety,
- certificate checkers, and
- executable reference models.
Use TLA+ for:
- quick exhaustive interleaving search,
- liveness and fairness (Lean has no mature temporal-logic library), and
- protocol exploration.
Combined: TLA+ finds the trace fast; Lean proves the fixed invariant for every size. Keep TLC
traces as decide regression examples in Lean. See formal-verify/references/choosing-tools.md.
Rules that keep proofs honest
- The model is the claim. Review theorem statements as carefully as code, and never edit a frozen statement to make a proof go through. Report it instead.
- Do not make read-modify-write atomic in the model unless the code makes it atomic. That single abstraction hides most concurrency bugs.
partial defcannot be unfolded in proofs. Use fuel (asExplore.bfsdoes) or well-founded recursion for anything you reason about.- A name like
idcan silently resolve to a builtin. Insidematch/∀, writePC.awaitingrather than.awaitingwhen the expected type is unknown. - Destructure invariant conjunctions (
obtain ⟨h1, h2, h3⟩ := h) and case-split on actor and program counter before callinggrindorsimp_all.
Reference files
references/patterns.md— each pattern: the code shape, the bug, the proof strategy, and how to adapt it.references/proving.md— the tactic playbook, the invariant-strengthening loop, stuck-proof strategies, AI provers.references/pitfalls.md— vacuity, trust base, drift, over-abstraction, spec gaming.references/differential-testing.md— executable models against production: replay, random inputs, CI.references/setup.md— elan, lake, toolchain pinning, Mathlib trade-offs, lean-lsp-mcp for agents.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

