
lean4-skills
LLM coding agents are already useful Lean copilots. For large formalizations, model intelligence is not the only bottleneck. One bottleneck we can control is the workflow: knowing when to search, inspect, edit, checkpoint, review, and stop.
Useful, but local and easy to dilute across a long session. The agent is left to infer how to behave.


lean4-skills turns prompts into a protocol.
An exchangeable {0,1}-sequence: the joint law is unchanged by permuting finitely many coordinates. Informally, the order of the observations carries no information.
A canonical way to get an exchangeable sequence:
It turns out this is the general story.
The theorem says this is the general picture: there is a latent random probability measure, conditional on which the sequence is i.i.d.
The Lean formalization is the standard-Borel, measure-theoretic version of this story. This involves kernels, conditional laws, and uniqueness of product measures.

Informally: an infinite exchangeable sequence (one whose joint law is invariant under finite permutations of coordinates) is conditionally i.i.d., given a directing random probability measure.
Ryll-Nardzewski’s formulation packages this as an equivalence involving contractability; exchangeability is one side of that equivalence.
The library’s headline theorem, stated in Lean:
theorem deFinetti_RyllNardzewski_equivalence
[StandardBorelSpace Ω]
{α : Type*} [MeasurableSpace α] [StandardBorelSpace α] [Nonempty α]
{μ : Measure Ω} [IsProbabilityMeasure μ]
(X : ℕ → Ω → α) (hX_meas : ∀ i, Measurable (X i)) :
Contractable μ X ↔ Exchangeable μ X ∧ ConditionallyIID μ X
The proof engineering involves standard Borel spaces, conditional laws as kernels, conditional independence, product-measure uniqueness, and L¹/L² convergence.


Three independent routes discharge one shared interface.

All three routes had to produce the same intermediate interface: finite conditional factorization relative to a sub-σ-algebra. From there a single shared “common ending” builds:
100+ files converging on a shared interface. The common ending is both a mathematical interface and a workflow guardrail.

Three independent routes feeding one interface gave continual cross-checks. They caught:
For AI-assisted formalization this matters: the agent gets a hard, reusable target, and divergence between routes is immediately visible.

Coding agents are often surprisingly useful at Lean. But without guidance, agents do not automatically follow the discipline that Lean developers learn over time. Common failure modes (stated charitably):
These operational failures are predictable consequences of optimizing locally without enough process.

lean4-skills grew out of the de Finetti project. When an agent hit a rough spot (a bad instance, a skipped Mathlib search, mishandled conditional-expectation bookkeeping), I often moved the relevant context into a separate skill-development session. That session folded in the new knowledge, keeping enough of the real example to stay concrete, and turned it into reusable guidance.
lean4-skills is a workflow pack for Lean coding agents: references, command recipes, subagents, hooks, scripts, and checks around the model.The shift: from wording a single prompt to structuring the whole interaction: search, inspect, bound, checkpoint, review, replan, hand off.

| Generic agent failure | Protocol countermeasure |
|---|---|
| Reproves existing Mathlib lemmas | Search Mathlib & local code before proving |
| Misses diagnostics or the exact goal | Inspect LSP goals & diagnostics before editing |
| Changes the statement to make it easier | Statement/header stability rule + human approval |
| Adds brittle local adapters | Prefer Mathlib idioms & reusable bridge lemmas |
| Loops on a stalled proof path | Bounded attempts + stop / replan / handoff |
| Compiles, but unmaintainable | Review → refactor → golf → minimize imports |
| Hides trust-base changes | lake build, sorry checks, axiom checks |
Each row is a failure observed in real agent work, paired with the protocol response.
Stopping with a clear obstruction is a success, not a failure. It lets the user replan, and keeps the coding agent from going in circles.

lean4-skills architectureThe skill does not bypass Lean. Lean and Mathlib feedback stays external and authoritative.

