lean-lockstep 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. lean_lockstep-0.1.0/CHANGELOG.md +22 -0
  2. lean_lockstep-0.1.0/LICENSE +22 -0
  3. lean_lockstep-0.1.0/MANIFEST.in +10 -0
  4. lean_lockstep-0.1.0/PKG-INFO +218 -0
  5. lean_lockstep-0.1.0/README.md +185 -0
  6. lean_lockstep-0.1.0/bin/lockstep +7 -0
  7. lean_lockstep-0.1.0/bin/lockstep-mcp +8 -0
  8. lean_lockstep-0.1.0/docs/automation.md +130 -0
  9. lean_lockstep-0.1.0/examples/minimal/.gitignore +2 -0
  10. lean_lockstep-0.1.0/examples/minimal/AGENTS.md +7 -0
  11. lean_lockstep-0.1.0/examples/minimal/Demo/Basic.lean +6 -0
  12. lean_lockstep-0.1.0/examples/minimal/Demo.lean +1 -0
  13. lean_lockstep-0.1.0/examples/minimal/DemoBlueprint.lean +2 -0
  14. lean_lockstep-0.1.0/examples/minimal/harness/allowlist.json +1 -0
  15. lean_lockstep-0.1.0/examples/minimal/harness/config.toml +48 -0
  16. lean_lockstep-0.1.0/examples/minimal/harness/destinations.txt +2 -0
  17. lean_lockstep-0.1.0/examples/minimal/harness/statements.lock +1 -0
  18. lean_lockstep-0.1.0/examples/minimal/lakefile.toml +13 -0
  19. lean_lockstep-0.1.0/examples/minimal/lean-toolchain +1 -0
  20. lean_lockstep-0.1.0/lean_lockstep.egg-info/PKG-INFO +218 -0
  21. lean_lockstep-0.1.0/lean_lockstep.egg-info/SOURCES.txt +118 -0
  22. lean_lockstep-0.1.0/lean_lockstep.egg-info/dependency_links.txt +1 -0
  23. lean_lockstep-0.1.0/lean_lockstep.egg-info/entry_points.txt +3 -0
  24. lean_lockstep-0.1.0/lean_lockstep.egg-info/requires.txt +9 -0
  25. lean_lockstep-0.1.0/lean_lockstep.egg-info/top_level.txt +1 -0
  26. lean_lockstep-0.1.0/lockstep/__init__.py +17 -0
  27. lean_lockstep-0.1.0/lockstep/__main__.py +3 -0
  28. lean_lockstep-0.1.0/lockstep/allowlist.py +40 -0
  29. lean_lockstep-0.1.0/lockstep/artifact_cache.py +515 -0
  30. lean_lockstep-0.1.0/lockstep/artifacts.py +185 -0
  31. lean_lockstep-0.1.0/lockstep/automation_schema.py +30 -0
  32. lean_lockstep-0.1.0/lockstep/build.py +468 -0
  33. lean_lockstep-0.1.0/lockstep/bundles.py +37 -0
  34. lean_lockstep-0.1.0/lockstep/cli.py +148 -0
  35. lean_lockstep-0.1.0/lockstep/commands/__init__.py +28 -0
  36. lean_lockstep-0.1.0/lockstep/commands/audit.py +46 -0
  37. lean_lockstep-0.1.0/lockstep/commands/automation.py +84 -0
  38. lean_lockstep-0.1.0/lockstep/commands/build.py +61 -0
  39. lean_lockstep-0.1.0/lockstep/commands/cache.py +58 -0
  40. lean_lockstep-0.1.0/lockstep/commands/check.py +81 -0
  41. lean_lockstep-0.1.0/lockstep/commands/claims.py +62 -0
  42. lean_lockstep-0.1.0/lockstep/commands/close.py +193 -0
  43. lean_lockstep-0.1.0/lockstep/commands/doctor.py +86 -0
  44. lean_lockstep-0.1.0/lockstep/commands/extract.py +169 -0
  45. lean_lockstep-0.1.0/lockstep/commands/falsify.py +108 -0
  46. lean_lockstep-0.1.0/lockstep/commands/index.py +16 -0
  47. lean_lockstep-0.1.0/lockstep/commands/jobs.py +188 -0
  48. lean_lockstep-0.1.0/lockstep/commands/layers.py +52 -0
  49. lean_lockstep-0.1.0/lockstep/commands/library.py +67 -0
  50. lean_lockstep-0.1.0/lockstep/commands/lock.py +58 -0
  51. lean_lockstep-0.1.0/lockstep/commands/log.py +132 -0
  52. lean_lockstep-0.1.0/lockstep/commands/node.py +429 -0
  53. lean_lockstep-0.1.0/lockstep/commands/note.py +76 -0
  54. lean_lockstep-0.1.0/lockstep/commands/probe.py +11 -0
  55. lean_lockstep-0.1.0/lockstep/commands/proof.py +31 -0
  56. lean_lockstep-0.1.0/lockstep/commands/ready.py +48 -0
  57. lean_lockstep-0.1.0/lockstep/commands/rules.py +61 -0
  58. lean_lockstep-0.1.0/lockstep/commands/search.py +37 -0
  59. lean_lockstep-0.1.0/lockstep/commands/status.py +172 -0
  60. lean_lockstep-0.1.0/lockstep/config.py +489 -0
  61. lean_lockstep-0.1.0/lockstep/context.py +33 -0
  62. lean_lockstep-0.1.0/lockstep/data/AuditCandidate.lean +18 -0
  63. lean_lockstep-0.1.0/lockstep/data/CacheArtifacts.lean +37 -0
  64. lean_lockstep-0.1.0/lockstep/data/Extract.lean.tmpl +362 -0
  65. lean_lockstep-0.1.0/lockstep/data/node.lean.tmpl +9 -0
  66. lean_lockstep-0.1.0/lockstep/delivery.py +45 -0
  67. lean_lockstep-0.1.0/lockstep/errors.py +28 -0
  68. lean_lockstep-0.1.0/lockstep/finalize.py +102 -0
  69. lean_lockstep-0.1.0/lockstep/gitutil.py +90 -0
  70. lean_lockstep-0.1.0/lockstep/hooks.py +143 -0
  71. lean_lockstep-0.1.0/lockstep/jobs.py +349 -0
  72. lean_lockstep-0.1.0/lockstep/layers.py +156 -0
  73. lean_lockstep-0.1.0/lockstep/lean.py +107 -0
  74. lean_lockstep-0.1.0/lockstep/library.py +188 -0
  75. lean_lockstep-0.1.0/lockstep/lock.py +68 -0
  76. lean_lockstep-0.1.0/lockstep/mcp_server.py +84 -0
  77. lean_lockstep-0.1.0/lockstep/metrics.py +12 -0
  78. lean_lockstep-0.1.0/lockstep/modules.py +197 -0
  79. lean_lockstep-0.1.0/lockstep/nodes.py +475 -0
  80. lean_lockstep-0.1.0/lockstep/operations.py +265 -0
  81. lean_lockstep-0.1.0/lockstep/output.py +45 -0
  82. lean_lockstep-0.1.0/lockstep/probes.py +72 -0
  83. lean_lockstep-0.1.0/lockstep/proofs.py +250 -0
  84. lean_lockstep-0.1.0/lockstep/resources.py +100 -0
  85. lean_lockstep-0.1.0/lockstep/shadow_index.py +47 -0
  86. lean_lockstep-0.1.0/lockstep/spec.py +36 -0
  87. lean_lockstep-0.1.0/lockstep/stage_runner.py +47 -0
  88. lean_lockstep-0.1.0/lockstep/storage.py +186 -0
  89. lean_lockstep-0.1.0/lockstep/store.py +338 -0
  90. lean_lockstep-0.1.0/lockstep/tags.py +84 -0
  91. lean_lockstep-0.1.0/pyproject.toml +56 -0
  92. lean_lockstep-0.1.0/setup.cfg +4 -0
  93. lean_lockstep-0.1.0/systemd/install.sh +21 -0
  94. lean_lockstep-0.1.0/systemd/lockstep-nightly.service +11 -0
  95. lean_lockstep-0.1.0/systemd/lockstep-nightly.timer +10 -0
  96. lean_lockstep-0.1.0/tests/conftest.py +103 -0
  97. lean_lockstep-0.1.0/tests/lean_automation_smoke.py +101 -0
  98. lean_lockstep-0.1.0/tests/test_artifact_boundaries.py +141 -0
  99. lean_lockstep-0.1.0/tests/test_artifact_cache.py +372 -0
  100. lean_lockstep-0.1.0/tests/test_audit_trail.py +292 -0
  101. lean_lockstep-0.1.0/tests/test_automation.py +95 -0
  102. lean_lockstep-0.1.0/tests/test_automation_boundaries.py +474 -0
  103. lean_lockstep-0.1.0/tests/test_cache_key_and_reservations.py +144 -0
  104. lean_lockstep-0.1.0/tests/test_cli.py +92 -0
  105. lean_lockstep-0.1.0/tests/test_close_and_note.py +50 -0
  106. lean_lockstep-0.1.0/tests/test_example_lifecycle.py +71 -0
  107. lean_lockstep-0.1.0/tests/test_hooks.py +85 -0
  108. lean_lockstep-0.1.0/tests/test_job_wait.py +84 -0
  109. lean_lockstep-0.1.0/tests/test_layers.py +84 -0
  110. lean_lockstep-0.1.0/tests/test_library.py +155 -0
  111. lean_lockstep-0.1.0/tests/test_node_add_names.py +92 -0
  112. lean_lockstep-0.1.0/tests/test_nodes.py +184 -0
  113. lean_lockstep-0.1.0/tests/test_rules.py +42 -0
  114. lean_lockstep-0.1.0/tests/test_shadow_index.py +28 -0
  115. lean_lockstep-0.1.0/tests/test_status_freshness.py +23 -0
  116. lean_lockstep-0.1.0/tests/test_storage.py +143 -0
  117. lean_lockstep-0.1.0/tests/test_store_and_jobs.py +66 -0
  118. lean_lockstep-0.1.0/tests/test_tags.py +49 -0
  119. lean_lockstep-0.1.0/tests/test_targeted_commands.py +141 -0
  120. lean_lockstep-0.1.0/tests/test_tool_resolution.py +32 -0
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes are listed here. Versions follow [semantic versioning](https://semver.org);
4
+ before 1.0, a minor version may break compatibility.
5
+
6
+ ## 0.1.0 (2026-10-18)
7
+
8
+ First public release. Extracted from the harness of two Lean research projects and generalized.
9
+
10
+ - State derived from the compiled environment: `lockstep extract` reports every blueprint node as
11
+ open, claimed or closed; `status`, `ready`, `node show`, `search`.
12
+ - Locked statements: `harness/statements.lock`, `node add`, `node restate`, `node retire`,
13
+ `lock refresh`, and `python -m lockstep.lock` for CI.
14
+ - Coordination: claims with intent and outcome, `log`, `journal`, `note`.
15
+ - Memory-guarded builds and jobs: `build`, `check`, `job run`, `job wait`, `worker`, under a shared
16
+ budget.
17
+ - Integrity: a cached per-module axiom audit, import-layer checks, `rules` (agent rule files in
18
+ step), and `hook pre-commit` for git.
19
+ - Client integration: an MCP server (`lockstep-mcp`) exposing every command; Claude Code hooks
20
+ (`hook session-start`, `hook pre-bash`).
21
+ - Experimental: durable operations, proof capture/check/prove, `node finish`, finite probes,
22
+ `context`, the research-library catalog, certificate layers and `storage share`.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gavin Litchfield
4
+ Copyright (c) 2026 Matt Pocock (Wayfinder-derived portions)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ # The sdist carries what the tests and the docs refer to.
2
+ include CHANGELOG.md
3
+ graft docs
4
+ graft examples
5
+ graft bin
6
+ graft systemd
7
+ graft tests
8
+ prune examples/*/.lake
9
+ prune examples/*/.lockstep
10
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: lean-lockstep
3
+ Version: 0.1.0
4
+ Summary: Coordinate several agents on a Lean 4 formalization: state derived from the compiled environment, locked statements, recorded claims and memory-guarded jobs
5
+ Author: Gavin Litchfield
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/GLitchfield/lean-lockstep
8
+ Project-URL: Repository, https://github.com/GLitchfield/lean-lockstep
9
+ Project-URL: Issues, https://github.com/GLitchfield/lean-lockstep/issues
10
+ Project-URL: Changelog, https://github.com/GLitchfield/lean-lockstep/blob/main/CHANGELOG.md
11
+ Keywords: lean4,formalization,theorem-proving,blueprint,ai-agents,mcp
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Classifier: Topic :: Software Development :: Build Tools
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: mcp
27
+ Requires-Dist: mcp<3,>=2; extra == "mcp"
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest; extra == "test"
30
+ Provides-Extra: research
31
+ Requires-Dist: numpy; extra == "research"
32
+ Dynamic: license-file
33
+
34
+ # lean-lockstep
35
+
36
+ A project harness for Lean 4 formalization work done by several agents at once: AI coding agents
37
+ of any kind (anything that can run a shell command or call an MCP tool) and people. It keeps the
38
+ state of the project (which statements are open, claimed, proved, and what depends on what)
39
+ derived from the compiled Lean environment, and it records who is doing what, why, and with how
40
+ much memory.
41
+
42
+ - **State comes from Lean, not from a document.** Each open statement is a *node*: a theorem
43
+ with a `sorry` in its own blueprint module, tagged in its docstring. `lockstep extract` reads the
44
+ compiled environment and reports each node as open, claimed or closed (sorry-free with
45
+ allowlisted axioms). There is no hand-kept status file that can drift.
46
+ - **Approved statements are locked.** `harness/statements.lock` records the type hash of every
47
+ approved statement. A proof that quietly weakens its statement shows up as a lock violation;
48
+ changing a statement takes `lockstep node restate`, which logs who approved it and why. This is the
49
+ failure mode that matters most when agents write the proofs.
50
+ - **Agents coordinate through the harness.** Claims expire and carry the intent (`--why`); every
51
+ release records an outcome; `lockstep log` and `lockstep journal` keep the trail; long commands run as
52
+ recorded, memory-capped jobs that every agent can see.
53
+ - **Audits scale.** The axiom audit is a per-module taint scan with a cache, which finishes on
54
+ projects where `#print axioms` does not.
55
+ - **No client or model is assumed.** Every command has a CLI and an MCP tool. A git pre-commit
56
+ hook guards commits for everyone; optional Claude Code hooks add a session banner and a guard
57
+ against compute launched outside the harness.
58
+
59
+ Status: experimental (0.1). Built and used in two research projects; expect rough edges outside
60
+ them. Linux first.
61
+
62
+ ## Requirements
63
+
64
+ - Python 3.11 or newer. The core has no dependencies; extras: `mcp` (MCP server), `test`
65
+ (pytest), `research` (NumPy for research-library probes).
66
+ - Lean 4 through [elan](https://github.com/leanprover/elan), Lake, and git.
67
+ - The memory guard runs each `lake` invocation in a capped `systemd-run --user` scope, so it
68
+ needs Linux with a systemd user session. Elsewhere set `[build] guard = false`.
69
+ - `bwrap` (bubblewrap) for the sandboxed proof checks of the experimental automation layer.
70
+
71
+ ## Try it
72
+
73
+ `examples/minimal` is a Lean-only Lake project (no Mathlib) with one core lemma and an empty
74
+ blueprint. From a checkout of this repository:
75
+
76
+ ```console
77
+ cd examples/minimal
78
+ export LOCKSTEP_AGENT=me:demo # every claim, job and log entry is recorded under this id
79
+ ../../bin/lockstep build core
80
+ ../../bin/lockstep extract # writes harness/nodes.json, index.json, status.md
81
+ ../../bin/lockstep node add --name double_succ --ws A --kind formalization \
82
+ --type "(n : Nat) : (n + 1) + (n + 1) = 2 * n + 2" \
83
+ --description "Doubling a successor." --depends double --dangling-ok "example"
84
+ ../../bin/lockstep ready # Demo.double_succ is open and ready
85
+ ../../bin/lockstep claim double_succ --why "rewrite with Demo.double, then omega"
86
+ # replace the `sorry` in DemoBlueprint/A/DoubleSucc.lean with `rw [double]; omega`
87
+ ../../bin/lockstep check double_succ # sorry=False status=closed
88
+ ../../bin/lockstep close double_succ # moves it into the core library, records the attempt
89
+ ../../bin/lockstep audit && ../../bin/lockstep journal
90
+ ```
91
+
92
+ `tests/test_example_lifecycle.py` runs the same sequence whenever `lake` is installed. To start
93
+ over, run `git checkout -- . && git clean -fdx .` in `examples/minimal`.
94
+
95
+ ## Installing
96
+
97
+ The harness writes Lean files into the project (the extractor, node modules) and its lock hashes
98
+ depend on the toolchain, so pin the version a project uses. Either:
99
+
100
+ - **From PyPI**, which puts `lockstep` and `lockstep-mcp` on the `PATH`:
101
+ ```console
102
+ uv tool install "lean-lockstep[mcp]==0.1.0" # or: pipx install "lean-lockstep[mcp]==0.1.0"
103
+ ```
104
+ Without PyPI access, install a tag from git instead:
105
+ `uv tool install "lean-lockstep[mcp] @ git+https://github.com/GLitchfield/lean-lockstep@v0.1.0"`.
106
+ - **As a git submodule**, pinned by commit and upgraded by bumping the pin, which suits a project
107
+ that also develops the harness:
108
+ ```console
109
+ git submodule add https://github.com/GLitchfield/lean-lockstep tools/lockstep
110
+ uv venv tools/lockstep/.venv && uv pip install -e "tools/lockstep[mcp]"
111
+ ```
112
+ `tools/lockstep/bin/lockstep` and `tools/lockstep/bin/lockstep-mcp` prefer that virtualenv and
113
+ fall back to the system `python3`.
114
+
115
+ The commands below write `lockstep`; with a submodule, that is `tools/lockstep/bin/lockstep`.
116
+
117
+ ## Adopting it in a project
118
+
119
+ 1. Give the Lake package two libraries: a core library (proved results only) and a blueprint
120
+ library whose root file imports every node module. `lockstep node add` maintains that root.
121
+ 2. Copy `examples/minimal/harness/` and edit `harness/config.toml`: namespaces, layers, build
122
+ targets, memory caps. Start `statements.lock` as `{}` and `allowlist.json` as `[]`.
123
+ 3. Ignore the harness state directory (`.lockstep/` with `[project] state_home = ".lockstep"`).
124
+ 4. Write the agent rules into the files your clients read (`AGENTS.md`, `CLAUDE.md`, ...) and
125
+ list them under `[rules]`; `lockstep rules` checks that they carry the same rule list (cheap enough
126
+ for CI). `examples/minimal/AGENTS.md` is a starting point. Tell each agent to export a stable
127
+ `LOCKSTEP_AGENT` (for example `<client>:<session id>`); claims and logs are recorded under it.
128
+ 5. Guard commits with the git hook, which works whoever commits:
129
+ ```console
130
+ printf '#!/bin/sh\nexec lockstep hook pre-commit\n' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
131
+ ```
132
+ It refuses a commit that fails `lockstep layers`, `lockstep extract --check` (generated files out of date
133
+ or a locked statement changed) or `lockstep audit --changed`.
134
+ 6. Register the MCP server with your client: command `lockstep-mcp` (or `tools/lockstep/bin/lockstep-mcp`), stdio,
135
+ no arguments. For example `.mcp.json`:
136
+ ```json
137
+ { "mcpServers": { "lockstep": { "command": "tools/lockstep/bin/lockstep-mcp", "args": [] } } }
138
+ ```
139
+ 7. Optionally, for Claude Code, add the session and shell hooks to `.claude/settings.json`:
140
+ ```json
141
+ { "hooks": {
142
+ "SessionStart": [{ "hooks": [{ "type": "command", "command": "tools/lockstep/bin/lockstep hook session-start" }] }],
143
+ "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "tools/lockstep/bin/lockstep hook pre-bash" }] }] } }
144
+ ```
145
+ `session-start` prints the session's agent id and `lockstep status --brief`. `pre-bash` blocks a
146
+ bare `nohup`/`systemd-run` (compute goes through `lockstep job run`) and runs the commit guard
147
+ before `git commit`/`git push`. Any client whose hooks pass the same JSON on stdin can use them.
148
+ 8. In CI, `python3 -m lockstep.lock BASE_LOCK HEAD_LOCK` (with `PYTHONPATH=tools/lockstep` for a submodule)
149
+ prints the log files a lock change must touch: `restatements.md` when an approved statement
150
+ changed or disappeared, `retired.md` for a refutation. New entries need none.
151
+ 9. Optionally, `tools/lockstep/systemd/install.sh` (from a checkout) installs a `lockstep-nightly-<project
152
+ id>` user timer (03:00) that runs the guarded core build, `lockstep extract --check` and `lockstep audit`.
153
+
154
+ ## Commands
155
+
156
+ | Area | Commands |
157
+ |---|---|
158
+ | State | `status`, `ready`, `node show`, `search`, `extract`, `extract --check`, `doctor` |
159
+ | Statements | `node add`, `node restate`, `node retire`, `lock refresh` (after a toolchain bump) |
160
+ | Work | `claim`, `release`, `check`, `close`, `falsify` (conjecture nodes) |
161
+ | Builds and jobs | `build core` / `blueprint` / `node`, `job run`, `job wait`, `job list`, `worker` |
162
+ | Trail | `log`, `journal`, `note` (weekly summary) |
163
+ | Integrity | `audit`, `layers`, `rules`, `hook pre-commit` |
164
+ | Client hooks | `hook session-start`, `hook pre-bash` |
165
+
166
+ Every command takes `--json`, and all but the hooks are also MCP tools. Node names may be
167
+ dotted relative to the declaration namespace (`--name List.foo` declares `theorem List.foo` in
168
+ it), and `--depends` lists the project declarations a node waits on (library lemmas belong in
169
+ the description).
170
+
171
+ ### Experimental
172
+
173
+ These work in the projects they came from but are less general; see
174
+ [`docs/automation.md`](docs/automation.md):
175
+
176
+ - **Automation layer**: durable `operation …` workflows run by `lockstep worker`, a results `inbox`,
177
+ `proof capture|check|prove`, `node finish`, `probe run|replay`, `context`.
178
+ - **Research library**: `lib search|show|list` over a project catalog
179
+ (`harness/research/catalog.json`).
180
+ - **Certificate layers**: `[layers]` rules for generated proof-module families, and
181
+ `storage share` for their compiled artifacts.
182
+
183
+ ## Things that cost hours to learn
184
+
185
+ - **Never `import` the project modules at the top of a `lake env lean --run` script that also
186
+ calls `importModules` on them.** The script's own import maps the oleans; the second load
187
+ cannot mmap at the same addresses and reads them into anonymous memory instead (37 GB and a
188
+ cgroup OOM kill on a project with large certificate modules). The extractor template imports
189
+ only `Lean`; `main` imports the project.
190
+ - **Never use `importAll := true`** for the extractor environment (30+ GB). A plain import
191
+ exposes every theorem value in `env.header.moduleData`.
192
+ - **Never run bare `lake build` on big targets.** Lake 5 has no `-j`; two dozen parallel
193
+ compilers exhaust a 20 GB guard. `lockstep build` builds Lake's out-of-date list one module per
194
+ guarded invocation first.
195
+ - **`#print axioms` does not finish on large generated proof families** (16 TB read in 3.5 h);
196
+ the extractor's module-order taint scan with a per-module cache is the audit path.
197
+ - **A module-system core root (`module` header) cannot import plain files.** When the core root
198
+ uses it, nodes are written in module form from the start, so a proof elaborates against the
199
+ same imports before and after `lockstep close`.
200
+ - **The elaborator moves parts of a proof into auxiliary declarations** (`foo._proof_1`), so a
201
+ dependency scan of a theorem's value alone reports the auxiliaries instead of the lemmas they
202
+ use. The extractor looks through a declaration's own auxiliaries.
203
+
204
+ ## Development
205
+
206
+ ```console
207
+ uv venv .venv && uv pip install -e ".[test,mcp,research]"
208
+ .venv/bin/python -m pytest
209
+ ```
210
+
211
+ Releases follow semantic versioning (0.x: minor versions may break). Changes are listed in
212
+ [`CHANGELOG.md`](CHANGELOG.md). To release, bump `__version__` in `lockstep/__init__.py`, add the
213
+ changelog entry, and push a `vX.Y.Z` tag: `.github/workflows/release.yml` builds the sdist and
214
+ wheel and publishes them to PyPI through trusted publishing.
215
+
216
+ ## License
217
+
218
+ MIT; see [`LICENSE`](LICENSE).
@@ -0,0 +1,185 @@
1
+ # lean-lockstep
2
+
3
+ A project harness for Lean 4 formalization work done by several agents at once: AI coding agents
4
+ of any kind (anything that can run a shell command or call an MCP tool) and people. It keeps the
5
+ state of the project (which statements are open, claimed, proved, and what depends on what)
6
+ derived from the compiled Lean environment, and it records who is doing what, why, and with how
7
+ much memory.
8
+
9
+ - **State comes from Lean, not from a document.** Each open statement is a *node*: a theorem
10
+ with a `sorry` in its own blueprint module, tagged in its docstring. `lockstep extract` reads the
11
+ compiled environment and reports each node as open, claimed or closed (sorry-free with
12
+ allowlisted axioms). There is no hand-kept status file that can drift.
13
+ - **Approved statements are locked.** `harness/statements.lock` records the type hash of every
14
+ approved statement. A proof that quietly weakens its statement shows up as a lock violation;
15
+ changing a statement takes `lockstep node restate`, which logs who approved it and why. This is the
16
+ failure mode that matters most when agents write the proofs.
17
+ - **Agents coordinate through the harness.** Claims expire and carry the intent (`--why`); every
18
+ release records an outcome; `lockstep log` and `lockstep journal` keep the trail; long commands run as
19
+ recorded, memory-capped jobs that every agent can see.
20
+ - **Audits scale.** The axiom audit is a per-module taint scan with a cache, which finishes on
21
+ projects where `#print axioms` does not.
22
+ - **No client or model is assumed.** Every command has a CLI and an MCP tool. A git pre-commit
23
+ hook guards commits for everyone; optional Claude Code hooks add a session banner and a guard
24
+ against compute launched outside the harness.
25
+
26
+ Status: experimental (0.1). Built and used in two research projects; expect rough edges outside
27
+ them. Linux first.
28
+
29
+ ## Requirements
30
+
31
+ - Python 3.11 or newer. The core has no dependencies; extras: `mcp` (MCP server), `test`
32
+ (pytest), `research` (NumPy for research-library probes).
33
+ - Lean 4 through [elan](https://github.com/leanprover/elan), Lake, and git.
34
+ - The memory guard runs each `lake` invocation in a capped `systemd-run --user` scope, so it
35
+ needs Linux with a systemd user session. Elsewhere set `[build] guard = false`.
36
+ - `bwrap` (bubblewrap) for the sandboxed proof checks of the experimental automation layer.
37
+
38
+ ## Try it
39
+
40
+ `examples/minimal` is a Lean-only Lake project (no Mathlib) with one core lemma and an empty
41
+ blueprint. From a checkout of this repository:
42
+
43
+ ```console
44
+ cd examples/minimal
45
+ export LOCKSTEP_AGENT=me:demo # every claim, job and log entry is recorded under this id
46
+ ../../bin/lockstep build core
47
+ ../../bin/lockstep extract # writes harness/nodes.json, index.json, status.md
48
+ ../../bin/lockstep node add --name double_succ --ws A --kind formalization \
49
+ --type "(n : Nat) : (n + 1) + (n + 1) = 2 * n + 2" \
50
+ --description "Doubling a successor." --depends double --dangling-ok "example"
51
+ ../../bin/lockstep ready # Demo.double_succ is open and ready
52
+ ../../bin/lockstep claim double_succ --why "rewrite with Demo.double, then omega"
53
+ # replace the `sorry` in DemoBlueprint/A/DoubleSucc.lean with `rw [double]; omega`
54
+ ../../bin/lockstep check double_succ # sorry=False status=closed
55
+ ../../bin/lockstep close double_succ # moves it into the core library, records the attempt
56
+ ../../bin/lockstep audit && ../../bin/lockstep journal
57
+ ```
58
+
59
+ `tests/test_example_lifecycle.py` runs the same sequence whenever `lake` is installed. To start
60
+ over, run `git checkout -- . && git clean -fdx .` in `examples/minimal`.
61
+
62
+ ## Installing
63
+
64
+ The harness writes Lean files into the project (the extractor, node modules) and its lock hashes
65
+ depend on the toolchain, so pin the version a project uses. Either:
66
+
67
+ - **From PyPI**, which puts `lockstep` and `lockstep-mcp` on the `PATH`:
68
+ ```console
69
+ uv tool install "lean-lockstep[mcp]==0.1.0" # or: pipx install "lean-lockstep[mcp]==0.1.0"
70
+ ```
71
+ Without PyPI access, install a tag from git instead:
72
+ `uv tool install "lean-lockstep[mcp] @ git+https://github.com/GLitchfield/lean-lockstep@v0.1.0"`.
73
+ - **As a git submodule**, pinned by commit and upgraded by bumping the pin, which suits a project
74
+ that also develops the harness:
75
+ ```console
76
+ git submodule add https://github.com/GLitchfield/lean-lockstep tools/lockstep
77
+ uv venv tools/lockstep/.venv && uv pip install -e "tools/lockstep[mcp]"
78
+ ```
79
+ `tools/lockstep/bin/lockstep` and `tools/lockstep/bin/lockstep-mcp` prefer that virtualenv and
80
+ fall back to the system `python3`.
81
+
82
+ The commands below write `lockstep`; with a submodule, that is `tools/lockstep/bin/lockstep`.
83
+
84
+ ## Adopting it in a project
85
+
86
+ 1. Give the Lake package two libraries: a core library (proved results only) and a blueprint
87
+ library whose root file imports every node module. `lockstep node add` maintains that root.
88
+ 2. Copy `examples/minimal/harness/` and edit `harness/config.toml`: namespaces, layers, build
89
+ targets, memory caps. Start `statements.lock` as `{}` and `allowlist.json` as `[]`.
90
+ 3. Ignore the harness state directory (`.lockstep/` with `[project] state_home = ".lockstep"`).
91
+ 4. Write the agent rules into the files your clients read (`AGENTS.md`, `CLAUDE.md`, ...) and
92
+ list them under `[rules]`; `lockstep rules` checks that they carry the same rule list (cheap enough
93
+ for CI). `examples/minimal/AGENTS.md` is a starting point. Tell each agent to export a stable
94
+ `LOCKSTEP_AGENT` (for example `<client>:<session id>`); claims and logs are recorded under it.
95
+ 5. Guard commits with the git hook, which works whoever commits:
96
+ ```console
97
+ printf '#!/bin/sh\nexec lockstep hook pre-commit\n' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
98
+ ```
99
+ It refuses a commit that fails `lockstep layers`, `lockstep extract --check` (generated files out of date
100
+ or a locked statement changed) or `lockstep audit --changed`.
101
+ 6. Register the MCP server with your client: command `lockstep-mcp` (or `tools/lockstep/bin/lockstep-mcp`), stdio,
102
+ no arguments. For example `.mcp.json`:
103
+ ```json
104
+ { "mcpServers": { "lockstep": { "command": "tools/lockstep/bin/lockstep-mcp", "args": [] } } }
105
+ ```
106
+ 7. Optionally, for Claude Code, add the session and shell hooks to `.claude/settings.json`:
107
+ ```json
108
+ { "hooks": {
109
+ "SessionStart": [{ "hooks": [{ "type": "command", "command": "tools/lockstep/bin/lockstep hook session-start" }] }],
110
+ "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "tools/lockstep/bin/lockstep hook pre-bash" }] }] } }
111
+ ```
112
+ `session-start` prints the session's agent id and `lockstep status --brief`. `pre-bash` blocks a
113
+ bare `nohup`/`systemd-run` (compute goes through `lockstep job run`) and runs the commit guard
114
+ before `git commit`/`git push`. Any client whose hooks pass the same JSON on stdin can use them.
115
+ 8. In CI, `python3 -m lockstep.lock BASE_LOCK HEAD_LOCK` (with `PYTHONPATH=tools/lockstep` for a submodule)
116
+ prints the log files a lock change must touch: `restatements.md` when an approved statement
117
+ changed or disappeared, `retired.md` for a refutation. New entries need none.
118
+ 9. Optionally, `tools/lockstep/systemd/install.sh` (from a checkout) installs a `lockstep-nightly-<project
119
+ id>` user timer (03:00) that runs the guarded core build, `lockstep extract --check` and `lockstep audit`.
120
+
121
+ ## Commands
122
+
123
+ | Area | Commands |
124
+ |---|---|
125
+ | State | `status`, `ready`, `node show`, `search`, `extract`, `extract --check`, `doctor` |
126
+ | Statements | `node add`, `node restate`, `node retire`, `lock refresh` (after a toolchain bump) |
127
+ | Work | `claim`, `release`, `check`, `close`, `falsify` (conjecture nodes) |
128
+ | Builds and jobs | `build core` / `blueprint` / `node`, `job run`, `job wait`, `job list`, `worker` |
129
+ | Trail | `log`, `journal`, `note` (weekly summary) |
130
+ | Integrity | `audit`, `layers`, `rules`, `hook pre-commit` |
131
+ | Client hooks | `hook session-start`, `hook pre-bash` |
132
+
133
+ Every command takes `--json`, and all but the hooks are also MCP tools. Node names may be
134
+ dotted relative to the declaration namespace (`--name List.foo` declares `theorem List.foo` in
135
+ it), and `--depends` lists the project declarations a node waits on (library lemmas belong in
136
+ the description).
137
+
138
+ ### Experimental
139
+
140
+ These work in the projects they came from but are less general; see
141
+ [`docs/automation.md`](docs/automation.md):
142
+
143
+ - **Automation layer**: durable `operation …` workflows run by `lockstep worker`, a results `inbox`,
144
+ `proof capture|check|prove`, `node finish`, `probe run|replay`, `context`.
145
+ - **Research library**: `lib search|show|list` over a project catalog
146
+ (`harness/research/catalog.json`).
147
+ - **Certificate layers**: `[layers]` rules for generated proof-module families, and
148
+ `storage share` for their compiled artifacts.
149
+
150
+ ## Things that cost hours to learn
151
+
152
+ - **Never `import` the project modules at the top of a `lake env lean --run` script that also
153
+ calls `importModules` on them.** The script's own import maps the oleans; the second load
154
+ cannot mmap at the same addresses and reads them into anonymous memory instead (37 GB and a
155
+ cgroup OOM kill on a project with large certificate modules). The extractor template imports
156
+ only `Lean`; `main` imports the project.
157
+ - **Never use `importAll := true`** for the extractor environment (30+ GB). A plain import
158
+ exposes every theorem value in `env.header.moduleData`.
159
+ - **Never run bare `lake build` on big targets.** Lake 5 has no `-j`; two dozen parallel
160
+ compilers exhaust a 20 GB guard. `lockstep build` builds Lake's out-of-date list one module per
161
+ guarded invocation first.
162
+ - **`#print axioms` does not finish on large generated proof families** (16 TB read in 3.5 h);
163
+ the extractor's module-order taint scan with a per-module cache is the audit path.
164
+ - **A module-system core root (`module` header) cannot import plain files.** When the core root
165
+ uses it, nodes are written in module form from the start, so a proof elaborates against the
166
+ same imports before and after `lockstep close`.
167
+ - **The elaborator moves parts of a proof into auxiliary declarations** (`foo._proof_1`), so a
168
+ dependency scan of a theorem's value alone reports the auxiliaries instead of the lemmas they
169
+ use. The extractor looks through a declaration's own auxiliaries.
170
+
171
+ ## Development
172
+
173
+ ```console
174
+ uv venv .venv && uv pip install -e ".[test,mcp,research]"
175
+ .venv/bin/python -m pytest
176
+ ```
177
+
178
+ Releases follow semantic versioning (0.x: minor versions may break). Changes are listed in
179
+ [`CHANGELOG.md`](CHANGELOG.md). To release, bump `__version__` in `lockstep/__init__.py`, add the
180
+ changelog entry, and push a `vX.Y.Z` tag: `.github/workflows/release.yml` builds the sdist and
181
+ wheel and publishes them to PyPI through trusted publishing.
182
+
183
+ ## License
184
+
185
+ MIT; see [`LICENSE`](LICENSE).
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Launcher: prefer the tools/lockstep virtualenv, fall back to the system python3 (core has no dependencies).
3
+ here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ if [ -x "$here/../.venv/bin/python" ]; then
5
+ exec "$here/../.venv/bin/python" -m lockstep "$@"
6
+ fi
7
+ PYTHONPATH="$here/..${PYTHONPATH:+:$PYTHONPATH}" exec python3 -m lockstep "$@"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ # MCP server launcher (stdio). Needs the `mcp` package: uv pip install -e "tools/lockstep[mcp]".
3
+ here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ export LOCKSTEP_PROJECT_ROOT="${LOCKSTEP_PROJECT_ROOT:-$(cd "$here/../../.." && pwd)}"
5
+ if [ -x "$here/../.venv/bin/python" ]; then
6
+ exec "$here/../.venv/bin/python" -m lockstep.mcp_server "$@"
7
+ fi
8
+ PYTHONPATH="$here/..${PYTHONPATH:+:$PYTHONPATH}" exec python3 -m lockstep.mcp_server "$@"
@@ -0,0 +1,130 @@
1
+ # Automation layer (experimental)
2
+
3
+ The automation layer runs multi-step work (proof checks, tactic attempts, finite searches, the
4
+ finish of a node) as durable *operations* that `lockstep worker` executes through the same guarded,
5
+ budgeted jobs as `lockstep job run`. It calls no model: whichever agent submits an operation reads
6
+ the result later, so it works the same for every client. Nothing in this layer establishes a
7
+ theorem: only the compiled Lean environment, `lockstep extract` and the audit do.
8
+
9
+ Every command here is also an MCP tool. Results are compact by default; large fields are stored
10
+ as immutable SHA-256 artifacts and referenced by id (`lockstep artifact show ID --offset N`, or repeat
11
+ the command with `--full`).
12
+
13
+ ## Operations
14
+
15
+ ```console
16
+ lockstep operation submit --spec work.json [--key IDEMPOTENCY_KEY]
17
+ lockstep operation show OP_ID # stage, stages_done, stages_total, receipt references
18
+ lockstep operation list
19
+ lockstep operation resume|retry|cancel OP_ID
20
+ lockstep operation reconcile # detect dead runners without replaying uncertain effects
21
+ ```
22
+
23
+ A spec is a finite list of uniquely named stages, each with a memory cap and a time limit
24
+ (defaults: `[jobs] default_memory_max`, 10 minutes):
25
+
26
+ ```json
27
+ {"schema_version": 1, "stages": [
28
+ {"name": "sweep", "action": "command", "argv": ["python3", "scripts/sweep.py"], "memory": "2G", "minutes": 30}
29
+ ]}
30
+ ```
31
+
32
+ - Stages run in order; a stage that succeeded is never rerun by `resume` or `retry`.
33
+ - Submission is idempotent per agent: the same spec (or the same `--key`) returns the existing
34
+ operation.
35
+ - Admission is atomic against the shared memory budget (`[jobs]`), so concurrent workers never
36
+ overcommit. A request that can never fit is reported as `blocked_resource`.
37
+ - Cancellation is a request; the operation counts as cancelled once the owning worker confirms
38
+ the process is gone. A runner that died mid-stage becomes `unknown_execution` and is never
39
+ replayed automatically.
40
+ - `lockstep resources reconcile` releases a memory reservation only after confirming its cgroup stopped.
41
+
42
+ Restart `lockstep worker` after upgrading the harness, so that it runs the current code.
43
+
44
+ ## Results inbox
45
+
46
+ A finished operation leaves an event in its submitter's inbox (keyed by `LOCKSTEP_AGENT`):
47
+
48
+ ```console
49
+ lockstep inbox list
50
+ lockstep inbox claim --consumer ID # lease one event (at-least-once delivery)
51
+ lockstep inbox ack EVENT_ID --consumer ID
52
+ lockstep inbox pause|resume # stop or restart delivery; compute continues
53
+ ```
54
+
55
+ The inbox wakes nobody. A client that wants push delivery polls `inbox claim`, or keeps its turn
56
+ open with `lockstep job wait`.
57
+
58
+ ## Proofs
59
+
60
+ ```console
61
+ lockstep proof capture NODE [--start BYTE --end BYTE] # freeze the proof span and its source/dependency identities
62
+ lockstep proof check FIXTURE_ID --body body.lean [--memory 4G --minutes 10]
63
+ lockstep proof prove FIXTURE_ID [--tactics omega simp ...]
64
+ lockstep node finish NODE --body body.lean [--extract-memory 16G]
65
+ ```
66
+
67
+ - `proof capture` records the exact UTF-8 byte span of a node's proof plus hashes of the source
68
+ file, its compiled dependencies and the toolchain. Any later change to them requires a recapture.
69
+ - `proof check` compiles the candidate in a network-less `bwrap` sandbox that sees only the
70
+ compiled libraries and the toolchain, then audits the whole parent declaration in a separate
71
+ Lean process. Without `bwrap` it refuses rather than falling back. Failed checks return bounded
72
+ error headers with byte offsets into the full diagnostic artifact.
73
+ - `proof prove` tries a fixed portfolio of tactics (`rfl`, `simp`, `simp_all`, `omega`,
74
+ `norm_num`, `ring`, `linarith`, `nlinarith`, `positivity`, `grind`, `aesop`) and stops at the
75
+ first one the kernel accepts.
76
+ - `node finish` checks the claim, branch and statement lock, checks the candidate, substitutes
77
+ only its proof span, rebuilds the original module, runs a full extraction and the audits, and
78
+ writes a reviewable diff. It never commits or publishes (`--policy review` is the only policy).
79
+
80
+ ## Finite probes
81
+
82
+ A probe is a finite computation (a search for a counterexample, a bounded enumeration) run by a
83
+ project plugin registered in `harness/config.toml`:
84
+
85
+ ```toml
86
+ [research.plugins.my_search]
87
+ module = "harness.research.my_search" # a module in the project
88
+ sources = ["harness/research/my_search.py"] # hashed at admission; a change refuses the run
89
+ semantics = ["my-search-v1"] # the spec must name one of these
90
+ ```
91
+
92
+ The module provides `evaluate(spec) -> dict` (with an optional `witness`) and
93
+ `replay(witness) -> {"replayed": bool, ...}`. A spec names the plugin, its semantics and a
94
+ positive case and time budget:
95
+
96
+ ```console
97
+ lockstep probe run --spec probe.json
98
+ lockstep probe replay RESULT_ARTIFACT_ID # recompute a saved witness independently
99
+ ```
100
+
101
+ A witness is replayed before the result is stored. A probe result never closes or refutes a
102
+ node: a counterexample still needs a Lean refutation, and a bounded negative proves nothing.
103
+
104
+ ## Context and the research library
105
+
106
+ - `lockstep context NODE [--previous BUNDLE_ID]` returns a content-addressed bundle with the node, its
107
+ source hash, the signatures of the declarations it uses, recent events and operations, and
108
+ whether the index is fresh. With `--previous` it returns only what changed.
109
+ - `lockstep lib search "TASK"`, `lockstep lib show ID` and `lockstep lib list` search the project's curated catalog
110
+ of reusable code, `harness/research/catalog.json`: `schema_version: 1` and a list of entries
111
+ with `id`, `kind` (`primitive` or `workflow`), `title`, `summary`, `api`, `example`,
112
+ `semantics`, `keywords` and an optional `source`. The symbols a source lists are checked by
113
+ parsing it, never by importing it. A miss is a reason to search the source, not evidence that
114
+ nothing exists.
115
+
116
+ ## Certificate layers and the artifact cache
117
+
118
+ For projects with large generated proof modules (SAT certificates, for example):
119
+
120
+ - A `[layers]` entry can mark a layer as generated, pin its member list, and freeze the
121
+ hand-written import closure it depends on; `lockstep layers` enforces this. `lockstep layers
122
+ --allow-rebuild` accepts changes to those inputs, and the Claude Code hook passes it for a
123
+ commit whose message carries `[certificate-rebuild]`.
124
+ - `[build.artifact_cache]` and `lockstep storage share` verify byte-identical compiled artifacts and
125
+ hard-link them through Lake's immutable cache, which saves disk across worktrees.
126
+
127
+ ## Index shadowing
128
+
129
+ `lockstep index shadow` merges an incremental update of the index and compares it with a supplied full
130
+ extraction. It checks incremental extraction and never publishes an index.
@@ -0,0 +1,2 @@
1
+ .lockstep/
2
+ .lake/
@@ -0,0 +1,7 @@
1
+ # Working rules
2
+
3
+ - Export a stable `LOCKSTEP_AGENT=<client>:<session id>` in every shell; claims, jobs and log entries are recorded under it.
4
+ - Project state comes from `lockstep status`; pick work with `lockstep ready`.
5
+ - Run `lockstep claim <node> --why "<plan>"` before starting, and `lockstep release <node> --outcome ... --note ...` when you stop.
6
+ - New statements go in only through `lockstep node add`; never edit a statement by hand.
7
+ - Commands that run longer than a minute go through `lockstep job run --why "<purpose>" --out runs/<dir> -- <command>`.
@@ -0,0 +1,6 @@
1
+ namespace Demo
2
+
3
+ /-- A core lemma that blueprint nodes can build on. -/
4
+ theorem double (n : Nat) : n + n = 2 * n := by omega
5
+
6
+ end Demo
@@ -0,0 +1 @@
1
+ import Demo.Basic
@@ -0,0 +1,2 @@
1
+ -- Root of the DemoBlueprint library: imports every node module.
2
+ -- Maintained by `lockstep node add` / `lockstep node retire`; do not edit by hand.