Skip to content

Merge changes from HOL Light#11

Merged
dnezam merged 16 commits into
masterfrom
sync
Apr 17, 2026
Merged

Merge changes from HOL Light#11
dnezam merged 16 commits into
masterfrom
sync

Conversation

@dnezam

@dnezam dnezam commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

sgmenda and others added 16 commits March 31, 2026 17:56
* mcp: add MCP server for HOL Light with structured proof tools

FastMCP stdio server for LLM-assisted theorem proving. Embeds HOL
Light as a subprocess with OCaml-side JSON serialization for
structured goal states.

Tools:
  - eval: raw OCaml/HOL Light evaluation
  - set_goal: set proof goal, return JSON goal state
  - goal_state: inspect current goals as JSON
  - apply_tactic: apply tactic, return new state or proved theorem
  - backtrack: undo N tactic steps
  - search_theorems: search by name, return JSON results
  - hol_type: get the type of a term
  - hol_load: load a HOL Light file via needs

Features:
  - DMTCP checkpoint support for instant startup (~2s vs ~75s)
  - 14-check MCP smoke test + 12 unit tests
  - OCaml helpers (mcp_helpers.ml) for JSON serialization
  - Auto-detects checkpoint, falls back to cold start

Docs:
  - README: setup, quick start, checkpointing, related work
  - TUTORIAL: 6 worked proof examples of increasing difficulty
  - SKILL: LLM tactic reference and proof patterns

* mcp: add hol_interrupt, named checkpoints, config file, ARM tutorial

New features:
  - hol_interrupt tool: sends SIGINT to cancel hung tactics
  - Named checkpoints: make_checkpoint.py --name <name> creates
    hol-<name>.ckpt/, allowing multiple checkpoints (e.g. base, s2n)
  - hol-mcp.toml config file: set checkpoint name and timeout,
    auto-discovered in CWD or passed via --config
  - Configurable timeout via config or HOL_TIMEOUT env (default 600s)

Docs:
  - Security considerations section in README
  - s2n-bignum ARM proof examples in TUTORIAL (simple, branch, loop)
  - ARM tactic reference in SKILL.md
  - kceren acknowledgment in README

Fixes:
  - Smoke test updated for hol_interrupt (14/14 passing)
  - .gitignore wildcards hol-*.ckpt

* mcp: use ephemeral coord port for dmtcp_restart

Pass --coord-port 0 so each server instance gets its own
coordinator port, allowing multiple instances to run in parallel
instead of failing with broken pipe when port 7779 is taken.

* mcp: add hol_restart tool for recovering from dead subprocess

Kills the existing HOL Light process, resets state, and starts a
fresh one. Useful when the subprocess dies mid-session instead of
requiring a full MCP server restart.

* mcp: add hol_status tool and per-call timeout

- hol_status: reports alive/dead, pid, checkpoint name, uptime, and
  default timeout — useful for diagnosing issues without eval
- Per-call timeout: eval and apply_tactic accept an optional timeout
  parameter (seconds) that overrides the global default, so slow
  tactics like ARM_STEPS_TAC can get more time without raising the
  global timeout for everything

* mcp: add tests for hol_status, hol_restart, and per-call timeout

* mcp: update smoke test, docs, and acknowledgments for new tools

- smoke_test.py: add checks for hol_status, hol_restart, per-call
  timeout, and post-restart eval; update expected tool list to 11
- README.md: add hol_restart and hol_status to tools table, update
  acknowledgments
- SKILL.md, TUTORIAL.md: add hol_status and hol_restart to workflow

* mcp: add hol_help tool, Agent Skills frontmatter, update docs

- Add hol_help tool that serves SKILL.md directly from the MCP server,
  eliminating the need for separate skill installation or agent config
- Set FastMCP instructions to hint the LLM to call hol_help()
- Add Agent Skills frontmatter (name/description/compatibility) to
  SKILL.md per the agentskills.io specification
- Update README: tools table, skill section, acknowledgments
- Update smoke test for new tool (12 tools total)
- Update SKILL.md and TUTORIAL.md workflow sections

* mcp: default hol-mcp.toml checkpoint to noledit

Matches the code's hardcoded fallback so the shipped config and
server agree out of the box.
This looks like a typo to me, but I am not 100% sure.
Compacts the OCaml heap before taking the DMTCP snapshot.
Reduces bare HOL Light checkpoint size from 511M to 306M (~40%).
…rh13#162)

* mcp: faster eval, fix locking, add prove/apply_tactics tools

Speed up MCP eval round-trips, fix threading issues, and add
convenience tools for one-shot and batch proofs.