lean4-skills is a layered Lean workflow pack. The current plugin distribution targets Claude Code, but the core skill content is mostly host-agnostic. The model sees a short contract first, and pulls in detail only when the situation calls for it.
SKILL.md: a short, always-visible contract.commands/ (11): /draft, /formalize, /autoformalize, /prove, /autoprove, /checkpoint, /review, /refactor, /golf, /learn, /doctor.agents/ (4): proof-repair, proof-golfer, sorry-filler-deep, axiom-eliminator.references/ (39): cycle engine, LSP tools, Mathlib search, compilation errors, measure theory, instance pollution, golfing, performance, and more.UserPromptSubmit validation, and shell guardrails.Durable rules stay in front of the model; specialized advice loads only when it is relevant.

SKILL.md is deliberately short and normative: a small constitution the agent sees on every task.
A routing layer: pick the workflow first, then disclose the right details.

SKILL.md: the contract, from the source## Core Principles
**Search before prove.** …
**Build incrementally.** …
**Respect scope.** …
**Use 100-character line width for Lean files.** …
**Never change statements or add axioms without explicit permission.** …
This is the part the model sees every time: a small operational constitution, not one clever paragraph.

| Recurring issue | Where it lives | Why there |
|---|---|---|
| Reproving Mathlib lemmas | Mathlib-search guide | a habit, not theorem-specific |
| Statement drift | top-level rule + header fence | must be visible every time |
| Conditional-expectation bookkeeping | measure-theory reference | domain-specific, high detail |
| Instance pollution / timeouts | instance-pollution reference | important but situational |
| Repeated sorry-filling loop | /prove · /autoprove | a workflow, not a note |
| Repetitive or longwinded proof | /golf + proof-golfer | post-success optimization |
Rule of thumb: universal invariant → SKILL.md; detailed Lean lore → reference; repeated procedure → command; bounded specialist task → subagent or script.

/prove split from autonomous /autoprove; a shared cycle engine; build-verification ladder; /learn./refactor.When a failure repeated often enough, I wrote it down as a reference, promoted it to a workflow, or hardened it into a contract.

SKILL.md: “do not change statements”advisorylean-lsp-mcp: real goals and diagnostics as evidence, not model guessworkevidencesorry_analyzer.py and axiom checks flag stray sorries and axiomsevidence/gatevalidate_user_prompt.py and guardrails.sh (blocking for covered cases)blockinglake build and Lean checking (a sorry still builds, with a warning)blockingNot every rule is enforced at the same layer. Part of building the skill was deciding whether a rule should be a prompt, a tool habit, a script, a hook, or a build gate. Sorry-freedom and standard-axiom checks are separate gates.


/lean4:prove in detailA slash command constrains the process, not just the final answer. /prove runs that bounded loop one cycle at a time, asking before each new cycle:
lean_multi_attempt, validate diagnostics, apply Lean’s “Try this” code actions, then edit.The fast path caps candidates per sorry, keeps diffs small, forbids cross-file refactors, and treats declaration headers as immutable; deep mode adds snapshot and rollback under a line budget.

/lean4:prove: the agent may not change the theorem**Constraints:** … NO statement changes, … Declaration headers are immutable — if deep mode suggests a header change, it must stop and recommend `/lean4:formalize`.
…
## Stuck Definition
A sorry is **stuck** when: same failure 2-3x, same build error 2x, …
**When stuck:** … Handoff must include LSP queries attempted, top candidates, and `lean_multi_attempt` outcomes.
The agent does not get to prove a different theorem. When stuck, it stops with evidence.

proof-repairA subagent is a specialized prompt with a narrow input/output contract. proof-repair turns an antipattern (“after a local error, rewrite too much”) into an interface: structured error context in, a unified diff out.
## Constraints
- Output ONLY unified diff (no explanations)
- Change ONLY 1-5 lines per call
- Stay within stage budget
- May NOT rewrite entire functions
- May NOT try random tactics
- May NOT skip mathlib search
- May NOT modify declaration headers (header fence). …
…
Intentionally boring: classify the error, make the smallest plausible edit, return a diff. That is an antipattern promoted to a contract.

