OpenProver uses a planner-worker architecture. A single planner LLM coordinates the proof search by maintaining a whiteboard and repository, spawning focused worker tasks that run in parallel. All state lives on disk for resumability.
cli.py Parse args, setup TUI, run prover, print cost
prover.py Planner loop, step dispatch, action handlers, Repo
prompts.py All prompt templates, TOML parser, actions enum
budget.py Budget tracking (token or time limits)
inspect.py Read-only run browser
llm/
_base.py Shared helpers: Interrupted, StreamingUnavailable, archive(), error detection
claude.py LLMClient - Claude CLI wrapper (default backend)
mistral.py MistralClient - Mistral Conversations API (Leanstral)
glm.py GLMClient - Z.ai OpenAI-compatible API (GLM-5)
openrouter.py OpenRouterClient - OpenRouter API (Kimi K2.5, MiniMax M2.5/M2.7)
hf.py HFClient - local OpenAI-compatible servers (vLLM, serve_hf.py)
lean/
core.py Lean 4 integration: parsing, assembly, verification
data.py Lean Explore data management (fetch, availability checks)
mcp_server.py MCP server exposing lean_verify, lean_store, lean_search tools
tools.py Tool definitions for vLLM native tool calling, execute_worker_tool()
tui/
tui.py Full-screen interactive TUI (ANSI scroll regions, tabs, streaming)
headless.py Non-interactive TUI (logs to stdout/stderr)
_render.py Terminal rendering, ANSI coloring
_steps.py Step/turn display logic
_stream.py Real-time streaming text updates
_input.py User input handling (keyboard, confirmation UI)
_nav.py Navigation state (tab switching, history)
_tabs.py Tab management (planner vs worker views)
_text.py Text utilities (wrapping, formatting)
_colors.py ANSI color/style definitions
_types.py Type definitions for TUI state
Planner (runs every step):
- Maintains a whiteboard: terse mathematical scratchpad (LaTeX, abbreviations)
- Manages a repository of items: lemmas, observations, failed attempts, literature findings
- Receives: whiteboard + repo index (one-line summaries) + recent outputs (rolling window)
- Outputs: structured decision with action, summary, updated whiteboard, and action-specific fields
Workers (spawned on demand, parallel):
- Receive a task description from the planner
- Can reference repo items via
[[wikilink]]syntax (resolved before sending) - When
--lean-projectis set with a tool-capable worker model, workers have access tolean_verify,lean_store, andlean_searchtools via MCP (Claude) or native tool calling (other backends) - Report free-form results back to the planner
Repository (repo/ directory):
- Each item is a
.mdfile:Summary: One sentence.\n\nFull content - Planner can create, read, update, or delete items
- Used to persist proven lemmas, observations, literature reviews, failed approaches
Entry point. Parses arguments, creates a Prover and TUI, installs signal handlers, runs the prover, prints cost summary on exit.
Subcommands:
openprover <theorem>- main proving loopopenprover inspect [run_dir]- browse a historical runopenprover fetch-lean-data- download Lean Explore search data and models
The LLM client is constructed via a factory pattern: Prover calls make_llm(archive_dir) after setting up the work directory, so the archive path is correct from the start. Separate planner and worker models are supported via --planner-model and --worker-model.
Model routing maps short names to backends:
sonnet,opus-LLMClient(Claude CLI)leanstral-MistralClient(Mistral Conversations API)glm-5-GLMClient(Z.ai native API)kimi-k2.5,minimax-m2.5,minimax-m2.7-OpenRouterClient
Run configuration is saved to run_config.toml in the work directory on fresh starts and restored on resume. CLI flags override saved values. Version mismatch between the saved config and the running binary is rejected.
Tracks proving session budget in two modes:
- Time budget (
--max-time, default4h): wall-clock seconds - Token budget (
--max-tokens): cumulative output tokens
The Budget class tracks elapsed time/tokens and exposes is_exhausted, should_conclude (at --conclude-after fraction). The planner prompt receives budget status so it can pace itself.
The Prover class owns the proving loop and all state.
Init: Creates or resumes a run directory (runs/<slug>-<timestamp>/). Loads or initializes the whiteboard. Creates the Repo instance. Resume is detected by checking for existing WHITEBOARD.md + THEOREM.md; step count inferred from step_NNN directories.
When lean_worker_tools is enabled, sets up tool calling for workers:
- Claude CLI workers: Configures an MCP server (
lean/mcp_server.py) withlean_verify,lean_store, andlean_searchtools - Other backends: Initializes LeanExplore search service in-process and uses native OpenAI tool calling
Step flow (run -> _do_step):
-
Planner call: Build prompt from whiteboard + repo index + prev worker output. LLM call with streaming (shows in planner tab). Response must contain a structured action block.
-
Parse action: Extract action, summary, whiteboard update, and action-specific fields (
[[tasks]],[[items]],search_query, etc.) viaparse_planner_toml(). -
Dispatch to action handler based on the action field.
-
Save step: Write
planner.toml, worker tasks/results, archive LLM calls, updateWHITEBOARD.mdon disk.
Action handlers:
| Handler | What it does |
|---|---|
_handle_spawn |
Run worker tasks in parallel via ThreadPoolExecutor (up to --max-workers). Each worker gets its task description with wikilinks resolved. Results pushed to output window. |
_handle_literature_search |
Spawn a web-enabled worker (Claude CLI with WebSearch + WebFetch tools). Results fed back to planner. |
_handle_read_items |
Fetch full content of requested repo items, push to output. |
_handle_write_items |
Create/update/delete repo items. Items with format="lean" are auto-verified via lake env lean. |
_handle_write_whiteboard |
Update the whiteboard without spawning workers. |
_handle_read_theorem |
Return THEOREM.md + THEOREM.lean + PROOF.md content to the planner. |
_handle_submit_proof |
Save proof to PROOF.md. Terminates the session in prove mode. |
_handle_submit_lean_proof |
Assembles and verifies Lean proof via lake env lean, writes PROOF.lean on success. Terminates the session in formalize_only mode; in prove_and_formalize, the session ends when both informal and formal proofs are accepted. |
Repo class (also in prover.py):
list_summaries(): Returns index of all items (name + first-line summary)read_item(slug)/read_items(slugs): Fetch contentwrite_item(slug, content): Create, update, or deleteresolve_wikilinks(text): Find[[slug]]references, return resolved text + appended reference materials
Output window: The planner sees recent outputs in a rolling window (via _push_output()). Outputs persist across steps until pushed out by newer ones. This gives the planner context about recent progress.
Operating modes (determined by CLI args):
| Mode | Inputs | Goal | Terminates when |
|---|---|---|---|
prove |
THEOREM.md | Informal proof | submit_proof -> PROOF.md |
prove_and_formalize |
THEOREM.md + THEOREM.lean | Both proofs | PROOF.md + PROOF.lean both exist |
formalize_only |
THEOREM.md + THEOREM.lean + PROOF.md | Formal proof | submit_lean_proof succeeds -> PROOF.lean |
Worker tool execution (when lean_worker_tools is enabled):
For backends with native tool calling (vLLM, GLM, OpenRouter, Mistral), tools are executed in a multi-turn loop: the LLM requests tool calls, execute_worker_tool() dispatches to the appropriate handler, results are appended to the conversation, and the LLM continues.
For the Claude CLI path, tool execution is handled by the MCP server subprocess. Tool call events are detected from the stream and reported to the TUI via add_worker_action().
Other methods:
_write_discussion(): Post-session analysis via LLM callis_finished: Check if run completed (mode-aware: checks for required artifacts)inspect(): Browse a historical run in read-only mode_load_history(): Restore step history from disk for inspect mode
Five LLM client backends with a shared interface.
Shared interface (call method):
def call(
prompt, system_prompt,
json_schema=None, label="", web_search=False,
stream_callback=None, archive_path=None, max_tokens=None, **kwargs
) -> dict # {result, thinking, cost, duration_ms, raw, finish_reason, ...}Tool-capable clients also support chat() for multi-turn tool calling conversations.
LLMClient (Claude CLI, claude.py):
Non-streaming:
claude -p --model <model> --system-prompt <...> --output-format json --tools ""
Streaming:
claude -p --model <model> --system-prompt <...> --output-format stream-json --verbose --include-partial-messages --tools ""
Uses Popen + readline() (not the line iterator, which has read-ahead buffering that defeats real-time streaming). Parses NDJSON lines, dispatches content_block_delta text to the callback.
Web search: When web_search=True, replaces --tools "" with --permission-mode bypassPermissions --allowedTools WebSearch WebFetch.
MCP tool calling: When mcp_config is set, adds --mcp-config <json> --strict-mcp-config --permission-mode bypassPermissions --allowedTools mcp__lean_tools__lean_verify mcp__lean_tools__lean_store mcp__lean_tools__lean_search. Tool events are detected from the stream:
tool_usecontent blocks are tracked by their ID (capturing tool name and input as they stream in)- Tool results appear as top-level
{"type": "user"}messages (not as content blocks), containingtool_resultentries matched bytool_use_id - MCP prefixes are stripped from tool names (
mcp__lean_tools__lean_verify->lean_verify) - Status is inferred from result text (e.g.,
lean_verifyresults starting with "OK" = success)
Archiving: Every call saved to archive/calls/call_NNN.json with full prompt, system prompt, schema, response, cost, timing, and errors.
MistralClient (mistral.py):
- Uses the Mistral Conversations API, which persists context server-side
- Avoids re-sending the full transcript on each turn
- Supports streaming and tool calling
- Requires
MISTRAL_API_KEY
GLMClient (glm.py):
- Calls Z.ai's native OpenAI-compatible API for accurate token usage reporting
- Supports
reasoning_content(extended thinking, same format as DeepSeek) - 128k context window
- Requires
GLM_API_KEY
OpenRouterClient (openrouter.py):
- Calls OpenRouter's OpenAI-compatible API
- Hosts Kimi K2.5 (262k context), MiniMax M2.5/M2.7 (196k context)
- Supports streaming and tool calling
- Requires
OPENROUTER_API_KEY
HFClient (hf.py, for vLLM/serve_hf.py):
- Calls an OpenAI-compatible API at
--provider-url - Health check on init (
/healthendpoint) - Streaming via chunked transfer encoding
chat()method for multi-turn tool calling conversations- Supports
reasoning_contentfield - Cost always 0.0 (local model)
- Automatically enforces
--isolation
Key gotchas:
- Claude CLI:
--json-schemaputs structured output inraw["structured_output"], notraw["result"] - Claude CLI:
--tools ""disables all tools (pure reasoning mode) - Cost tracking uses
total_cost_usdfrom the Claude CLI response - All backends archive calls with markdown frontmatter + raw JSON sidecars
All prompt templates, the TOML parser, and the actions enum.
Actions:
ACTIONS = [
"submit_proof", "submit_lean_proof", "read_items", "write_items",
"spawn", "literature_search", "read_theorem", "write_whiteboard",
]System prompts:
planner_system_prompt(...): Built dynamically. Instructs the planner to coordinate proof search, maintain whiteboard, manage repo, delegate to workers. Actions and principles are assembled based on mode (prove/prove_and_formalize/formalize_only), isolation setting, and Lean availability.worker_system_prompt(lean_worker_tools=False): Instructs worker to complete its task rigorously. Whenlean_worker_tools=True, documentslean_verify,lean_store, andlean_searchtools. If verifying, be skeptical and end withVERDICT: CORRECTorVERDICT: INCORRECT.SEARCH_SYSTEM_PROMPT: Instructs literature search worker.
Prompt formatters:
format_planner_prompt(whiteboard, repo_index, prev_outputs, ...): Planner input (prev_outputs is a list of recent outputs). Includes status indicators for theorem statement, Lean statement, informal proof, and formal proof.format_worker_prompt(task_description, resolved_refs): Worker inputformat_search_prompt(query, context): Literature search promptformat_initial_whiteboard(theorem, mode): Template with Goal / Strategy / Status / Tried. Includes mode banner for lean modes.format_discussion_prompt(...): Post-session analysis
TOML parser (parse_planner_toml):
- Extracts TOML from fenced block or bare TOML at end of response
- Uses
tomllib(Python 3.11+) ortomli(3.10 fallback) - Falls back to a minimal regex-based parser if neither available
- Handles
[[tasks]],[[items]], and[[lean_blocks]]array-of-tables syntax - Post-processes
lean_block_Nnumbered keys into alean_blockslist (both styles accepted from LLMs)
Lean 4 integration -- all formal verification logic isolated here.
LeanTheorem: Parses a THEOREM.lean file.
- Extracts preamble (import/open lines at top), locates all
sorrypositions via\bsorry\bregex assemble_proof(replacements, context): replaces each sorry with its corresponding block (in reverse order to preserve offsets), injects optional context after preamble. Validates: correct count, noimportin injected code.
run_lean_check(lean_file, project_dir, timeout=300): Runs lake env lean <file> from the project directory. Returns (True, "") if returncode 0 and empty stdout; otherwise (False, feedback) with combined stdout/stderr.
LeanWorkDir: Manages an OpenProver-{random_8hex} subdirectory within the Lean project. Generated lean files go here with {slug}-{random_6hex}.lean naming. The final verified proof is written as PROOF.lean.
MCP server exposing lean_verify, lean_store, and lean_search tools for Claude CLI workers. Runs as a subprocess spawned by Claude CLI via --mcp-config. Communicates over stdio using JSON-RPC (MCP protocol).
lean_verify(code): Writes code to a temp file in the Lean work directory, runsrun_lean_check(), returns "OK - no errors" or compiler output. If a persistent store exists, its contents are automatically prepended.lean_store(code): Verifies the snippet, then appends it to the worker's persistent prefix. Stored code is auto-prepended to all subsequentlean_verifycalls for that worker. Rejects code withsorry,axiom,unsafe,set_option, ornative_decide. Imports are automatically deduplicated.lean_search(query): Searches Lean 4 declarations using LeanExplore. Returns matching names, signatures, and docstrings.
Environment variables LEAN_PROJECT_DIR and LEAN_WORK_DIR are set by the prover when spawning the MCP server.
Tool definitions in OpenAI function-calling format for non-Claude backends. Defines WORKER_TOOLS (list of tool specs for lean_verify, lean_store, lean_search) and execute_worker_tool() which dispatches tool calls and manages per-worker persistent stores.
Manages LeanExplore search data and dependencies.
is_lean_data_available(): Checks for lean-explore package, torch, sentence-transformers, and fetched data filesfetch_lean_data(): Installs missing dependencies (lean-explore, torch CPU, sentence-transformers), fetches search data vialean-explore data fetch, and pre-downloads the embedding model
Called automatically on startup when --lean-worker-tools is enabled and data is missing. Also available as openprover fetch-lean-data.
The lean_search tool searches ~400k Lean 4 declarations (Init, Batteries, Lean, Mathlib, Std) using a multi-stage pipeline:
-
BM25 retrieval (~0.01s): Keyword search over LLM-generated natural language descriptions ("informalizations") of each declaration. Two indices with different tokenization strategies, results merged.
-
Semantic retrieval (~1s): Encodes query with Qwen3-Embedding-0.6B (sentence-transformer, 1024-dim), searches pre-built FAISS index for nearest neighbors by cosine similarity.
-
Score fusion: Normalizes both score sets to [0,1], combines as
0.3 * bm25 + 0.7 * semantic, applies dependency boost for related declarations. -
Reranking (GPU only, ~1-2s; skipped on CPU): Cross-encoder scoring with Qwen3-Reranker-0.6B. Disabled on CPU where it takes ~50s per query.
-
Hydration: Looks up full metadata (signature, docstring, source) from SQLite.
First call takes ~10s (model loading from disk). Subsequent calls ~1s.
Data is fetched once via openprover fetch-lean-data or automatically on first use. Stored in ~/.lean_explore/cache/.
Modular full-screen terminal UI using ANSI escape codes and scroll regions.
Layout: Rows 1-4 are a fixed header (theorem name, step counter, model, tab bar). Row 5+ is the scrolling content area.
Tab system:
- Always has a
plannertab (fixed) - Spawn and search steps create worker tabs dynamically
- Tab bar shows status indicators: checkmark = done, ellipsis = streaming
- Left/right arrows switch tabs instantly
Views: main (log + streaming trace), whiteboard, help, step_detail, input (worker task on worker tabs).
Key handling: Background thread reads stdin in cbreak mode via select() + os.read(). Instant keys (work during streaming): r, i, w, a, left/right, up/down, PgUp/PgDn, ?. Queued keys (during confirmation): Tab, Enter, s, p, q, Esc.
Worker action display: When workers use lean_verify, lean_store, or lean_search, each tool call appears as a navigable entry in the worker tab. Up/down arrows cycle through actions, Enter opens a detail view showing the tool input and result.
Streaming: Braille spinner while waiting for first token (~12 FPS). Toggleable reasoning trace with r. Worker tabs show their own streaming output independently.
Confirmation UI: Two-option selector (accept / give feedback) with text input. Up/down browse step history; Enter on a history entry opens detail view.
Thread safety: All stdout writes protected by _write_lock. Background key thread doesn't hold the lock during I/O.
HeadlessTUI: Non-interactive alternative for --headless mode. Logs step summaries and worker results to stdout, errors to stderr. No streaming display.
Read-only run browser. Loads a completed run directory and displays it in the TUI for review. Accessed via openprover inspect [run_dir] or automatically when resuming a finished run.
runs/<slug>-<timestamp>/
THEOREM.md - immutable copy of input
THEOREM.lean - formal Lean statement (if --lean-theorem)
WHITEBOARD.md - latest whiteboard state (enables resume)
PROOF.md - written only if proof found
PROOF.lean - formal Lean proof (if lean mode)
DISCUSSION.md - post-session analysis
run_config.toml - saved run configuration (for resume)
repo/
*.md - repository items (lemmas, observations, etc.)
steps/
step_001/
planner.toml - planner's TOML decision
workers/
task_0.md - worker task description
result_0.md - worker output
worker_0_call.json - archived LLM call
step_002/...
archive/
calls/
call_001.json - full LLM call record
Slug format: First 40 chars of theorem, lowercased, non-alphanumeric replaced with hyphens. Example: sqrt2-irrational-20260220-143706.
Resume: If the run directory contains WHITEBOARD.md + THEOREM.md, the prover picks up from the last completed step. Settings are restored from run_config.toml; CLI flags override saved values.
Informal verification (all modes): Workers can be tasked with verification by the planner. A verifier worker sees only the proof text (not the reasoning that produced it) and must end its response with VERDICT: CORRECT or VERDICT: INCORRECT. The planner is instructed to verify proofs before submitting. The --verifier / --no-verifier flag controls whether an automatic LLM verifier runs after each worker.
Formal verification (lean modes): When --lean-project is provided, the system supports automatic Lean 4 verification:
write_itemswithformat="lean": Lean items are written to theOpenProver-{id}/subdirectory within the Lean project and verified vialake env lean. The planner receives pass/fail feedback with compiler errors.submit_lean_proof: The planner provides N replacement blocks (one persorryin THEOREM.lean) plus optional context. The system assembles the complete file, verifies it, and writes PROOF.lean on success. On failure, compiler errors are fed back.read_theorem: Returns THEOREM.md, THEOREM.lean, and PROOF.md (if provided) content so the planner can reference the formal statement.
Worker tools (when --lean-worker-tools is enabled): Workers can directly verify Lean code (lean_verify), store verified snippets (lean_store), and search Lean libraries (lean_search) during their reasoning. Tool calls are shown in the TUI worker tab.
Generated Lean files are placed in <lean-project>/OpenProver-<random_id>/ with {slug}-{random_suffix}.lean names to avoid collisions. No import statements are allowed in injected code (enforced at assembly time).
Task descriptions can reference repository items via [[slug]] syntax. Before a worker receives its task, repo.resolve_wikilinks() finds all references, fetches the content, and appends it as a "Referenced Materials" section. This lets the planner share proven lemmas, observations, or literature findings with workers without duplicating content in every task.
- Add the action name to
ACTIONSinprompts.py - Describe it in
_build_actions()inprompts.py(format and when to use it) - Handle it in
Prover._do_step()(add a_handle_<action>method) - Add a color in
ACTION_STYLEintui/_colors.py
- Add the tool function to
lean/mcp_server.py(decorated with@mcp.tool()) - Add the tool definition to
WORKER_TOOLSinlean/tools.py(OpenAI format) - Add a dispatch case in
execute_worker_tool()inlean/tools.py - Add the
--allowedToolsentry inllm/claude.py(MCP tool name) - Document it in
worker_system_prompt()inprompts.py - Add a color in
TOOL_STYLEintui/_colors.py
- Create a new module in
llm/implementingcall()and optionallychat()methods - Export it from
llm/__init__.py - Add model name(s) to
model_choicesand the appropriate*_MODEL_MAP/*_MODELSset incli.py - Add the factory logic in
cli.pyto construct the client