MSc in CS (UFF, 2005): formal methods, rewriting logic in Maude.
PhD in CS (PUC-Rio, 2010): proof theory for description logics. Internships at SRI (PVS, 2009) and Microsoft Research, with the Z3 team (2008).
IBM Research Brazil (2012–2025): NLP, computational semantics, ontologies.
FGV/EMAp, professor since 2010: discrete mathematics, programming, algorithms, type theory, category theory.
In the Lean community since March 2015.
The library. What CSLib is, where it stands, how it is governed, how to contribute.
The API, concretely. Two worked examples, type-checked against CSLib while this deck was built.
The Initiative. What Renaissance Philanthropy supports, and why that is not the same thing as the library.
The ecosystem, and AI. Textbooks, courses and verification projects built on CSLib; CSLib as data for AI, and AI as a CSLib contributor.

lean-lang.org: a proof assistant and programming language that is transforming how we approach mathematics, software verification, and AI.
Lean provides machine-checkable proofs.
Lean addresses the trust bottleneck.
Lean is implemented in Lean, and is very extensible and scalable.
It is based on dependent type theory.
Small trusted kernel. Proofs can be exported and independently checked.
325,000+ unique installations: VS Code (184K) + Open VSX (141K).
This and the next two slides are adapted from the FROCON 2026 deck of Leo de Moura (@leodemoura).
def odd (n : Nat) : Prop := ∃ k, n = 2 * k + 1
theorem square_of_odd_is_odd : odd n → odd (n * n) := byn:ℕ⊢ odd n → odd (n * n)
intro ⟨k₁, e₁⟩n:ℕk₁:ℕe₁:n = 2 * k₁ + 1⊢ odd (n * n)
simp [e₁, odd]n:ℕk₁:ℕe₁:n = 2 * k₁ + 1⊢ ∃ k, (2 * k₁ + 1) * (2 * k₁ + 1) = 2 * k + 1
exists 2 * k₁ * k₁ + 2 * k₁n:ℕk₁:ℕe₁:n = 2 * k₁ + 1⊢ (2 * k₁ + 1) * (2 * k₁ + 1) = 2 * (2 * k₁ * k₁ + 2 * k₁) + 1
liaAll goals completed! 🐙
The "game board": you see goals and hypotheses, then apply "moves" (tactics). Each tactic transforms the game board.
Created in July 2017, in Lean 3 during Big Proof. An open-source, community-driven library. Today:
280,000+ formalized theorems.
2.4M+ lines of Lean. 50,000+ lines of extensions.
750+ contributors.
1,500+ type classes, 20,000+ instances.