Performance:
- Queue-based sentinel signaling: replace 50ms polling loop with
  queue.Queue. Reader thread produces results, _wait_for_sentinel
  consumes via queue.get(). Near-zero latency per eval call.
- Single round-trip for apply_tactic and set_goal (was 2 sequential
  _eval_raw calls each).
- Buffer-based OCaml JSON serialization in mcp_helpers.ml, replacing
  O(n^2) string concatenation with O(n).

Robustness:
- queue.Queue eliminates race condition between _eval_raw clearing
  state and reader thread setting it (atomic consumption).
- Dead process detection: reader thread puts error on queue at EOF,
  so callers get immediate feedback instead of hanging for 600s.
- hol_interrupt now holds _lock, preventing races with hol_restart.

New tools:
- prove(goal, tactic): one-shot proof via HOL Light's prove().
- apply_tactics(tactics): batch tactic application in a single
  round-trip via new OCaml helper mcp_json_apply_tactics. Stops at
  first error or proof completion, returns step count.

Docs:
- Update README tools table with prove and apply_tactics.
- Update SKILL.md and TUTORIAL.md workflow sections to document all
  14 tools including hol_type, hol_load, hol_interrupt, and hol_help.

Tests:
- 35 unit tests (tool-level coverage for all 14 tools).
- 25 smoke tests.

* mcp: document --config, expose config path in hol_status

- hol_status now includes "config" field showing the absolute path
  of the loaded hol-mcp.toml (or null if using defaults).
- Update README to state the default config path explicitly:
  /path/to/hol-light/mcp/hol-mcp.toml.
- Update hol-mcp.toml comments to match.
- Update Kiro CLI example to show --config with absolute path.

* typos
This is in effect performing multiplication in GF2[X], using the
bits of a machine word to represent polynomial coefficients in
a quite standard way (little-endian: LSB = constant term). It
has generic word sizes `word_pmul:M word->N word->P word`, so it
can be used as a target for modeling different variants supported
by CPUs or just used in other settings. There is an associated
evaluation conversion WORD_PMUL_CONV that is included in the
usual WORD_RED_CONV / WORD_REDUCE_CONV suite, as well as a
bitwise conversion BIT_WORD_PMUL_CONV. New definition:

        word_pmul

new theorems:

        BITVAL_BIT_WORD_PMUL
        BIT_WORD_PMUL
        BIT_WORD_PMUL_ALT
        WORD_CLZ_USHR
        WORD_CTZ_SHL
        WORD_PMUL_0
        WORD_PMUL_POW2
        WORD_PMUL_STEP
        WORD_PMUL_SYM
        WORD_PMUL_XOR
        WORD_PMUL_ZX
        WORD_SHL_AS_USHR
        WORD_USHR_AS_SHL

and conversions:

        WORD_PMUL_CONV
        BIT_WORD_PMUL_CONV
…ized

by Claude Opus 4.6 in "100/green.ml". The proof is based on the Cauchy
transform as a left inverse of the Wirtinger d-bar derivative
operator. This approach, avoiding any approximation or subdivision
arguments while allowing quite general path parametrization, was
inspired by Kostya_I's answer here:

  https://mathoverflow.net/questions/307713

The technique is rooted in the Cauchy transform / dbar framework of
Ahlfors, "Lectures on Quasiconformal Mappings" (1966, 2nd ed. 2006), and
appears in Bonk's UCLA complex analysis lecture notes, Ch. 20:

  https://www.math.ucla.edu/~mbonk/complana.pdf

The primary versions use complex infrastructure in the statements. More
traditional (but verbose) real-analytic variants of the Green theorem and
area formulas are derived as easy consequences, e.g.

  GREEN_THEOREM_CURL =
   |- f g u.
          g absolutely_continuous_on interval[vec 0,vec 1] /\
          pathfinish g = pathstart g /\
          (!z. z IN inside(path_image g) ==> winding_number(g,z) = Cx(&1)) /\
          open u /\
          inside(path_image g) UNION path_image g SUBSET u /\
          f differentiable_on u /\
          (!h:real^2. (\z. frechet_derivative f (at z) h) continuous_on u)
          ==> (\z. lift(frechet_derivative f (at z) (basis 1) $1 -
                        frechet_derivative f (at z) (basis 2) $2))
              integrable_on inside(path_image g) /\
              (\t. lift(f(g t)$2 * vector_derivative g (at t) $1 +
                        f(g t)$1 * vector_derivative g (at t) $2))
              integrable_on interval[vec 0,vec 1] /\
              integral (interval[vec 0,vec 1])
                (\t. lift(f(g t)$2 * vector_derivative g (at t) $1 +
                          f(g t)$1 * vector_derivative g (at t) $2)) =
              integral (inside(path_image g))
                (\z. lift(frechet_derivative f (at z) (basis 1) $1 -
                          frechet_derivative f (at z) (basis 2) $2))

