| name | hol-light |
| description | Prove theorems in HOL Light via MCP tools. Use when the user asks to prove a theorem, verify a proof, or work with HOL Light tactics, goals, or lemmas. |
| compatibility | Requires the hol-light MCP server running (uv run hol-light-mcp). |
HOL Light MCP — LLM Skill Guide
You have access to a HOL Light theorem prover via MCP tools. This document teaches you how to use them effectively.
HOL Light is a classical higher-order logic theorem prover. The law of excluded middle is available, and proof by contradiction is fine. Only use (* ... *) for OCaml comments.
Proof workflow
One-shot proofs: If you know the full tactic, use prove(goal, tactic) to get the theorem in a single call.
Interactive proofs:
- set_goal — state the theorem to prove
- goal_state — inspect current goals (check hypotheses and conclusion)
- search_theorems — find relevant lemmas by name substring
- apply_tactic — apply a tactic; check response for
"proved":true
- apply_tactics — apply multiple tactics in one round-trip (faster for straightforward sequences)
- backtrack — undo if a tactic made things worse
- Repeat 2–6 until proved
- hol_status — check if HOL Light is alive (useful for debugging)
- hol_restart — restart HOL Light if it has died or is in a bad state
Always read the goal state carefully before choosing a tactic. The structured JSON tells you exactly what hypotheses you have and what you need to show.
Core tactics
Logical structure
| Tactic | Use when |
|---|
GEN_TAC | Goal is !x. P(x) — strips one universal quantifier |
X_GEN_TAC \n:num`` | Like GEN_TAC but names the variable |
STRIP_TAC | Goal has ==>, /\, or ! at the top — strips one connective |
REPEAT STRIP_TAC | Strip all top-level connectives at once |
CONJ_TAC | Goal is P /\ Q — splits into two subgoals |
DISJ1_TAC / DISJ2_TAC | Goal is P \/ Q — choose which disjunct to prove |
EQ_TAC | Goal is P <=> Q — splits into P ==> Q and Q ==> P |
DISCH_TAC | Move antecedent of P ==> Q into hypotheses |
EXISTS_TAC \witness`` | Goal is ?x. P(x) — provide the witness |
Rewriting
| Tactic | Use when |
|---|
REWRITE_TAC[thm1; thm2] | Rewrite goal using equations (left-to-right) |
ASM_REWRITE_TAC[thms] | Rewrite using hypotheses AND given theorems |
ONCE_REWRITE_TAC[thm] | Rewrite one pass, all topmost matches (not just one match!) |
GEN_REWRITE_TAC I [thm] | Rewrite at top level only |
GEN_REWRITE_TAC (RAND_CONV) [thm] | Rewrite only the operand of the top-level application |
GEN_REWRITE_TAC (LAND_CONV o ONCE_DEPTH_CONV) [thm] | Rewrite inside the LHS of a binary operator |
SIMP_TAC[thms] | Conditional rewriting (handles side conditions) |
ASM_SIMP_TAC[thms] | Conditional rewriting with hypotheses |
Automation
| Tactic | Solves |
|---|
ARITH_TAC | Linear arithmetic over naturals and integers (goal only, ignores hypotheses) |
REAL_ARITH_TAC | Linear arithmetic over reals |
MESON_TAC[thms] | First-order logic with given lemmas |
ASM_MESON_TAC[thms] | First-order logic with hypotheses + lemmas |
TAUT (as a rule) | Propositional tautologies: TAUT \p /\ q ==> q`` |
SET_TAC[thms] | Set-theoretic goals |
RING_TAC | Ring equalities |
WORD_RULE | Word (bitvector) equalities |
CONV_TAC WORD_RULE | Word equalities as a tactic |
Induction
| Tactic | Use when |
|---|
INDUCT_TAC | Induction on the outermost !n. (natural number) |
LIST_INDUCT_TAC | Induction on a list |
MATCH_MP_TAC thm | Apply a theorem backwards (modus ponens in reverse) |
Hypothesis management
| Tactic | Effect |
|---|
ASM_REWRITE_TAC[] | Rewrite goal using all hypotheses |
FIRST_X_ASSUM MATCH_MP_TAC | Use first matching hypothesis backwards (consumes it) |
FIRST_ASSUM MATCH_MP_TAC | Same but keeps the hypothesis |
UNDISCH_TAC \term`` | Move a hypothesis back to the goal as antecedent |
SUBGOAL_THEN \P` ASSUME_TAC` | Assert and prove an intermediate fact |
SUBGOAL_THEN \P` MP_TAC` | Assert, prove, and add as antecedent of goal |
SUBGOAL_THEN \P` SUBST1_TAC` | Assert, prove, and substitute the equality in goal |
SUBGOAL_THEN \P` STRIP_ASSUME_TAC` | Assert, prove, and split conjunctions into separate assumptions |
ABBREV_TAC \x = expr`` | Replace expr with x in goal, add expr = x as assumption |
EXPAND_TAC "x" | Expand an abbreviation introduced by ABBREV_TAC |
Combinators
| Combinator | Meaning |
|---|
tac1 THEN tac2 | Apply tac1, then tac2 to all resulting subgoals |
tac1 THENL [tac2; tac3] | Apply tac1, then tac2 to first subgoal, tac3 to second |
REPEAT tac | Apply tac repeatedly until it fails |
TRY tac | Try tac, succeed even if it fails |
Searching for theorems
search_theorems searches by name substring. The search is cheap — use it freely. Tips:
- Search for the main constant:
search_theorems name="EVEN" for theorems about EVEN
- Search for the operation:
search_theorems name="ADD" for addition theorems
- Common prefixes:
ADD_, MULT_, LE_, LT_, EVEN_, ODD_, DIV_, MOD_
- For rewriting, look for theorems with
<=> or = in the statement
You can also use eval for more powerful searches:
eval 'search [`EVEN`]' -- search by subterm pattern
eval 'search [`EVEN`; `ODD`]' -- multiple patterns
Common proof patterns
Prove by simplification
apply_tactic "SIMP_TAC[thm1; thm2; thm3]"
Prove by rewriting then arithmetic
apply_tactic "REWRITE_TAC[DEF1; DEF2] THEN ARITH_TAC"
Prove by induction
apply_tactic "INDUCT_TAC" -- creates base case + inductive step
apply_tactic "base case tactic" -- solve base case
apply_tactic "ASM_REWRITE_TAC[...] THEN inductive step tactic"
Prove using a key lemma
search_theorems name="KEY_LEMMA"
apply_tactic "MATCH_MP_TAC KEY_LEMMA THEN ..."
Case split
apply_tactic "ASM_CASES_TAC `P` THEN ASM_REWRITE_TAC[]"
Declarative sub-lemma
apply_tactic "SUBGOAL_THEN `intermediate_fact` ASSUME_TAC"
-- prove the sub-lemma first, then use it
Pitfalls
REWRITE_TAC[GSYM thm] can loop. Use ONCE_REWRITE_TAC[GSYM thm] instead.
ONCE_REWRITE_TAC rewrites ALL topmost matches, not just one. If the goal has f a b and f c d, and the theorem matches f x y, both get rewritten in one pass. For targeted single-occurrence rewriting, use GEN_REWRITE_TAC with conversionals like RAND_CONV, LAND_CONV.
FIRST_X_ASSUM consumes the hypothesis. Use FIRST_ASSUM when you need to reuse it.
ARITH_TAC ignores hypotheses. Use UNDISCH_TAC \needed_fact`to bring assumptions into the goal first.ASM_ARITH_TACuses hypotheses but can hang with manyval(...)` terms.
ASM_ARITH_TAC hangs with many assumptions. Discard irrelevant ones first with REPEAT (FIRST_X_ASSUM (K ALL_TAC)) or targeted UNDISCH_TAC + DISCH_TAC.
SUBST1_TAC silently succeeds even when the LHS doesn't appear in the goal. It just does nothing. This makes FIRST_ASSUM(fun th -> SUBST1_TAC(SYM th)) unreliable — it picks the first assumption where the tactic "succeeds" (which is all of them). Use EXPAND_TAC "name" to expand abbreviations instead.
THEN vs THENL after SUBGOAL_THEN ... SUBST1_TAC. THEN applies the proof to ALL subgoals (both the equality proof and the main goal). Use THENL [equality_proof; ALL_TAC] to target only the first subgoal.
WORD_RULE hangs on val(word(...)). Normalize via VAL_WORD_EQ first.
- Natural number subtraction is truncating.
n - m = 0 when m >= n. Use ARITH_TAC for goals involving subtraction.
* is right-associative for num. Use explicit parentheses to avoid surprises.
- When a tactic hangs, backtrack and try a different approach. Automation tactics (
MESON_TAC, ARITH_TAC) can diverge on hard goals.
s2n-bignum ARM proof tactics
For proving properties of AArch64 machine code (requires arm/proofs/base.ml):
| Tactic | Use when |
|---|
ARM_MK_EXEC_RULE mc | Decode a machine code byte list into instruction theorems |
ENSURES_INIT_TAC "s0" | Initialize symbolic state for an ensures proof |
ARM_STEPS_TAC EXEC (1--n) | Symbolically execute n instructions |
ARM_SIM_TAC EXEC (1--n) | INIT + STEPS + FINAL in one shot |
ENSURES_FINAL_STATE_TAC | Close postcondition and frame |
ENSURES_WHILE_PAUP_TAC a b pc_body pc_back inv | Declare a counting-up loop with invariant |
COND_CASES_TAC | Case split on a conditional branch |
CONV_TAC WORD_RULE | Solve word (bitvector) equalities |
WORD_BLAST | Bit-blasting for word goals |
Typical ARM proof pattern:
REPEAT STRIP_TAC THEN ENSURES_INIT_TAC "s0" THEN
ARM_STEPS_TAC EXEC (1--n) THEN
ENSURES_FINAL_STATE_TAC THEN ASM_REWRITE_TAC[] THEN
CONV_TAC WORD_RULE
Utility tools
- hol_type — get the type of a HOL Light term (e.g.,
hol_type with term `1 + 1` returns num)
- hol_load — load a HOL Light file via
needs (e.g., hol_load with file "Library/words.ml")
- hol_interrupt — send SIGINT to cancel a hung tactic (e.g., when
MESON_TAC diverges)
- hol_help — return this tactic reference (SKILL.md). Call before your first proof.
- start_recording — record all tactic applications to a JSONL file for later replay
- stop_recording — stop recording and return the file path
General advice
- Try automation first.
ARITH_TAC, MESON_TAC[], or ASM_MESON_TAC[] often solve goals outright.
- Read the goal. The JSON tells you exactly what you have (hypotheses) and what you need (conclusion). Don't guess.
- Search before you rewrite. Use
search_theorems to find the right lemma name rather than guessing.
- Backtrack freely. If a tactic doesn't simplify the goal, undo it and try something else.
- Strip first.
REPEAT STRIP_TAC is almost always a good opening move.
ASM_REWRITE_TAC[] is your friend. It rewrites with all hypotheses. Use it after STRIP_TAC.
- Work incrementally. Apply one tactic at a time, inspect the goal state, then proceed.
- Use
SUBGOAL_THEN for declarative proofs. Clear intermediate goals make proofs easier to follow.
- Structure longer proofs into smaller lemmas. It's fine for a proof to have 100+ tactic steps, but breaking into lemmas is preferred.
- Avoid exotic tactics unless you know they're needed.