AI writes more and more of the world's code. Tests sample inputs; a proof covers all of them.
But a proof needs a statement, and a statement needs vocabulary: transition systems, logics, semantics, cost models.
CSLib's aim: that vocabulary for computer science, shared, and checked by Lean's kernel.
AI-based provers are bottlenecked by the abstractions available in the underlying proof assistant.
— Barrett et al., CSLib: Towards a Lean Computer Science Library, CACM (forthcoming)
An open-source library of reusable components for proving theorems in computer science and writing formally verified code in the Lean programming language and proof assistant.
Two pillars:
Formalizing computer science in Lean. Models of computation, semantics, logics, algorithms and data structures with correctness and complexity proofs.
Reasoning about everyday code. Boole, an intermediate verification language embedded in Lean, as the bridge from mainstream imperative code to CSLib's machinery.
Formal-methods work tends to produce isolated artifacts: a semantics here, a memory model there, a process calculus somewhere else.
The formalizations are designed to form a coherent and integrated framework rather than a collection of unrelated modules.
— Barrett et al., CSLib: Towards a Lean Computer Science Library, CACM (forthcoming)
Mathlib is a dependency:
CSLib reuses its mathematics, style guide and linters, and many definitions.
The rule: do not rebuild what Mathlib already has.
The boundary is not always obvious. Mathlib also has DFA, context-free grammars and graphs, RegularExpressions, etc; all of them, computer science concepts.
Some independent efforts converged around July, 2025, bringing together Clark Barrett, Swarat Chaudhuri, Jim Grundy (Amazon), Pushmeet Kohli, Fabrizio Montesi and Leonardo de Moura:
Clark Barrett and Swarat Chaudhuri were discussing shared infrastructure for formal computer science.
Pushmeet Kohli had raised the same idea with Leonardo de Moura (Kevin Hartnett, The Proof in the Code, 2026).
Fabrizio Montesi had independently started a cslib repository.
Amazon and Google DeepMind funded two technical-lead positions.
Renaissance Philanthropy came in to build the Initiative around it.
Supporting organisations listed on cslib.io: Amazon, Google DeepMind, FORM, Renaissance Philanthropy, Stanford Center for Automated Reasoning.
Fifteen and a half months after the first commit:
Metric | Value |
|---|---|
Lean files in | 253 |
Non-empty lines | 33,727 |
Theorems and lemmas | 2,579 |
Definitions, structures, classes | 1,179 |
Contributors | 57 |
Merged pull requests | 622 |
GitHub stars / forks | 728 / 202 |
leanprover/cslib main at c149a9d (2026-10-01).
Area | Lines | Highlights |
|---|---|---|
| 12,319 | Automata, Turing machines, URM, circuits, FLP |
| 7,836 | LTS, bisimulation, relations, monads, data, syntax |
| 7,004 | λ-calculi, F<:, CCS, choreographies, processes |
| 3,327 | Linear, modal, HML, propositional |
| 1,423 | Perfect secrecy, secret sharing, commitments, PRGs |
| 872 | PAC learning, VC dimension |
| 660 | Time monad, sorting, Diffie–Hellman |
| 251 | PMF, statistical distance |
CSLib lives in the leanprover GitHub organisation, next to Lean itself. The steering committee guides the vision and secures support:
Clark Barrett (Stanford, Amazon)
Swarat Chaudhuri (Google DeepMind, UT Austin)
Jim Grundy (Amazon)
Pushmeet Kohli (Google DeepMind)
Leonardo de Moura (Lean FRO, Amazon)
Fabrizio Montesi (FORM, University of Southern Denmark)
The maintainers own the codebase and its technical direction. Lead maintainer: Fabrizio Montesi. Technical leads: Alexandre Rademaker (Logic), Sorrachai Yingchareonthawornchai (Algorithms and data structures).
Area maintainers:
Chris Henson (Drexel): λ-calculus, metaprogramming
Kim Morrison (Lean FRO): CI/CD with Lean and Mathlib
Samuel Schlesinger (Google): Complexity, cryptography, learning theory
Christian Reitwiessner: Complexity
and the technical leads
New members are invited on project need and merit.
Two papers, two complementary views of the same library:
Computer Science as Infrastructure: the Spine of CSLib. Christopher Henson and Fabrizio Montesi. Founding technical principles, reusable semantic interfaces (reduction and labelled transition systems), proof automation, CI for Mathlib compatibility. arXiv:2602.15078
CSLib: The Lean Computer Science Library. Presented by Clark Barrett and Sorrachai Yingchareonthawornchai; the vision paper of the steering committee and technical leads, forthcoming in CACM. arXiv:2602.04846
Depend on it first. That is the fastest way to find what is missing:
[[require]]
name = "cslib"
scope = "leanprover"
rev = "<a commit SHA>" # pin it: `main` moves fast
Most contributions are pull requests, approved by a relevant maintainer.
Anything cross-cutting: discuss first, on the #CSLib Zulip channel or an issue.
Weekly online meetings
The rules are written down: CONTRIBUTING.md (style, documentation, AI
use) and DECISION_MAKING.md (how PRs are reviewed and accepted).
From DECISION_MAKING.md:
Every PR is reviewed. Anyone may review, and reviewing is a contribution in its own right.
Acceptance needs one maintainer's approval, and every substantive part reviewed.
An objection blocks the merge until it is resolved, by technical discussion first. If that fails, the Lead Maintainer decides.
Reuse over duplication: build on existing definitions, in CSLib or its dependencies, unless an alternative has a clear technical benefit.
Every Lean block from here on is elaborated against CSLib at 12ba7cf,
unless the slide says it is quoting.
The most reused abstraction in the library: a labelled transition system is just a ternary relation.
structure LTS (State : Type u) (Label : Type v) where
/-- The transition relation. -/
Tr : State → Label → State → PropA nondeterministic automaton is an LTS with start states:
structure NA (State Symbol : Type*) extends LTS State Symbol where
/-- The set of initial states of the automaton. -/
start : Set State
Nondeterministic Turing machines SingleTapeNTM extends NA, with tape actions
(read, write, move) as labels.
CSLib authors: Fabrizio Montesi (@fmontesi), Thomas Waring (@thomaskwaring), Ching-Tsun Chou (@ctchou), Chris Henson (@chenson2018)
An NA.FinAcc is an NA (an LTS with start states) plus a set of accepting
states. Its instance of Acceptor says when a word is accepted: a multistep
transition (MTr) labelled by the word leads from a start state to an
accepting state.
instance : Acceptor (FinAcc State Symbol) Symbol where
Accepts (a : FinAcc State Symbol) (xs : List Symbol) :=
∃ s ∈ a.start, ∃ s' ∈ a.accept, a.MTr s xs s'
language a is the set of words that a accepts.
Words over {a, b} that end in a. On a, state q₀ may stay or move to
q₁:
inductive St | q₀ | q₁
def endsInA : NA.FinAcc St Char where
Tr s c s' := (s = .q₀ ∧ s' = .q₀) ∨ (s = .q₀ ∧ c = 'a' ∧ s' = .q₁)
start := {.q₀}
accept := {.q₁}
example : ['b', 'a'] ∈ language endsInA :=
⟨.q₀, rfl, .q₁, rfl,
.stepL (.inl ⟨rfl, rfl⟩) (.stepL (.inr ⟨rfl, rfl, rfl⟩) .refl)⟩
example : ['a', 'b'] ∉ language endsInA := by⊢ ['a', 'b'] ∉ language endsInA
rintro ⟨_, rfl, _, rfl, h⟩h:endsInA.MTr St.q₀ ['a', 'b'] St.q₁⊢ False
cases h with | stepL _ h =>s2✝:Sta✝:endsInA.Tr St.q₀ 'a' s2✝h:endsInA.MTr s2✝ ['b'] St.q₁⊢ False
cases h with | stepL h r =>s2✝¹:Sta✝:endsInA.Tr St.q₀ 'a' s2✝s2✝:Sth:endsInA.Tr s2✝¹ 'b' s2✝r:endsInA.MTr s2✝ [] St.q₁⊢ False cases rs2✝:Sta✝:endsInA.Tr St.q₀ 'a' s2✝h:endsInA.Tr s2✝ 'b' St.q₁⊢ False; simp [endsInA] at hAll goals completed! 🐙
def IsBisimulation (lts₁ : LTS State₁ Label) (lts₂ : LTS State₂ Label)
(r : State₁ → State₂ → Prop) : Prop :=
∀ ⦃s₁ s₂⦄, r s₁ s₂ → ∀ μ, (
(∀ s₁', lts₁.Tr s₁ μ s₁' → ∃ s₂', lts₂.Tr s₂ μ s₂' ∧ r s₁' s₂')
∧
(∀ s₂', lts₂.Tr s₂ μ s₂' → ∃ s₁', lts₁.Tr s₁ μ s₁' ∧ r s₁' s₂')
)
Two states are bisimilar, s₁ ~[lts₁, lts₂] s₂, when some bisimulation
relates them.
Two clocks: one state ticking forever, and two states ticking back and forth. To prove them bisimilar, exhibit a relation, here everything to everything, and check the transfer condition:
inductive C₁ | t₀
inductive C₂ | t₁ | t₂
def one : LTS C₁ Unit := ⟨fun _ _ _ => True⟩
def two : LTS C₂ Unit := ⟨fun s _ s' => s ≠ s'⟩
example : C₁.t₀ ~[one, two] C₂.t₁ := by⊢ C₁.t₀ ~[one,two] C₂.t₁
refine ⟨fun _ _ => True, trivial, ?_⟩⊢ one.IsBisimulation two fun x x_1 => True
intro _ s₂ _ _s₁✝:C₁s₂:C₂a✝:Trueμ✝:Unit⊢ (∀ (s₁' : C₁), one.Tr s₁✝ μ✝ s₁' → ∃ s₂', two.Tr s₂ μ✝ s₂' ∧ (fun x x_1 => True) s₁' s₂') ∧
∀ (s₂' : C₂), two.Tr s₂ μ✝ s₂' → ∃ s₁', one.Tr s₁✝ μ✝ s₁' ∧ (fun x x_1 => True) s₁' s₂'
refine ⟨fun _ _ => ?_, fun _ _ => ⟨.t₀, trivial, trivial⟩⟩s₁✝:C₁s₂:C₂a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ ∃ s₂', two.Tr s₂ μ✝ s₂' ∧ (fun x x_1 => True) x✝¹ s₂'
cases s₂s₁✝:C₁a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ ∃ s₂', two.Tr C₂.t₁ μ✝ s₂' ∧ (fun x x_1 => True) x✝¹ s₂'s₁✝:C₁a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ ∃ s₂', two.Tr C₂.t₂ μ✝ s₂' ∧ (fun x x_1 => True) x✝¹ s₂'
·s₁✝:C₁a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ ∃ s₂', two.Tr C₂.t₁ μ✝ s₂' ∧ (fun x x_1 => True) x✝¹ s₂' exact ⟨.t₂, bys₁✝:C₁a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ two.Tr C₂.t₁ μ✝ C₂.t₂ simp [two]All goals completed! 🐙, trivial⟩
·s₁✝:C₁a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ ∃ s₂', two.Tr C₂.t₂ μ✝ s₂' ∧ (fun x x_1 => True) x✝¹ s₂' exact ⟨.t₁, bys₁✝:C₁a✝:Trueμ✝:Unitx✝¹:C₁x✝:one.Tr s₁✝ μ✝ x✝¹⊢ two.Tr C₂.t₂ μ✝ C₂.t₁ simp [two]All goals completed! 🐙, trivial⟩
Milner, Communication and Concurrency (1989).
Good: coin, then you pick tea or coffee.
\mathit{Good} \stackrel{\text{def}}{=} \mathit{coin}.(\mathit{tea}.\mathit{Good} + \mathit{coffee}.\mathit{Good})
Bad: coin, then the machine picks.
\mathit{Bad} \stackrel{\text{def}}{=} \mathit{coin}.\mathit{tea}.\mathit{Bad} + \mathit{coin}.\mathit{coffee}.\mathit{Bad}
Their logs are identical: coin tea, coin coffee, ... Same traces. A trace-based test cannot tell them apart.
After the coin, the bad machine may already have chosen coffee, and then the tea button does nothing. A trace records the actions that happened; it never records an action that was refused.
Example: Jesse Alama (@jessealama). CSLib authors: Fabrizio Montesi (@fmontesi).
Trace equivalence ignores which actions are available at each step.
Bisimilarity (slide 14.4): every move of one machine must be matched by a move of the other, and the states reached must again be related, at every step.
Two ways to prove that our machines are not bisimilar:
Directly, as CSLib's own vm_ltsD_ltsND_not_bisim does: assume a bisimulation and follow the transitions until it breaks.
With a logic: find a formula that one machine satisfies and the other does not.
abbrev coin := Act.name "coin"
abbrev tea := Act.name "tea"
abbrev coffee := Act.name "coffee"
inductive Machine | good | bad
/-- good = coin.(tea + coffee) bad = coin.tea + coin.coffee -/
@[grind =]
def defs : Machine → Option (Process String Machine)
| .good => some `(CCS| coin. ((tea. const .good) + (coffee. const .good)))
| .bad => some `(CCS| (coin. tea. const .bad) + (coin. coffee. const .bad))
/-- Both machines live in one transition system: CCS's, from CSLib. -/
abbrev machines := CCS.lts (defs := defs)
One LTS, machines, with two constants: the good machine is const .good,
the bad one const .bad.
The transitions of a CCS process are an inductive relation, one rule per
construct. CSLib declares it as an LTS with @[lts lts]:
@[lts lts]
inductive Tr : Process Name Constant → Act Name → Process Name Constant → Prop where
| pre : Tr (pre μ p) μ p
| parL : Tr p μ p' → Tr (par p q) μ (par p' q)
| parR : Tr q μ q' → Tr (par p q) μ (par p q')
| com :
μ.Co μ' → Tr p μ p' → Tr q μ' q' → Tr (par p q) Act.τ (par p' q')
| choiceL : Tr p μ p' → Tr (choice p q) μ p'
| choiceR : Tr q μ q' → Tr (choice p q) μ q'
| res : μ ≠ Act.name a → μ ≠ Act.coname a → Tr p μ p' → Tr (res a p) μ (res a p')
| const : defs k = some p → Tr p μ p' → Tr (const k) μ p'
Our machines need four rules: pre, choiceL, choiceR, and const, which
unfolds a constant through defs.
Both have the traces coin tea, coin coffee, ... After coin, Good
still offers both drinks; Bad has already committed to one.
Assume a bisimulation r. After coin, the good machine has a single
successor, so r must relate tea.Good + coffee.Good with tea.Bad. Only the
first can do coffee:
theorem good_not_bisim_bad_direct :
¬ ((const Machine.good) ~[machines] (const Machine.bad)) := by⊢ ¬(«const» Machine.good) ~[machines] («const» Machine.bad)
rintro ⟨r, hr, hb⟩r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines r⊢ False
let p₁ := `(CCS| (tea. const Machine.good) + (coffee. const Machine.good))r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))⊢ False
let q₁ := `(CCS| tea. const Machine.bad)r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))q₁:Process String Machine := pre tea («const» Machine.bad)⊢ False
have hdet : machines.DeterministicStateLabel (const .good) coin := by⊢ ¬(«const» Machine.good) ~[machines] («const» Machine.bad)
intro _ _ h₁ h₂r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))q₁:Process String Machine := pre tea («const» Machine.bad)s₁✝:Process String Machines₂✝:Process String Machineh₁:machines.Tr («const» Machine.good) coin s₁✝h₂:machines.Tr («const» Machine.good) coin s₂✝⊢ s₁✝ = s₂✝
grind [const_tr h₁, const_tr h₂]All goals completed! 🐙r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))q₁:Process String Machine := pre tea («const» Machine.bad)hdet:machines.DeterministicStateLabel («const» Machine.good) coin⊢ False
have h : r p₁ q₁ :=
match_deterministic hb hr hdet
(.const rfl .pre) (.const rfl (.choiceL .pre))r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))q₁:Process String Machine := pre tea («const» Machine.bad)hdet:machines.DeterministicStateLabel («const» Machine.good) coinh:r p₁ q₁⊢ False
have hpq : p₁ ~[machines] q₁ := ⟨r, h, hb⟩r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))q₁:Process String Machine := pre tea («const» Machine.bad)hdet:machines.DeterministicStateLabel («const» Machine.good) coinh:r p₁ q₁hpq:p₁ ~[machines] q₁⊢ False
have hc : machines.Tr p₁ coffee (const .good) := .choiceR .prer:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines rp₁:Process String Machine := (pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good))q₁:Process String Machine := pre tea («const» Machine.bad)hdet:machines.DeterministicStateLabel («const» Machine.good) coinh:r p₁ q₁hpq:p₁ ~[machines] q₁hc:machines.Tr p₁ coffee («const» Machine.good)⊢ False
grind [hpq.follow_fst]All goals completed! 🐙
A modal logic for labelled transition systems. Its formulas describe which actions a state can perform, and what holds after them:
\begin{aligned}
s \models \langle a\rangle\,\varphi &\iff \exists s'.\; s \xrightarrow{a} s' \text{ and } s' \models \varphi
&& \text{some } a \text{ is possible, then } \varphi \\
s \models [a]\,\varphi &\iff \forall s'.\; s \xrightarrow{a} s' \text{ implies } s' \models \varphi
&& \text{after every } a,\ \varphi
\end{aligned}
plus ⊤, ¬ and ∧.
Hennessy–Milner theorem (1985):
Bisimilar states satisfy the same formulas.
For image-finite systems, the converse holds: states that are not bisimilar are always told apart by some formula.
The first direction is the one we use, as CSLib's bisimulation_satisfies:
lemma bisimulation_satisfies {hrb : m.lts.IsHomBisimulation r}
(hv : ∀ {s1 s2}, r s1 s2 → ∀ p, m.v s1 p ↔ m.v s2 p) (hr : r s1 s2)
(φ : HML.Proposition Label Atom) : ⇓HML[m,s1 ⊨ φ] ↔ ⇓HML[m,s2 ⊨ φ] := byState:Type u_2Label:Type u_1Atom:Type u_3m:HML.Model State Label Atomr:State → State → Props1:States2:Statehrb:m.lts.IsHomBisimulation rhv:∀ {s1 s2 : State}, r s1 s2 → ∀ (p : Atom), m.v s1 p ↔ m.v s2 phr:r s1 s2φ:HML.Proposition Label Atom⊢ ⇓Modal[m.toModal,s1 ⊨ φ] ↔ ⇓Modal[m.toModal,s2 ⊨ φ]CSLib authors: Fabrizio Montesi (@fmontesi), Marco Peressotti (@mperessotti), Alexandre Rademaker (@arademaker).
In CSLib, formulas are evaluated in a model, an LTS plus a valuation of atomic
propositions (Cslib/Logics/HML/Basic.lean):
structure Model State Label Atom where
/-- The labelled transition system. -/
lts : LTS State Label
/-- Valuation of atoms at states. -/
v : State → Atom → Prop
Our model m is the CCS LTS of both machines with no atomic propositions
(Atom := Empty), so only transitions matter. The formula
[coin] ⟨tea⟩ ⊤: after every coin, tea is possible.
/-- No atomic propositions: only the transitions matter. -/
abbrev m : HML.Model (Process String Machine) (Act String) Empty :=
⟨machines, fun _ _ => False⟩
/-- "After every coin, tea is on offer." -/
def teaAfterCoin : HML.Proposition (Act String) Empty := d[coin] (d⟨tea⟩ ⊤)
theorem good_ok : ⇓HML[m, const Machine.good ⊨ teaAfterCoin] := by⊢ ⇓Modal[m.toModal,«const» Machine.good ⊨ teaAfterCoin]
rw [teaAfterCoin,⊢ ⇓Modal[m.toModal,«const» Machine.good ⊨ d[coin](d⟨tea⟩⊤)] Satisfies.hml_dynBox_iff_forall⊢ ∀ (s' : Process String Machine), m.lts.Tr («const» Machine.good) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]]⊢ ∀ (s' : Process String Machine), m.lts.Tr («const» Machine.good) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]
rintro s' htrs':Process String Machinehtr:m.lts.Tr («const» Machine.good) coin s'⊢ ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]
obtain ⟨_, ⟨⟩, ⟨⟩⟩ := const_tr htrhtr:m.lts.Tr («const» Machine.good) coin ((pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good)))⊢ ⇓Modal[m.toModal,(pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good)) ⊨ d⟨tea⟩⊤]
exact Satisfies.hml_dynDiamond_intro (Tr.choiceL Tr.pre) (byhtr:m.lts.Tr («const» Machine.good) coin ((pre tea («const» Machine.good)).choice (pre coffee («const» Machine.good)))⊢ ⇓Modal[m.toModal,«const» Machine.good ⊨ ⊤] grind [modal]All goals completed! 🐙)
theorem bad_fails : ¬ ⇓HML[m, const Machine.bad ⊨ teaAfterCoin] := by⊢ ¬⇓Modal[m.toModal,«const» Machine.bad ⊨ teaAfterCoin]
rw [teaAfterCoin,⊢ ¬⇓Modal[m.toModal,«const» Machine.bad ⊨ d[coin](d⟨tea⟩⊤)] Satisfies.hml_dynBox_iff_forall⊢ ¬∀ (s' : Process String Machine), m.lts.Tr («const» Machine.bad) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]]⊢ ¬∀ (s' : Process String Machine), m.lts.Tr («const» Machine.bad) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]
intro hh:∀ (s' : Process String Machine), m.lts.Tr («const» Machine.bad) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]⊢ False
have := h (pre coffee (const .bad)) (Tr.const rfl (Tr.choiceR Tr.pre))h:∀ (s' : Process String Machine), m.lts.Tr («const» Machine.bad) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]this:⇓Modal[m.toModal,pre coffee («const» Machine.bad) ⊨ d⟨tea⟩⊤]⊢ False
obtain ⟨_, htr, -⟩ := Satisfies.hml_dynDiamond_iff_exists.mp thish:∀ (s' : Process String Machine), m.lts.Tr («const» Machine.bad) coin s' → ⇓Modal[m.toModal,s' ⊨ d⟨tea⟩⊤]this:⇓Modal[m.toModal,pre coffee («const» Machine.bad) ⊨ d⟨tea⟩⊤]w✝:Process String Machinehtr:m.lts.Tr (pre coffee («const» Machine.bad)) tea w✝⊢ False
grindAll goals completed! 🐙
In obtain ⟨_, ⟨⟩, ⟨⟩⟩, each ⟨⟩ case-splits a hypothesis and substitutes
it away: after one coin, the good machine has a single successor, and the goal
is now about that successor.
theorem good_not_bisim_bad :
¬ ((const Machine.good) ~[machines] (const Machine.bad)) := by⊢ ¬(«const» Machine.good) ~[machines] («const» Machine.bad)
rintro ⟨r, hr, hb⟩r:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines r⊢ False
grind [bisimulation_satisfies (m := m) (hrb := hb) (byr:Process String Machine → Process String Machine → Prophr:r («const» Machine.good) («const» Machine.bad)hb:machines.IsBisimulation machines r⊢ ∀ {s1 s2 : Process String Machine}, r s1 s2 → ∀ (p : Empty), m.v s1 p ↔ m.v s2 p simpAll goals completed! 🐙) hr teaAfterCoin,
good_ok, bad_fails]
So a formula true for one machine and false for the other is a certificate that they are not bisimilar.
A language: CCS, with its operational semantics.
A logic: HML, evaluated on that semantics.
The semantic infrastructure that connects them: transition systems, bisimulation, the Hennessy–Milner theorem.
Languages produce transition systems. Logics consume them. Metatheory is proved once.
To check that a fast program is correct, we need a specification. Often the simplest program that does the job is that specification: obviously correct, but too slow to use.
Specification: insertion sort, six lines, nothing clever.
Implementation: CSLib's verified mergeSort, standing in for the optimised
version an engineer, or an AI agent, would write.
Goal: prove they return the same output on every input, and count how many comparisons each one makes.
Both are written in CSLib's time monad TimeM: a program returns its result
together with a cost, and each ✓ charges one comparison.
Example: Jesse Alama (@jessealama). CSLib authors: Sorrachai (@sorrachai), Eric Wieser (@eric-wieser), Kim Morrison (@kim-em).
variable {α : Type} [LinearOrder α]
def insertSorted (x : α) : List α → TimeM ℕ (List α)
| [] => return [x]
| y :: ys => do
✓ let c := (x ≤ y : Bool)
if c then return x :: y :: ys
else return y :: (← insertSorted x ys)
def insertionSort : List α → TimeM ℕ (List α)
| [] => return []
| x :: xs => do insertSorted x (← insertionSort xs)
#eval[1, 2, 3, 4, 5] ⟪insertionSort [5, 3, 1, 4, 2]⟫
[1, 2, 3, 4, 5]#eval[1, 2, 3, 4, 5] ⟪mergeSort [5, 3, 1, 4, 2]⟫
[1, 2, 3, 4, 5]#eval499500 (insertionSort (List.range 1000).reverse).time
499500#eval4932 (mergeSort (List.range 1000).reverse).time
4932
⟪p⟫ drops the cost and keeps the result. Without its cost, our insertion
sort is Mathlib's List.insertionSort:
@[simp, grind =]
theorem ret_insertSorted (x : α) (l : List α) :
⟪insertSorted x l⟫ = l.orderedInsert (· ≤ ·) x := byα:Typeinst✝:LinearOrder αx:αl:List α⊢ (insertSorted x l).ret = List.orderedInsert (fun x1 x2 => x1 ≤ x2) x l
fun_induction insertSorted with grindAll goals completed! 🐙
@[simp, grind =]
theorem ret_insertionSort (xs : List α) :
⟪insertionSort xs⟫ = xs.insertionSort (· ≤ ·) := byα:Typeinst✝:LinearOrder αxs:List α⊢ (insertionSort xs).ret = List.insertionSort (fun x1 x2 => x1 ≤ x2) xs
fun_induction insertionSort with grindAll goals completed! 🐙
Why Mathlib's definitions: Mathlib already proves that List.insertionSort
returns a sorted permutation of its input. One bridge lemma, and we reuse
those proofs instead of redoing them.
Mathlib authors: Jeremy Avigad (@avigad), Wrenna Robson (@wrenna-robson).
Both outputs are sorted permutations of the input, and a list has only one sorted permutation:
/-- The fast one agrees with the obvious one. Not tested. Proved. -/
theorem mergeSort_eq_insertionSort (xs : List α) :
⟪mergeSort xs⟫ = ⟪insertionSort xs⟫ := byα:Typeinst✝:LinearOrder αxs:List α⊢ (mergeSort xs).ret = (insertionSort xs).ret
obtain ⟨hs, hp⟩ := mergeSort_correct xsα:Typeinst✝:LinearOrder αxs:List αhs:IsSorted (mergeSort xs).rethp:(mergeSort xs).ret.Perm xs⊢ (mergeSort xs).ret = (insertionSort xs).ret
simpa using List.Perm.eq_of_sortedLE hs.sortedLE List.sortedLE_insertionSort
(hp.trans (List.perm_insertionSort _ xs).symm)All goals completed! 🐙
From CSLib: mergeSort_correct, the output of mergeSort is sorted and a
permutation of xs.
From Mathlib: List.sortedLE_insertionSort and List.perm_insertionSort,
the same for insertion sort; and List.Perm.eq_of_sortedLE, two sorted
lists that are permutations of each other are equal.
insertionSort is n²theorem insertSorted_time (x : α) (l : List α) :
(insertSorted x l).time ≤ l.length := byα:Typeinst✝:LinearOrder αx:αl:List α⊢ (insertSorted x l).time ≤ l.length
fun_induction insertSorted with grindAll goals completed! 🐙
@[simp] theorem insertionSort_length (xs : List α) :
⟪insertionSort xs⟫.length = xs.length := byα:Typeinst✝:LinearOrder αxs:List α⊢ (insertionSort xs).ret.length = xs.length
simpAll goals completed! 🐙
theorem insertionSort_time (xs : List α) :
(insertionSort xs).time ≤ xs.length ^ 2 := byα:Typeinst✝:LinearOrder αxs:List α⊢ (insertionSort xs).time ≤ xs.length ^ 2
fun_induction insertionSortα:Typeinst✝:LinearOrder α⊢ (pure []).time ≤ [].length ^ 2α:Typeinst✝:LinearOrder αy✝:αys✝:List αih1✝:(insertionSort ys✝).time ≤ ys✝.length ^ 2⊢ (do
let __do_lift ← insertionSort ys✝
insertSorted y✝ __do_lift).time ≤
(y✝ :: ys✝).length ^ 2 <;>α:Typeinst✝:LinearOrder α⊢ (pure []).time ≤ [].length ^ 2α:Typeinst✝:LinearOrder αy✝:αys✝:List αih1✝:(insertionSort ys✝).time ≤ ys✝.length ^ 2⊢ (do
let __do_lift ← insertionSort ys✝
insertSorted y✝ __do_lift).time ≤
(y✝ :: ys✝).length ^ 2 simpα:Typeinst✝:LinearOrder αy✝:αys✝:List αih1✝:(insertionSort ys✝).time ≤ ys✝.length ^ 2⊢ (insertionSort ys✝).time + (insertSorted y✝ (List.insertionSort (fun x1 x2 => x1 ≤ x2) ys✝)).time ≤ (ys✝.length + 1) ^ 2
grind [insertSorted_time, insertionSort_length]All goals completed! 🐙
The mergeSort is n ⌈log₂ n⌉ as proved in CSLib
theorem mergeSort_time (xs : List α) :
let n := xs.length
(mergeSort xs).time ≤ n * clog 2 n := byα:Typeinst✝:LinearOrder αxs:List α⊢ let n := xs.length;
(mergeSort xs).time ≤ n * clog 2 n
grind [mergeSort_time_le, timeMergeSortRec_le]All goals completed! 🐙The exact worst case for insertion sort is accepted:
set_option maxRecDepth 4000 in
example :
(insertionSort (List.range 100).reverse).time = 100 * 99 / 2 := byα:Typeinst✝:LinearOrder α⊢ (insertionSort (List.range 100).reverse).time = 100 * 99 / 2
decideAll goals completed! 🐙
A false claim about merge sort is refused. Click native_decide for the
reason:
example : (mergeSort (List.range 1000).reverse).time = 5000 := byα:Typeinst✝:LinearOrder α⊢ (mergeSort (List.range 1000).reverse).time = 5000
native_decideα:Typeinst✝:LinearOrder α⊢ (mergeSort (List.range 1000).reverse).time = 5000Tactic `native_decide` evaluated that the proposition
(mergeSort (List.range 1000).reverse).time = 5000
is false
The library | The Initiative |
|---|---|
| A programme at Renaissance Philanthropy |
Community-governed; mostly volunteers | A director and a small team |
Decides its own technical direction | Helps CSLib growth; does not lead it |
Grows organically | Invests where growth won't happen on its own |
Modelled on the Mathlib Initiative: a thriving open-source project, paired with a dedicated team for work that is hard to sustain on volunteer effort.
Goal | What it means |
|---|---|
G1: Sustain organic growth | Review workflow, CI and DevOps, documentation |
G2: Demonstrate value | Pulling projects; an open AI dataset |
G3: Focused technical investment | Boole, semantics, specification logics, RAM model ... |
G4: Support community efforts | Software Foundations in Lean, Separation Logic, Signal Shot (BAIF, Crypto) |
The most important idea in the plan:
External formalisation efforts that demand new CSLib primitives and upstream their general results back.
They aim at software the world uses every day.
Their requirements decide what CSLib builds next.
The general pieces they produce come back to the library.
The test they impose: general lemmas must be separable from project code. If that is hard, the API is wrong.
lean-zip (Kim Morrison): DEFLATE in pure
Lean, with a kernel-checked theorem that decompression inverts compression,
for every input and every level. About 1,100 theorems, no sorry.
Written by AI agents, loosely supervised. Agents claimed issues, worked in their own git worktrees, and opened pull requests.
The proof is the gate. A PR could not merge unless the round-trip proof still went through.
So optimisation is safe to automate. It ended up faster than Rust's
miniz_oxide at the same compression ratio.
For the Initiative: learn from this way of building verified software, and upstream to CSLib whatever is general.
Benjamin Pierce (Penn), Michael Hicks (Penn and AWS) and Harry Goldstein (Buffalo) are adapting Software Foundations (also here), the standard introduction to interactive theorem proving, to Lean.
Courses that draw on CSLib and contribute back to it.
A pipeline from students to library contributors.
A planned follow-up course on agentic proof engineering.
Teaching is the maturity test that matters most: it forces the API to be explainable.
cslib-community/fad: Bird and Gibbons' Algorithm Design with Haskell, in Lean, taught at FGV/EMAp. We are starting to adapt it to Verso and to use CSLib's defs like TimeM:
def maxM (x y : ℕ) : TimeM ℕ ℕ := do ✓ pure (max x y)
def maximumM : List ℕ → TimeM ℕ ℕ
| [] => pure 0
| [x] => pure x
| x :: y :: ys => do maxM x (← maximumM (y :: ys))
/-- The maximum of `n` numbers takes `n - 1` comparisons. -/
theorem time_maximum (xs : List ℕ) : (maximumM xs).time = xs.length - 1 := byxs:List ℕ⊢ (maximumM xs).time = xs.length - 1
fun_induction maximumM⊢ (pure 0).time = [].length - 1x✝:ℕ⊢ (pure x✝).time = [x✝].length - 1x✝:ℕy✝:ℕys✝:List ℕih1✝:(maximumM (y✝ :: ys✝)).time = (y✝ :: ys✝).length - 1⊢ (do
let __do_lift ← maximumM (y✝ :: ys✝)
maxM x✝ __do_lift).time =
(x✝ :: y✝ :: ys✝).length - 1 <;>⊢ (pure 0).time = [].length - 1x✝:ℕ⊢ (pure x✝).time = [x✝].length - 1x✝:ℕy✝:ℕys✝:List ℕih1✝:(maximumM (y✝ :: ys✝)).time = (y✝ :: ys✝).length - 1⊢ (do
let __do_lift ← maximumM (y✝ :: ys✝)
maxM x✝ __do_lift).time =
(x✝ :: y✝ :: ys✝).length - 1 simp_all [maxM]All goals completed! 🐙
Beyond TimeM, two open proposals for a query-complexity model, #401 (Kim Morrison) and #685 (Shreyas Srinivas), derive costs from the operations a program performs: useful for amortised running times (ADwH §2.4).
Computational Semantics with Lean (repo) brings van Eijck and Unger's Computational Semantics with Functional Programming from Haskell to Lean, while the FGV/EMAp course runs.
Now: stay close to the book, the safe route while students follow it.
Next weeks: reuse what is already mapped: grammars, propositional and modal logic, LTS.
Then: contribute what CSLib lacks: first-order logic, feature structures and their unification.
With Software Foundations in Lean and FAD: CSLib as a base for teaching.
Amazon's s2n-bignum: integer arithmetic routines for cryptography, in x86-64 and AArch64 machine code. Correctness and constant-time execution are verified in HOL Light, with a purpose-built relational Hoare logic.
cslib-community/bignum: a faithful port of that logic to Lean 4, including its lenses (components). The aim: verify other critical machine code in Lean, possibly with AI-assisted proofs.
Two of s2n-bignum's ten ARM tutorials (simple, sequence) already run in
Lean, on the first model, while the architecture is being rewritten.
bignum: Guilherme Lima (@gflima), Alexandre Rademaker (@arademaker).
A component is a lens on one part of the machine state: a way to read it and a way to write it.
structure Component (α : Type u₁) (β : Type u₂) where
/-- Reader function. -/
read : α → β
/-- Writer function. -/
write : β → α → α
XREG n is register n: (XREG n).read s reads it in state s, and
(XREG n).write 0xabc s returns s with 0xabc in register n.
/-- Main integer registers. -/
def XREG (n : Nat) : Component State (BitVec 64) :=
if n ≥ 31 then XZR else registers :> .element ndef ADD {α : Type u} {w : Nat}
(Rd : Component α (BitVec w))
(Rm : Component α (BitVec w))
(Rn : Component α (BitVec w))
(s : α) : α → Prop :=
let m := Rm.read s
let n := Rn.read s
let d : BitVec w := m + n
(Rd ≔ d) s
Rd, Rm and Rn are components passed as arguments, not specific
registers. The same ADD, unchanged, gives:
ADD X0 X1 X2: 64-bit registers
ADD W0 W1 W2: 32-bit registers
ADD X0 (X1.shifted LSL 1) (.rvalue 0x33): a shifted register and a literal
No separate version for shifts, literals or 32 bits; the same holds for most other instructions.
github.com/cslib-community: home for community projects built with, around, and for CSLib.
fad: functional algorithm design.
CSwL: computational semantics.
bignum: s2n-bignum in Lean.
AlgoLib: graph algorithms, flows, union–find, with correctness and running-time proofs.
Where projects live before anything is upstreamed.
The ideal outcome would be a "flywheel" in which both AI and human experts become progressively more efficient at producing new knowledge.
— Barrett et al., CSLib: Towards a Lean Computer Science Library, CACM (forthcoming)
CSLib as vocabulary: definitions an AI can state specifications in.
CSLib as data: a planned regular dataset of goal states, tactic traces and cost annotations, in Mathlib's format.
The kernel as the gate: RL agents find every shortcut; the kernel is the one thing they cannot fake.
CSLib ships an AGENTS.md: build, lint and test instructions for coding
agents.
It follows Mathlib's disclosure rule:
If you use artificial intelligence ... please explain this in the PR description. Explain which tool(s) you used and how you used it.
Two reports from this year:
July, on Zulip: a proof about η-reduction and strong normalisation, invented by Claude and checked with Aristotle, found in no paper or textbook (#860).
September, cut elimination for linear logic: AI agents that "until recently failed badly" now carry out the proof; the library itself has grown enough "to give useful context".
Not whether AI writes Lean. What a library has to look like for AI-written contributions to be reviewable.
Specifications humans can read.
Small, stable interfaces that proofs can target.
Review that scales with volume.
The community is discussing how humans and agents should coordinate. One idea on the table: blueprints written by humans, stating the intent that both people and agents then work toward.
Depend on CSLib in your project, and tell us what is missing.
Bring a pulling project.
Review a PR, or join the weekly meeting.
Say hello on the #CSLib channel of the Lean Zulip.
Alexandre Rademaker
CSLib Initiative, Renaissance Philanthropy · FGV/EMAp
cslib.io · github.com/leanprover/cslib
Further reading: Barrett et al., CSLib: Towards a Lean Computer Science Library (CACM, forthcoming; arXiv:2602.04846). Henson and Montesi, Computer Science as Infrastructure (FLoC 2026; arXiv:2602.15078).
alexandre.rademaker@renphil.org