Used the Green area formula from this development to replace around 1k
lines of ad-hoccery in "100/isoperimetric.ml" with a simple corollary.

As a side-effect, added a number of general lemmas to the Multivariate
libraries, notably some results on absolutely continuous paths including
the winding number integral HAS_PATH_INTEGRAL_WINDING_NUMBER_AC, a
complement to HAS_PATH_INTEGRAL_WINDING_NUMBER with non-comparable
hypotheses:

        ABSOLUTELY_CONTINUOUS_IMP_PATH
        ABSOLUTELY_CONTINUOUS_IMP_RECTIFIABLE_PATH
        ABSOLUTELY_CONTINUOUS_JOINPATHS
        ABSOLUTELY_CONTINUOUS_REVERSEPATH
        ABSOLUTELY_INTEGRABLE_VECTOR_DERIVATIVE_ABSOLUTELY_CONTINUOUS
        CEXP_LIPSCHITZ_BOUNDED
        HAS_PATH_INTEGRAL_WINDING_NUMBER_AC
        LEBESGUE_MEASURABLE_SING
        LSPACE_ALT
        LSPACE_SUBSET
        MEASURABLE_ON_CLOG
        MEASURABLE_ON_CPOW
        NEGLIGIBLE_REAL

Added a smaller new example autoformalized by Claude Opus 4.6, Conway's
"Doomsday Algorithm" for finding the weekday for a given (valid Gregorian)
date: "Examples/doomsday.ml". The overall correctness theorem is:

  DOOMSDAY_ALGORITHM_CORRECT =
    |- !y m d. valid_date(y,m,d)
               ==> doomsday_algorithm y m d = day_of_week y m d

