11. The Mathematical API and the agent orchestrator
In the diagram, one box is between the Research Workspace and the Research Agents: "Mathematical API and Agent Orchestrator". This box has two roles in one process:
- the Mathematical API is the only way for anything outside the build to read the knowledge graph or to ask for work. This includes a person's browser, an agent and an external tool;
- the Orchestrator changes requests for work into agent runs. It schedules the runs, enforces budgets and records everything. It changes the results into pull requests.
Both are in tamnd/math2-agent (api, mcp, orchestrator). m2 serve and m2 orchestrate serve them.
11.1 Principles
- Read is free, write is a pull request. The API serves reads from the derived store of a release, or of
main. Every write becomes a branch and a pull request againsttamnd/math2. This is true for a write from a person in the workspace and for a write from an agent. The API has no other write path. Thus the audit (document 15) is the only gate, and git is the only history. - One surface, three encodings. The API uses HTTP+JSON for browsers and scripts, gRPC for the agents' tool calls inside the cluster, and MCP for any model client. One interface definition generates all three:
api/m2.protowith HTTP annotations. Thus the three encodings cannot drift apart. - Every response says what it is about. It carries the release or commit that served it, the tag's content hash and the evidence summary. An agent that reasons from an API response can cite exactly what it saw.
- The API never returns a status it did not compute. It serves the status that the build computed (document 04.7). It does not pass through any statement about truth from an external system.
11.2 The read surface
The methods are in groups by purpose. Each method is also an m2 subcommand and an MCP tool with the same name.
Resolve and fetch
| Method | Input | Output |
|---|---|---|
resolve |
any identifier: tag, alias, m2:// URI, Lean name, QID, A-number, Stacks tag, arXiv result tag, DOI |
tag(s) with match type, or "not held" plus the external system |
get |
tag[@release] | node: front matter, body, computed status, evidence summary |
get_formal |
tag | bindings, with the elaborated statement pretty-printed, axioms, replay result |
get_source |
tag | sources with locators and aligned paper results |
history |
tag | revisions with provenance, binding changes, review history |
Navigate
| Method | Input | Output |
|---|---|---|
neighbours |
tag, edge types, depth ≤ 3 | typed subgraph |
closure |
tag, strategy (min-depth / min-size), stop set | AND–OR closure with trust profile (05.5) |
rdeps |
tag | everything whose every proof depends on it |
path |
from, to, edge filter | prerequisite path (05.6 Q5) |
frontier |
topic, kind, evidence filter | ranked frontier lists (Q6, Q7) |
topic |
MSC code or topic tag | the topic page data: members by kind and level, coverage |
diff |
topic, release₁, release₂ | changes (Q10) |
Search
| Method | Input | Output |
|---|---|---|
search |
free text (natural language and/or LaTeX) | ranked nodes with snippets, scores and channel attribution (document 14) |
search_formal |
a Lean type pattern, or a Lean goal | bound nodes and Mathlib declarations, Loogle-style (14.3) |
search_formula |
LaTeX formula | formula-structure matches (14.2) |
similar |
tag | nearest nodes in embedding space, with the judge label when one is cached |
premises |
Lean goal state + file context | ranked candidate lemmas for a proof step (14.5) |
Check and compute
These methods do work, but they do not write content. They return their results. The results are also cached by hash.
| Method | Input | Output |
|---|---|---|
lean_check |
Lean source snippet + imports at the pinned toolchain | elaboration result, messages, goals, axioms |
lean_goal |
snippet + position | goal state (used by provers via REPL sessions, 11.3) |
fidelity_check |
tag + candidate Lean statement | the F1 battery: back-translation, degenerate checks, critics (document 10.4) |
compute |
computation spec (image, entry, inputs) | output, hash, certificate check result (document 08) |
counterexample |
Lean statement or informal claim + domain hints | SMT/SAT/brute-force search result |
Propose (writes, via pull request)
| Method | Effect |
|---|---|
propose_node |
open a PR with a new node (provisional id) |
propose_edit |
open a PR editing allowed fields of a node |
propose_binding |
open a PR adding a formal binding plus its Lean code |
propose_alignment |
open a PR adding same_as/close_to/aligned edges |
request_review |
file a review task for a binding (document 10.6) |
request_work |
file an orchestrator job (11.4) |
People use the same propose methods from the workspace. The PR that results is attributed to the person, and the workspace is named as the tool.
11.3 Lean sessions
A proof is interactive work: try a tactic, look at the new goals, then try a different tactic. One HTTP call for each attempt with a full re-elaboration is much too slow. Thus the API gives Lean sessions:
- A session is a Lean REPL process (the community
repl, or Pantograph-style goal-level access). It starts from the pinned toolchain with Mathlib andMath2imported. The process is snapshotted after the imports. Thus a session costs milliseconds to start, not the 10 to 30 s that it takes to load Mathlib. - A session holds a proof state tree. The calls are
run_tactic(state_id, tactic) → new states | error,goal(state_id),branch(state_id)andclose(). - Sessions run in the sandbox (document 08.6), with a memory cap and a timeout for each call. When a tactic times out, only its own call stops.
- Each runner has a session pool of a set size. The orchestrator charges session-seconds to the budget of the run.
The lean-lsp-mcp and Kimina-server style tools show the same interface (document 01). math2 uses its own interface because it must pin it to the release toolchain, sandbox it and meter it. Because of the MCP surface, any agent that can use MCP can prove things against math2 directly. This includes Claude Code with no math2-specific code.
11.4 The orchestrator: jobs
A job is a unit of work with a goal, a target and a budget. Jobs come from four places:
- People, through the workspace or
request_work("formalise the statement of 7K3QX2", "find a counterexample to this"). - Standing queues that the orchestrator computes from the graph: the frontier lists (Q6, Q7, Q12). Examples: each E3 statement with an F2 formal statement and no formal proof becomes a
provejob. Each node with no explanation at L2 becomes anexplainjob. Each unconfirmed alignment older than 30 days becomes aalignjob. - Events: a Mathlib bump breaks bindings (→
bumpjobs), a review becomes stale (→review-assistjobs), a new arXiv batch arrives (→extractandalignjobs). - Campaigns: a project that a person defines, with a target set. Examples are "the undergraduate core list to F2" and "the Erdős problems in additive combinatorics to formal statements". A campaign is a topic node plus a goal predicate. Human research projects use the agents through campaigns (document 16.3).
Each job has this record:
job: j-2026-11-02-41a7
role: prove
target: 7K3QX2
goal: "binding formal[0] reaches E5"
priority: 0.73 # computed (11.5)
budget: {usd: 4.00, wall: 2h, lean_session_s: 3600, compute_cpu_s: 7200}
on_behalf_of: campaign:ug-core # or a person, or schedule:standing-prove
requested_by: orcid:0000-0002-1825-0097
constraints: {models: [claude-opus-5-5, claude-sonnet-5-5]} # native_decide: true permits native_decide, and type_change: true permits a bump rebind to another type hash
Priority
Priority is a score, not a decision by a person. Its formula has versions, as a prompt has:
priority = w1 · centrality(target) # PageRank on the uses graph, so foundational nodes first
+ w2 · demand(target) # workspace views, citations, campaign membership
+ w3 · unlock(target) # how many frontier items this would move a level
+ w4 · tractability(target, role) # learned: predicted success from past runs on similar targets
− w5 · attempts(target, role) # diminishing returns after failed attempts
unlock is the most important term. For example, 200 E1 statements use a lemma, and each one needs only that lemma. A proof of that lemma has more value than a proof of any one of the 200 statements. The closure structure in document 05.5 lets the orchestrator compute this.
Scheduling
- Jobs go into one queue for each role. The queues share fairly across campaigns, so that one project cannot use all of the budget.
- Portfolio scheduling. A hard target gets several cheap attempts with different strategies before one expensive attempt. The strategies use different models, different retrieval and different decompositions. This is the method that worked in the provers of 2025 to 2026 (document 01): many different samples, with a verifier as the filter, are better than one long attempt.
- Escalation ladder. A job starts at the cheapest configuration that has some recorded success on similar targets. If the job fails, it moves up the ladder: Haiku 5.5 or a small open prover, then Sonnet 5.5, then Opus 5.5 at higher effort, then Fable 5.1 at high or max effort. Each role has its own ladder. The run records tune the ladder (document 12.9).
- Deduplication. If the target and goal of a job match a running job, the orchestrator merges the new job into the running job. Sometimes a job's target had an attempt in the last N days with the same configuration, and the attempt failed. The orchestrator skips that job until something changes in the neighbourhood of the target.
11.5 Budgets
Money and compute are the real limits, so they are first-class:
- Global: a monthly budget, divided by role in
orchestrator.yaml. People review this file each month and commit it. - Per campaign: the owner of the campaign sets it. The global allocation is its cap.
- Per job: the priority tier sets it, and the orchestrator enforces it. Model calls carry token budgets. These are the API's task budgets where the model supports them, and hard
max_tokensin all cases. The sandbox meters Lean time and compute time. - Review capacity: money is not the only limit. Each role has a cap on PRs per week. The cap is a multiple of the review throughput for that kind of PR over the previous four weeks. Thus agents cannot go faster than the people who must check their work (document 21, Q-11).
- Stopping: a job stops at success, when the budget is used up, or when the agent reports that it is stuck and gives a reason. A stuck report with a reason is valuable output. "This statement appears false at n=7, here is the computation" is a result.
Each job records its spend: tokens in and out for each model, cached tokens, session-seconds, CPU-seconds and dollars. The weekly agent report (document 12.9) uses these records. The report shows what a formal proof costs, by area and by difficulty. At this time, the field does not publish this number.
The first version
The first version of the orchestrator is in the orchestrator package, and m2 orchestrate runs it on a directory of job files (tamnd/math2-agent#61). It has these parts and limits:
- One queue for each role. The orchestrator takes the roles in turn. In a role, it takes first the project or schedule that spent the least in the month, then the job with the highest priority. The priority is a field of the job, and the formula of the priority is not written yet.
- The monthly budget of each role and the monthly cap of each project or schedule are in
orchestrator.yaml. A run gets the least of the money that is left to its job, to its role and to its project or schedule, and the orchestrator reserves that money while the run goes. The job budget is for all the runs of the job together.agent.Runnersetsmax_tokensbefore each turn so that the turn cannot cost more than the money that is left: it counts each byte of the request as one input token at the highest input price. Thus a run never goes over its budget, even when its last turn uses each token that it may use. - A job starts on the cheapest rung of the ladder of its role with a recorded success on the role, and each failed run moves the job one rung up. A failed attempt on the same target, goal and rung in the retry time (7 days by default) makes the orchestrator skip that rung. A job with the target and the goal of a job in the queues merges into that job.
- A job ends at success, at a stuck report, when the ladder or the budget of the job is used up, or when a run cannot start. The orchestrator knows the past attempts from the run records in the store.
- Add refuses a job that names no person, project or schedule (rule P03).
- With
-sandbox IMAGE, each call of lake, Lean and M2Extract in a run is a container of the image, with no network (11.7). The work tree is writable at/out, and the Lake packages of the tree are read-only at/work.m2 agent runandm2 orchestraterefuse to run with no image, unless-no-sandboxsays that a person watches the run. The imagem2-leanis not built yet, and the quotas for each identity are not written yet. - The standing queues, the events, the portfolio of strategies and the cap on pull requests of each role are not written yet.
11.6 Run records
Each agent run makes a run record in the artefact store. The run id is the key. The record contains:
- the job record and the resolved configuration: role version, prompt version, model ids, tool versions, image digests;
- the full transcript: every model request and response (thinking blocks as the API returned them), every tool call and result;
- the tactic tree of every Lean session;
- the outcome: success with the diff, failure with the reason, or a partial result;
- the spend.
Run records complete the provenance chain in document 06.5. They are also the training and evaluation data that improve the agents (document 17.4). The system keeps them as document 06.6 specifies. Their summaries are public. When the output of a run was merged, the full transcript is public. When the output was not merged, the system keeps the full transcript but does not publish it. By default, math2 does not publish a failed attempt, but it uses the attempt to learn.
11.7 Security and abuse
- Read endpoints are public and have rate limits. Write endpoints need a GitHub identity, and the PR opens under that identity.
lean_check,computeand Lean sessions run arbitrary code: Lean metaprograms can do arbitrary IO at elaboration time. Thus they run in the sandbox, with no network, a read-only toolchain and quotas for each identity. Anonymous use is permitted only forlean_checkon small snippets, with a strict timeout.- MCP tools that write have a mark that says so. The read-only MCP profile disables them. That profile is the default for third-party agent clients.
- Text from sources (paper text, MathOverflow posts) is data. It goes into agent contexts with an untrusted mark. It is never put in a system prompt, and it is never concatenated into a tool description (document 12.7).
11.8 The first version
The first version of the read surface is in the api package, and m2 serve serves it. It has these limits:
- It serves the last build of one working tree, not a release. A request for
tag@releasegives an error. get_source,history,topicanddiffcome with the provenance of document 06 and the topic pages of document 13. The check and compute methods,request_reviewandrequest_workcome with the orchestrator.- The one interface definition is the table of methods in the
apipackage. The HTTP handler and the MCP server read the table.api.Protomakesapi/m2.protofrom it, with the HTTP annotations, andbufmakes the Go code inapi/m2pbfrom that file (tamnd/math2-agent#126). A test checks thatapi/m2.protois the file that the table makes, a test checks that the code inapi/m2pbagrees with the table, and a test checks that each gRPC call of the contract gives the HTTP answer. Thus the three encodings cannot drift apart. The gRPC service ism2.v1.ReadService, and each rpc gives oneResponsemessage with the result as agoogle.protobuf.Value, as each HTTP call gives the same JSON envelope.m2 serve -grpc ADDRserves it with the same rate limit for each IP address. - The rate limit is a token bucket for each IP address of a peer, in memory. Behind a proxy or a CDN, the handler must read the address that the proxy sets.
- The MCP server is in the
mcppackage. It has the read-only profile only, with one tool for each read method, andm2 serveserves it over streamable HTTP at/mcpand over stdio. It uses the Go SDK of the Model Context Protocol. similaruses the embedding channel with the text of the node, and it has no judge label yet.- The write methods
propose_node,propose_edit,propose_bindingandpropose_alignmentare in theproposepackage, andm2 serve -proposeserves them over HTTP only (tamnd/math2-agent#54). The gRPC service and the MCP server do not have them yet, thus the MCP server keeps the read-only profile of 11.7. Each write needs the GitHub token of a person, opens the pull request under that identity, and passes the audit of the changed files before the pull request opens. The cap of 11.5 counts the pull requests of each method from their branch names, with twice the closed pull requests of a week over four weeks and a floor of 10.propose_bindingadds the binding to the node file, and it does not add Lean code yet.request_reviewandrequest_workcome with the orchestrator.