Mostly operational and constraint-heavy: what evidence to collect, what not to touch, when to escalate, and what shape the output takes.

lean-lsp-mcp: the agent’s InfoView and Mathlib searchHumans use the Lean InfoView to read goals, hypotheses, errors, and types, and they search Mathlib for lemmas. lean-lsp-mcp gives the agent analogous feedback and search tools. Without it, the agent loses much of the feedback loop human Lean users rely on.
The protocol tells the agent when to reach for each: search before proving, inspect the goal before editing, verify before claiming done.

One small Lean snippet, three easy sorries, captured with Claude Code / Opus 4.8. Prompt:
❯ let's work on these: example (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by sorry
example [DecidableEq α] (s t : Finset α) (h : s ⊆ t) : s.card ≤ t.card := by sorry
example (p q : Prop) : p ∧ q → q ∧ p := by sorry
The theorems are intentionally small so the workflow is visible. In a hard theorem the same loop is still there, just harder to see because the math dominates.

▐▛███▜▌ Claude Code v2.1.156
▝▜█████▛▘ Opus 4.8 (1M context) with xhigh effort · Claude Max
▘▘ ▝▝ ~/work/spherepacking-repos/Sphere-Packing-Lean
❯ let's work on these: example (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by sorry
example [DecidableEq α] (s t : Finset α) (h : s ⊆ t) : s.card ≤ t.card := by sorry
example (p q : Prop) : p ∧ q → q ∧ p := by sorry
● I'll start by invoking the Lean 4 skill since we're working on Lean proofs.
● Skill(lean4:lean4)
⎿ Successfully loaded skill
● These are three straightforward warm-up examples. Let me verify the proofs compile using the Lean REPL.
● Now let me verify all three proofs compile:
Called lean-lsp (ctrl+o to expand)
● I need to prime the project path first. Let me check a file in the project.
Searched for 1 pattern, called lean-lsp 2 times (ctrl+o to expand)
● The proofs are correct — the only errors are because autoImplicit is off in this project, so I need to explicitly bind α. Let me add the type variable:
Called lean-lsp (ctrl+o to expand)
● All three compile cleanly (the remaining message is just a whitespace-linter artifact from the snippet layout). Here are the proofs:
example {α : Type*} (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by
rw [List.length_append]
example {α : Type*} [DecidableEq α] (s t : Finset α) (h : s ⊆ t) : s.card ≤ t.card :=
Finset.card_le_card h
example (p q : Prop) : p ∧ q → q ∧ p :=
fun ⟨hp, hq⟩ => ⟨hq, hp⟩
Notes:
1. List length — List.length_append is the statement, so rw closes it. Even shorter: := List.length_append (term mode) or by simp.
2. Finset card — Finset.card_le_card : s ⊆ t → s.card ≤ t.card applies directly. (DecidableEq isn't actually needed for this lemma, but it's harmless to keep.)
3. Conjunction swap — pure term mode: destructure the hypothesis and rebuild swapped. Equivalent alternatives: fun h => ⟨h.2, h.1⟩ or And.comm.mp.
One thing to flag: your originals wrote List α / Finset α without binding α. This project sets autoImplicit false, so I added {α : Type*} explicitly — without it the snippets don't compile
here. If you're dropping these into a section that already has a variable {α : Type*}, you can omit the per-example binder.
Want me to place these somewhere in the repo, or were they just warm-up exercises?
✻ Worked for 1m 30s

▐▛███▜▌ Claude Code v2.1.156
▝▜█████▛▘ Opus 4.8 (1M context) with xhigh effort · Claude Max
▘▘ ▝▝ ~/work/spherepacking-repos/Sphere-Packing-Lean
❯ let's work on these: example (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by sorry
example [DecidableEq α] (s t : Finset α) (h : s ⊆ t) : s.card ≤ t.card := by sorry
example (p q : Prop) : p ∧ q → q ∧ p := by sorry
03:08 PM claude-opus-4-8
● I'll start by invoking the Lean 4 skill since we're working on Lean proofs.
● Skill(lean4:lean4)
⎿ Successfully loaded skill
03:08 PM claude-opus-4-8
● These are three straightforward warm-up examples. Let me verify the proofs compile using the Lean REPL.
03:08 PM claude-opus-4-8
● Now let me verify all three proofs compile:
● lean-lsp - Run Code (MCP)(code: "import Mathlib\n\nexample (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by\n rw [List.length_append]\n\nexample [DecidableEq α] (s t :
Finset α) (h : s ⊆ t) : s.card ≤ t.card :=\n Finset.card_le_card h\n\nexample (p q : Prop) : p ∧ q → q ∧ p :=\n fun ⟨hp, hq⟩ => ⟨hq, hp⟩")
03:09 PM claude-opus-4-8
● I need to prime the project path first. Let me check a file in the project.
● Bash(find /home/freer/work/spherepacking-repos/Sphere-Packing-Lean -name "*.lean" -path "*ContourEndpoints*" 2>/dev/null | head -5)
⎿ /home/freer/work/spherepacking-repos/Sphere-Packing-Lean/SpherePacking/MagicFunction/a/ContourEndpoints.lean
● lean-lsp - Diagnostics (MCP)(file_path: "/home/freer/work/spherepacking-repos/Sphere-Packing-Lean/SpherePacking/MagicFunction/a/ContourEndpoints.lean")
⎿ {
"result": {
"success": false,
"timed_out": false,
"items": [],
"failed_dependencies": []
}
}
● lean-lsp - Run Code (MCP)(code: "import Mathlib\n\nexample (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by\n rw [List.length_append]\n\nexample [DecidableEq α] (s t :
Finset α) (h : s ⊆ t) : s.card ≤ t.card :=\n Finset.card_le_card h\n\nexample (p q : Prop) : p ∧ q → q ∧ p :=\n fun ⟨hp, hq⟩ => ⟨hq, hp⟩")
⎿ {
"success": false,
"timed_out": false,
"diagnostics": [
{
"severity": "error",
"message": "Unknown identifier `α`\n\nNote: It is not possible to treat `α` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.",
"line": 3,
"column": 23
},
{
"severity": "error",
"message": "Unknown identifier `α`\n\nNote: It is not possible to treat `α` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.",
"line": 3,
"column": 23
},
{
"severity": "error",
"message": "Unknown identifier `α`\n\nNote: It is not possible to treat `α` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.",
"line": 6,
"column": 22
},
{
"severity": "error",
"message": "Unknown identifier `α`\n\nNote: It is not possible to treat `α` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.",
"line": 6,
"column": 39
},
{
"severity": "error",
"message": "Unknown identifier `α`\n\nNote: It is not possible to treat `α` as an implicitly bound variable here because the `autoImplicit` option is set to `false`.",
"line": 6,
"column": 39
},
{
"severity": "warning",
"message": "'' starts on column 26, but all commands should start at the beginning of the line.\n\nNote: This linter can be disabled with `set_option linter.style.whitespace
false`",
"line": 10,
"column": 27
}
]
}
03:09 PM claude-opus-4-8
● The proofs are correct — the only errors are because autoImplicit is off in this project, so I need to explicitly bind α. Let me add the type variable:
● lean-lsp - Run Code (MCP)(code: "import Mathlib\n\nexample {α : Type*} (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by\n rw [List.length_append]\n\nexample {α : Type*}
[DecidableEq α] (s t : Finset α) (h : s ⊆ t) : s.card ≤ t.card :=\n Finset.card_le_card h\n\nexample (p q : Prop) : p ∧ q → q ∧ p :=\n fun ⟨hp, hq⟩ => ⟨hq, hp⟩")
⎿ {
"success": true,
"timed_out": false,
"diagnostics": [
{
"severity": "warning",
"message": "'' starts on column 26, but all commands should start at the beginning of the line.\n\nNote: This linter can be disabled with `set_option linter.style.whitespace
false`",
"line": 10,
"column": 27
}
]
}
03:10 PM claude-opus-4-8
● All three compile cleanly (the remaining message is just a whitespace-linter artifact from the snippet layout). Here are the proofs:
example {α : Type*} (xs ys : List α) : (xs ++ ys).length = xs.length + ys.length := by
rw [List.length_append]
example {α : Type*} [DecidableEq α] (s t : Finset α) (h : s ⊆ t) : s.card ≤ t.card :=
Finset.card_le_card h
example (p q : Prop) : p ∧ q → q ∧ p :=
fun ⟨hp, hq⟩ => ⟨hq, hp⟩
Notes:
1. List length — List.length_append is the statement, so rw closes it. Even shorter: := List.length_append (term mode) or by simp.
2. Finset card — Finset.card_le_card : s ⊆ t → s.card ≤ t.card applies directly. (DecidableEq isn't actually needed for this lemma, but it's harmless to keep.)
3. Conjunction swap — pure term mode: destructure the hypothesis and rebuild swapped. Equivalent alternatives: fun h => ⟨h.2, h.1⟩ or And.comm.mp.
One thing to flag: your originals wrote List α / Finset α without binding α. This project sets autoImplicit false, so I added {α : Type*} explicitly — without it the snippets don't compile
here. If you're dropping these into a section that already has a variable {α : Type*}, you can omit the per-example binder.
Want me to place these somewhere in the repo, or were they just warm-up exercises?
✻ Worked for 1m 30s

/learn: keeping human understanding in the loopWhen agents write more of the proof, humans can learn less, unless explanation is built into the workflow. /learn explores Mathlib around a topic, explains why a lemma applies, and grounds informal claims in checked definitions.
Pedagogy: Socratic mode, starting from the user’s prior knowledge to calibrate depth.
What does it mean for a function to be “measurable” between two measurable spaces?
If someone hands you f : α → β and both carry a MeasurableSpace instance,
what property must f satisfy?
/learn is not “explain after the fact”; it keeps the human inside the proof loop.
If I change the skill, how do I know the change helped? There is no single number.
Quality is layered, and a change can improve one layer while harming another:

Fix
Record
#print axioms deFinetti → propext, Quot.sound, Classical.choice)Review

Kernel correctness is the floor; quality and workflow gates sit above it. A failed attempt can still improve the workflow if it stops earlier with a useful obstruction summary.

So evaluation mixes:
regression tasks · build/sorry/axiom checks · trace review · qualitative failure analysis · project dogfooding · user reports · benchmark batches when available.

/lean4:golf: golfing is review, not line-count hackingScore candidates by: correctness → directness → clarity/inference burden → performance/determinism → length. …
**Hard reject if:** introduces naked `;` · introduces `<;>` on non-identical goals (per semicolon policy) … collapsed term > ~80 chars or dot-chain > 2 …
A shorter proof can be worse; golfing is a win only if it stays at least as direct, clear, and deterministic.

/lean4:review: evaluation as a commandRead-only review of Lean proofs for quality, style, and optimization opportunities.
Scope levels:
| Scope | Description |
|---|---|
sorry | Single sorry at --line (requires target file + --line) |
deps | Sorry + same-file helpers and directly referenced lemmas |
file | All sorries in target file |
changed | Files modified since last commit (git diff) |
project | Entire project (requires confirmation) |
next_action (stuck mode): continue (retryable), deep (needs escalation), repair (compiler blocker), redraft (statement-shape blocker), golf (sorry-free), stop (no path).
A review command makes quality checks repeatable. It reports the project’s state; it does not prove anything.

Add capability
Consolidate capability

lean4-skillsThe workflow is portable (measure theory, combinatorics, logic, computation), but each domain needs its own interfaces and review norms.

E8 root system. (J. Gregory Moxness)
A cube has Rupert’s property. (David Renshaw / Quanta)
The Noperthedron does not. (David Renshaw)

lean-lsp-mcp startup.Some of this is already live; some is active design work, tracked in the open issues and PRs on lean4-skills and lean-lsp-mcp.
Stronger models help; the leverage is in the protocols that let humans, agents, Lean, and Mathlib work together.