Added a documentation file for the new "mapi" function introduced as
part of the METIS restructuring.
…timing (jrh13#163)

* mcp: structured JSON from eval/hol_load, output truncation, built-in timing

eval now returns structured JSON:
  {success, output, output_truncated, full_output_chars, time_seconds}

Large outputs are truncated to max_output_chars (default 4000, configurable
in hol-mcp.toml and per-call).

hol_load now returns quiet structured JSON:
  {success, file, time_seconds} (plus error on failure)
Intermediate val bindings are suppressed. Use eval with needs for verbose output.

Built-in timing on all eval/hol_load calls via _eval_raw returning
(output, elapsed_seconds) tuples.

Also: add hol_interrupt smoke test (all 14 tools now covered), bump all deps
(mcp 1.27, starlette 1.0, cryptography 46.0.6, etc.), fix README test counts.

Breaking change: eval and hol_load return types changed from raw strings
to JSON. All tests and smoke tests updated.

* mcp: fix smoke test hol_load to use Library/iter.ml

this was a typo, and now it matches the unit test.

* mcp: remove extra blank lines in test_server.py
…rk (jrh13#164)

- Fix ONCE_REWRITE_TAC description: 'one pass, all topmost matches' not 'only once'
- Add GEN_REWRITE_TAC examples with RAND_CONV and LAND_CONV conversionals
- Note that ARITH_TAC ignores hypotheses (goal only)
- Add SUBGOAL_THEN variants: MP_TAC, SUBST1_TAC, STRIP_ASSUME_TAC
- Add ABBREV_TAC / EXPAND_TAC pair
- Add pitfalls: SUBST1_TAC silent success, THEN vs THENL after SUBST1_TAC,
  ONCE_REWRITE_TAC all-matches semantics, ARITH_TAC ignoring hypotheses

Co-authored-by: Kiro (Claude Opus 4.6) <kiro@amazon.com>
* mcp: add proof recording with start_recording/stop_recording tools

Record e(TACTIC) and b() calls from eval, apply_tactic, and backtrack
to a JSONL file. Includes paren-counting tactic extraction from eval
and automatic backtrack tracking.

New tools: start_recording(path), stop_recording().

* mcp: add recording_dir config for auto-recording

If recording_dir is set in hol-mcp.toml or HOL_RECORDING_DIR env var,
recording starts automatically at server startup without needing the
LLM to call start_recording.

* mcp: add replay_init and replay_prefix config for proof resumption

On startup, optionally #use an ML init file then replay a JSONL tactic
prefix to restore proof state. Configured via replay_init/replay_prefix
in hol-mcp.toml or HOL_REPLAY_INIT/HOL_REPLAY_PREFIX env vars. Seeds
the recording with replayed steps so new tactics append to the prefix.
Backtracks cleanly on any failure.

* mcp: add recording tests and update SKILL.md

Add start_recording/stop_recording to smoke test tool list and exercise
the recording round-trip. Add unit tests for recording lifecycle,
backtrack removal, failed tactic skipping, parent dir creation, and
the _extract_e_tactic/_is_backtrack helpers. Add the two new tools to
the SKILL.md utility tools section.

---------

Co-authored-by: Ceren Kocaogullar <kceren@amazon.com>
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.5 to 46.0.7.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](pyca/cryptography@46.0.5...46.0.7)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This is a proof format translator for HOL Light, converting between
interactive (g/e) and structured (prove) proof styles. It is inspired
by Mark Adams' Tactician tool for HOL Light:

  http://www.proof-technologies.com/tactician/

which provided similar functionality via a "hiproof" representation
and refactoring pipeline. This is a from-scratch reimplementation by
Claude Code using string-based tactic recording rather than the
original promotion/demotion mechanism. Although this simpler version
requires some additional user work (e.g. saving a log of the tactic
invocations to a file for processing by "i2s"), it is considerably
simpler and less sensitive to OCaml internals.
…ishing (jrh13#169)

* mcp: rename default checkpoint from "noledit" to "base"

The old name was a leftover from when the checkpoint was created
without ledit. "base" is clearer and matches the README examples.

* mcp: add CI workflow for unit and smoke tests

Runs on PRs/pushes to master when mcp/ files change.
Builds HOL Light (OCaml 4.14), installs uv, then runs:
  - pytest test_server.py (unit tests)
  - smoke_test.py (MCP integration tests)

* mcp: drop unused [cli] extra from mcp dependency

We only use the stdio transport (FastMCP server + client). The [cli]
extra pulled in typer, rich, shellingham, etc. that we never use.
Removes 7 transitive dependencies, reducing dependabot surface.

* add dependabot config, limit pip alerts to direct deps

The mcp SDK pulls ~30 transitive dependencies (uvicorn, starlette,
cryptography, pydantic, etc.) that we don't control. Using allow-list
so dependabot only opens PRs for our direct deps (mcp, pytest) and
GitHub Actions versions.

* mcp: fix all audit issues

Docs:
- Fix stale test counts in README (38→48 unit, 34→37 smoke)
- Add start_recording/stop_recording to README tools table
- Add recording tools to TUTORIAL.md workflow summary

CI:
- Add timeout-minutes: 30 to mcp.yml workflow

Code:
- mcp_helpers.ml: replace fragile String.sub JSON slicing in
  mcp_json_apply_tactics with proper buffer-based construction
- server.py: make recording append-only with backtrack markers
  instead of rewriting the entire file on every tactic
- server.py: handle nested OCaml comments (* ... *) in
  _extract_e_tactic paren counting
- make_checkpoint.py: deduplicate opam env parsing by importing
  _opam_env from server.py (with inline fallback)

All 48 unit tests pass.

* mcp: fix 5 more audit issues

- start_recording: truncate existing file instead of appending
- _record_tactics_batch: capture total_goals for last tactic
  from the batch result instead of always recording 0
- smoke_test: use set-contains for tool list check so adding
  new tools doesn't break the existing assertion
- .gitignore: add __pycache__/ (was only in global gitignore)
- hol_restart: reset _recording_flushed so recording stays
  consistent after a restart

All 48 unit tests pass.

* fix missing trailing newline in .gitignore

* mcp: rewrite make_checkpoint.py for better UX

- Add argparse with --help, --name, -I flags
- Validate prerequisites before launching (ocaml-hol exists,
  dmtcp_launch on PATH, include dirs exist)
- Clear error messages instead of cryptic "EOF before loaded"
- Fix LD_LIBRARY_PATH not being set (caused silent dmtcp failures)
- Print checkpoint plan and size on completion
- s/Bare/Base/ in README checkpoint example

* mcp: fix recording duplication on replay and restart

_replay_prefix() was setting _recording_flushed=0 then flushing,
which re-appended all replayed entries to the file that already
contained them. Fix: set _recording_flushed=len(replayed) since
the entries are already on disk.

Similarly, hol_restart() was setting _recording_flushed=0 without
truncating the file, causing duplication on next flush. Fix: set
_recording_flushed=len(_recording) to mark existing entries as
already written.

* mcp: fix off-by-one in _record_tactics_batch error path

The OCaml side sets "step" to the count of successful tactics
(incremented after e(tac) succeeds). The Python code was computing
succeeded = step - 1, undercounting by 1. For example, if the first
tactic succeeded and the second failed (step=1), zero tactics were
recorded instead of one.
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v2...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 7.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@v4...v7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@dnezam dnezam merged commit 7da6742 into master Apr 17, 2026
1 check passed
@dnezam dnezam deleted the sync branch April 17, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants