formal-mas / docs /analysis_plan.md
NicolasT08's picture
Formal-MAS source code
dfe1826 verified
|
Raw History Blame Contribute Delete
22.8 kB
# Analysis plan (pre-registered before any run on `test`)
Written 2026-11-03, during Phase 8 Part A, **before any configuration was run on the `test`
split**. It fixes the metrics, tests and handling rules so that the analysis does not depend on
the results. Any later change must be recorded at the end of this file with its date and reason,
and reported in the paper as a deviation.
Notation and equations refer to the report (`docs/report/Proyecto_Inteligencia.pdf`, Section IV-A).
## 1. Data and design
- **Problems:** `data/eval.jsonl`, split `test`: 30 problems, stratified on the 3 × 3 grid
(difficulty easy/medium/hard × area algebra/number theory/combinatorics, with the documented
reallocation of Phase 7). The split was fixed with seed 2026 before any prompt tuning.
- **Configurations:** C1–C5 (Section IV-A8), all on the same LangGraph graph, profile
`experiment` (`deepseek-flash`, `config/models.yaml`), seed 2026, with the configuration frozen
by the team's commit at the end of Part A (its hash is recorded in each `run_config.json`).
- **Run order on `test`:** C5, C3, C4, C2, C1 (team decision 2026-10-31).
- **One run per (configuration, problem).** No configuration is re-run on `test` to obtain a
better result. The only exception is the `provider_error` rule of Section 5.
- **Paired design:** every configuration sees the same 30 problems, so comparisons between
configurations are paired by problem.
- The LLM response cache stays on. Identical requests across configurations (e.g. the first
attempt of C4 and C5) are answered from the cache; this keeps the paired comparison tighter and is
stated in the paper. Costs are compared with the **nominal** cost (`cost_usd_nominal`: cache
hits priced as fresh calls) so that run order does not favour later configurations.
## 2. Primary outcome
- **Verified success** of configuration *m* on problem *x*: Acc(φ_x, c*_x) of Eq. 2 for the final
assembled proof (status `solved` in `results.jsonl`). Only the Lean kernel decides (RNF-1).
- **Success rate:** SR_m = (number solved) / 30, with a **Wilson 95 % interval**.
- Since each problem is run once, pass@k of Eq. 15 is estimated only for k = 1 (= SR_m). This is
stated as a limitation; no repeated sampling is run.
## 3. Planned comparisons (confirmatory)
Three comparisons, decided now, each a **two-sided exact McNemar test** on the paired outcomes
(binomial test on the discordant pairs b, c with p = 1/2), **Bonferroni-corrected** for 3 tests
(reject at p < 0.05 / 3 ≈ 0.0167; adjusted p-values = min(1, 3p) are also reported):
| ID | Comparison | Question |
|---|---|---|
| H1 | C5 vs C1 | Does the multi-agent pipeline beat direct proof prompting (at equal mean measured computation, `b_max` of C1 fixed in Part A)? |
| H2 | C5 vs C3 | RAG contribution, Δ_RAG = SR_C5 − SR_C3 (Section IV-A9e). |
| H3 | C5 vs C4 | Contribution of the Critic loop (retries and replanning). |
For each one we report: b and c (discordant counts), ΔSR, a 95 % **paired bootstrap** interval for
ΔSR (10 000 resamples of problems, seed 2026, percentile method), the exact p-value and the
Bonferroni-adjusted p-value. With n = 30 the power is low; a non-significant result is reported as
"no evidence of a difference", never as "no difference".
**Exploratory only** (no correction, labelled exploratory, no claims): C3 vs C4, C1 vs C3, C1 vs C4,
and any per-difficulty or per-area comparison.
C2 is **not** compared with McNemar on verified success: its success is self-assessed, not verified.
## 4. Secondary outcomes
1. **Per cell of the 3 × 3 grid:** for each configuration, solved / n of each (difficulty, area)
cell, with the cell's n shown. Descriptive only: no tests and no intervals per cell (n ≤ 5).
Margins by difficulty and by area are reported with Wilson intervals. Empty cells (no easy
combinatorics) are shown as "—".
2. **Baseline B (Eq. 16):** claimed rate |B⁺| / 30 (Wilson), and FPR_B = |{x ∈ B⁺ : ¬Acc}| / |B⁺|
(Wilson), where every self-assessed proof is passed to the Autoformalizer–Verifier loop with the
same budget (`run_baseline_b.py --check`, to be built before the C2 run on `test`). FPR_B is an
upper bound on the true false-positive rate; a manual review of the B⁺ problems that fail
formalization (all of them if ≤ 10, otherwise 10 drawn with seed 2026) separates wrong informal
proofs from formalization failures.
3. **Efficiency (Section IV-A9c, RNF-3):** per configuration, C̄_solved = Σ_x C(x) / #solved
(failed problems' calls included in the numerator), and the same ratio for tokens, nominal USD
cost and wall-clock time; also the means per problem. 95 % bootstrap intervals (resampling
problems). RNF-3 (C̄_solved ≤ 20) is checked for C5 and reported as met or not; it is not tuned.
4. **Refinement robustness (Eqs. 14, IV-A9d):** SR(k) for k = 1, 2, 3, where a C5 problem counts
as solved at k if its accepted proof used at most k attempts on every subgoal (read from
`trace.jsonl`); k = 1 is also reported from C4. Δ̂(k) = SR(k) − SR(k−1) is plotted against the
independence curve of Eq. 14 with p_i estimated from first-attempt subgoal success in C5. The
paper states that k = 1 from C4 has no retries or advice while k = 2, 3 come from C5.
5. **Error taxonomy (Section IV-A9f):** relative frequency of each κ class of Eq. 11 over **all
failed attempts**, per configuration and per area; separately, the final failure reason per
problem (`stage` and final status). C1 attempts are classified with the same κ.
## 5. Status handling
| Status in `results.jsonl` | Meaning | Primary analysis |
|---|---|---|
| `solved` | Acc holds on the assembled proof | success |
| `failed` (any `stage`) | the method did not find an accepted proof | failure |
| `budget_exhausted` | `b_max` LLM calls reached | failure (the budget is part of the method) |
| `timeout` / memory limit | Lean verification limits | failure |
| `error` / `node_error` / `invalid_statement` | a bug in the system or the statement | failure; counted and listed |
| `provider_error` | the API refused or failed after all transport retries | see below |
| `budget_stop` | the project or run spend cap stopped the run | see below |
| run status `lean_unavailable`; row with `lean_down: true` | Lean could not load Mathlib (start-up probes) | as `provider_error`: infrastructure; re-run once, see below |
- **`provider_error`** is an infrastructure failure, not a decision of the model. After a run, the
problems with `provider_error` are re-run **once**, in a new run folder with the same
configuration and cache (the original run is kept untouched). The re-run's result replaces the
provider error in the analysis. If it is again `provider_error`, it counts as a **failure** in the
primary analysis, and a sensitivity analysis excludes those problems from that configuration
(and, for McNemar, the pairs where either side is excluded). Counts are reported per configuration.
- **`budget_stop`**: the problem and the rest of the run were not attempted. The team decides
whether to raise the spend cap; if raised, only the problems not attempted are run with the same
frozen configuration and merged. If not, that configuration is reported as **incomplete**: SR on
the attempted problems (with n stated), a conservative SR counting not-attempted problems as
failures, and McNemar only on the problems attempted by both configurations.
- **Lean unavailable**: a run that stops with `lean_unavailable` made no LLM call for the problems
not run; those problems, and a row marked `lean_down: true` (Lean was lost during it, so its
failure may be Lean's), are re-run once with the same configuration, exactly like
`provider_error`. A success in a `lean_down` row stands (the kernel accepted it).
- Runs marked `degraded` (Lean file back-end used) are valid and are flagged in the tables.
## 6. Fidelity notes (manual check before reporting success)
`data/curation/fidelity_review.yaml` lists, under `notes`, problems whose formal statement differs
from the informal text in a way judged harmless at curation time. Every problem **solved on `test`
by any configuration** that has a note is reviewed by hand before it is counted as a success in the
paper (PLAN.md, Phase 9). The review records whether the accepted proof establishes what the
informal problem asks. The verified SR (Section 2) is never changed by this review: the kernel's
verdict is what it is. If a review concludes that the formal statement does not capture the
informal problem, the paper reports it next to the table (problem, configuration, reason) and gives
the SR both with and without that problem.
## 7. What is reported, in order
1. Table: SR with Wilson 95 % CI for C1, C3, C4, C5; claimed rate and FPR_B for C2.
2. Table: H1–H3 with b, c, ΔSR, bootstrap CI, exact p and Bonferroni-adjusted p.
3. Table: per-cell solved/n for each configuration, with difficulty and area margins.
4. Table: efficiency (calls, tokens, nominal cost, time per problem and per solved problem).
5. Figure: SR(k), k = 1…3, vs the independence curve of Eq. 14.
6. Figure or table: κ taxonomy per configuration and area.
7. Counts of `provider_error`, `budget_stop`, `error`, degraded runs and the fidelity reviews.
Analysis code (`src/formal_mas/analysis/stats.py`, `scripts/compare.py`) is written in Phase 9 to
implement exactly this plan; it is tested on `dev` runs before it reads any `test` result.
## Exploratory analysis (post hoc, decided 2026-11-08)
Decided after all confirmatory `test` runs and their analysis were finished. It is exploratory
and changes no result, test or conclusion of §2–§7.
- **Motivation.** The hard problems look limited by the model rather than by the method: C1 and
C5 solve the same 4 of the 10 hard `test` problems (`imo_1964_p1_2`, `imo_1965_p2`,
`putnam_1964_b2`, `putnam_1965_b2`), so the agents add nothing on that stratum with
DeepSeek-Flash.
- **Run.** C5 exactly as frozen for the main runs (same graph, prompts, `pipeline.yaml` method
settings, seed, `B_max`/`b_max`), with model profile `experiment_pro`: identical to
`experiment` except that every role uses `deepseek-v4-pro` instead of `deepseek-flash`
(`config/models.yaml`). Input: the 10 hard `test` problems, all of them (not only those C5-Flash
failed), as a file with exactly those lines of `data/eval.jsonl` in dataset order; its sha256 and
id list go in `run_config.json`. One run, `--run-usd 5.00`; if it ends in `budget_stop` it is not
resumed and the problems without a complete row are reported as not attempted.
- **Reported.** A descriptive paired table, problem by problem, C5-Flash (the kept rows of the
main C5 `test` result, §Deviations) vs C5-Pro on the same 10 problems: solved, LLM calls,
cost (real and nominal), wall time in minutes; plus totals. The 2×2 table of solved/not solved
may be shown as counts.
- **Not done.** No hypothesis test, CI or p-value; nothing is added to the Bonferroni family; no
claim about Pro beyond these 10 problems. Pro's results never replace C5's in any main table.
- **Fidelity.** Any new success gets the same manual fidelity check as §6 before it is reported.
## Deviations
No deviation from the analysis itself. Infrastructure incidents during the `test` runs:
- **2026-11-03, C5 on `test` interrupted by low memory.** Run `20261103T174646Z_C5_experiment`
(commit `ad70ec2`, clean tree) was killed at about 19:16 UTC by the host (Claude Code stops
background jobs when the machine runs critically low on memory; the Lean REPL, ~3.5 GB, was
sharing RAM with an idle Lean language server, ~2.7 GB). It is an infrastructure failure, not a
decision of the method. 23 of 30 problems had finished and are kept unchanged; the 24th
(`imo_1987_p6`) was cut mid-problem and left no row in `results.jsonl`. No row was `lean_down`,
and there was no `provider_error` or `budget_stop`.
The 7 problems without a row were completed in two resume runs. Both used the same frozen
configuration (the analysis is the same at either commit), the same seed and cache, and the
language server was stopped first. The input of each is a file holding exactly the needed lines of
`data/eval.jsonl`, unchanged and in the original order. Its sha256 and the id list are in that
run's `run_config.json`, and a copy is kept as `test_remaining.jsonl` in that run's folder.
1. `20261103T212359Z_C5_experiment_resume` (commit `ad70ec2`, clean tree; input sha256
`53284f8f…086fc4`) ran all 7 from scratch: `imo_1987_p6`, `imo_1979_p1`, `imo_1984_p6`,
`imo_1964_p1_2`, `putnam_1964_b2`, `putnam_1995_b1`, `putnam_1965_b2`. It reached its spend
cap (`--run-usd 0.50`) during the third problem. `imo_1987_p6` and `imo_1979_p1` finished
(both `failed`). `imo_1984_p6` got a `budget_stop` row after 19 calls, and the last 4 were
not attempted.
2. `20261103T225140Z_C5_experiment_resume2` (commit `93bc701`, which changes only this file; clean
tree; input sha256 `f3da0f6d…6d77f91`) ran `imo_1984_p6` from scratch,
plus the 4 problems not attempted, with `--run-usd 1.00` (the cap raised by team decision, §5).
The requests that `imo_1984_p6` repeats from step 1 are answered from the cache.
**Merge:** the C5 result on `test` has one row per problem (30 in all), taken from the run where
the problem finished:
- 23 rows from the first run;
- `imo_1987_p6` and `imo_1979_p1` from resume 1;
- `imo_1984_p6` and the other 4 from resume 2.
The `budget_stop` row of `imo_1984_p6` in resume 1 is discarded; only its complete row counts. The
spend of the discarded attempts (`imo_1987_p6` in the first run and `imo_1984_p6` in resume 1)
counts towards the project's real cost. It is not in any kept row, so each problem's efficiency
figures come from its kept row only. Cache hits lower the real cost of the resume runs but not
their nominal cost, and nominal cost is the figure compared (§1).
- **2026-11-03, run order C4 before C3.** C4 was run right after C5, and C3 moved to the next
off-peak morning, so the order is C5, C4, C3, C2, C1 instead of C5, C3, C4, C2, C1 (§1). The
reason is schedule and cost only. C3 takes about 3 h and would have run into the provider's peak
window (01:00 UTC), while C4 is short and is answered mostly from the cache of C5. This has no
effect on the analysis. Each configuration is still run once on the same 30 problems with the
same frozen configuration, and the comparisons are paired by problem. C4 shares its cached
requests with C5, not with C3, so its cache hits are the same in either order. Costs are compared
at nominal prices (§1), so run order does not favour either configuration.
- **2026-11-04, per-run spend cap raised from 1.0 to 2.0 USD before C3 on `test`.** Team decision.
Only `config/pipeline.yaml → spend.run_usd` changed (1.0 → 2.0); `run_eval.py --run-usd` can only
lower that cap, so C3 (about 3 h, no cache shared with C5 because its prompts carry no lemmas;
C5 on `test` cost 1.36 USD real in total) would otherwise very likely end in `budget_stop`
before the 30th problem. This is a spend-control change, not a method change: the cap is
checked before each paid request and only decides whether the request is sent, so it does not
change any prompt, model, budget `B_max`/`b_max`, policy or result of a request that is sent. The
project cap `spend.total_usd` stays at 9.0 USD. C5 and C4 ran under the 1.0 USD cap (or a lower
`--run-usd`); C3 runs with `--run-usd 2.00`. The offline test that pins the configured caps
(`tests/test_spend.py::test_real_pipeline_yaml_has_the_caps`) was updated to the new value.
- **2026-11-05, per-run spend cap raised from 2.0 to 3.5 USD before C1 on `test`.** Team decision.
Only `config/pipeline.yaml → spend.run_usd` changed (2.0 → 3.5). C1 costs more per call than C5
(the cost run `20261103T161957Z_C1_experiment_c1_cost` spent 0.21 USD real on 3 `dev` problems,
about 0.07 USD per problem), so 30 problems would likely end in `budget_stop` under 2.0 USD. As
before, this is a spend-control change, not a method change: the cap is checked before each paid
request and only decides whether the request is sent, so it does not change any prompt, model,
budget `B_max`/`b_max`, policy or result of a request that is sent. The project cap
`spend.total_usd` stays at 9.0 USD. C1 runs with `--run-usd 3.50`. The offline test that pins
the configured caps was updated to the new value.
- **2026-11-05, C1 on `test` stopped by the team.** Run `20261105T131443Z_C1_experiment` (commit
`5ed7507`, clean tree, `--run-usd 3.50`) was stopped on purpose at about 17:43 UTC, together with
its whole process tree (REPL, `lake`), to free the machine for other work. It is an operational
incident, not a decision of the method: no code, prompt, model or configuration changed. 23 of 30
problems had finished and are kept unchanged; the 24th (`imo_1987_p6`) was cut mid-problem
(after 10 calls, 0.10 USD real) and left no row in `results.jsonl`. There was no `provider_error`
or `budget_stop`. The 7 problems without a row will be completed in a resume run, following the
same procedure as for C5 (same frozen configuration, seed and cache; input file with exactly the
needed lines of `data/eval.jsonl`; `imo_1987_p6` run from scratch, its earlier spend counted in the
project's real cost but in no kept row).
The resume run is `20261107T200233Z_C1_experiment_resume` (commit `2c8eae5`, clean tree; input
sha256 `009bece56dc0fdde3c814c9e81aeda6ee7bd8dd677d0fa29bab5dd723c907b8b`, the 7 problems in
dataset order; `--run-usd 1.65` = 3.50 minus the 1.8407 USD C1 had spent, rounded down, so that
C1 as a whole stays under 3.50).
- **2026-11-07, `dirty: true` in four `run_config.json` files (recording artefact, found in the
analysis).** `run_eval.py` rewrites `run_config.json` when a run ends, and the git state (and
`created_utc`) were read again at that moment instead of at launch. In
`20261103T212359Z_C5_experiment_resume`, `20261103T225140Z_C5_experiment_resume2`,
`20261103T235556Z_C4_experiment` and `20261107T200233Z_C1_experiment_resume` the only changed file
is `docs/analysis_plan.md`, edited while the run was going to log these very incidents; every
tree was clean at launch (checked before each run, as the Phase 8 rules require). No code,
prompt, configuration or data file changed, so the method is not affected. In those files
`created_utc` is also the end time; the start is the timestamp in the run name and the
`run_start` event of `trace.jsonl`. The two interrupted runs (`20261103T174646Z_C5_experiment`,
`20261105T131443Z_C1_experiment`) never reached the final rewrite and show their launch state.
Fixed after all `test` runs (`run_config.run_start_info`): `git` and `created_utc` now describe
the launch, and the state when the file is written goes to `git_end` and `written_utc`. Runs
already made are not rewritten.
- **2026-11-08, spend caps raised for the exploratory C5-Pro run (spend control).** Team decision,
after all confirmatory `test` runs. Only `config/pipeline.yaml → spend` changed: `total_usd`
9.0 → 12.5 and `run_usd` 3.5 → 5.0. The ledger stood at 7.36 USD; re-pricing C5-Flash's tokens on
the 10 hard `test` problems at Pro prices gives about 3.75 USD off-peak (7.49 USD at peak), so the
old caps would stop the run. As before, the caps are checked before each paid request and only
decide whether it is sent; no prompt, model of the main profiles, budget `B_max`/`b_max`, policy
or result changes, and no confirmatory run is repeated. The run uses `--run-usd 5.00`. The
offline test that pins the configured caps
(`tests/test_spend.py::test_real_pipeline_yaml_has_the_caps`) was updated to the new values.
- **2026-11-08, exploratory C5-Pro run stopped by the team (operational incident).** Run
`20261108T130803Z_C5_experiment_pro_pro_hard` (commit `8f72b02`, clean tree, profile
`experiment_pro`, `--run-usd 5.00`; input sha256 `acfd3825…26cf7a66`, the 10 hard `test`
problems in dataset order) was stopped on purpose at about 17:23 UTC, together with its whole
process tree (REPL, `lake`), because the team had to leave. No code, prompt, model or
configuration changed; all calls were off-peak. 5 of 10 problems finished and are kept
unchanged: `imo_1965_p2`, `imo_1993_p5`, `imo_1979_p1` (`failed`), `imo_1987_p6`
(`budget_exhausted`) and `imo_1978_p5` (`provider_error`: the Autoformalizer request on subgoal 1,
the problem's 30th call, got `APIConnectionError: Connection error.` with no HTTP response on all
5 transport attempts, 15:34:50–15:37:28 UTC). The 6th (`imo_1984_p6`) was cut mid-problem after 4
calls and left no row. The last 4 (`imo_1964_p1_2`, `putnam_1964_b2`, `putnam_1995_b1`,
`putnam_1965_b2`) were not attempted. The run is **not resumed** and the `provider_error` is
**not re-run**: the exploratory table reports the 5 finished rows, the cut problem and the 4
not attempted as such. Real cost 1.9407 USD (151 paid calls; 0.1725 USD of it on the cut
problem, in no kept row); ledger total after the run 9.3003 USD. `run_config.json` still shows
`status: running` because the run never reached its final rewrite.
Clarifications written before any run on `test`:
- **2026-11-03, FPR_B check (§4.2), how "the Autoformalizer–Verifier loop with the same budget" is
implemented** (`scripts/run_baseline_b.py --check`, `baselines/baseline_b.check_claim`): each claimed
proof (B⁺) is the sketch of the Autoformalizer on the trivial plan (the whole theorem as the only
subgoal), with the Retriever's lemmas as in C5; up to `critic.k_max` = 3 attempts, each failure
classified by κ and followed by the Critic's deterministic feedback (no LLM advice, no Planner, no
Generator, no automation pre-pass, so no fixed tactic can stand in for the claimed proof); its own
budget of `B_max` = 40 calls, counted apart from C2's own calls (C2's efficiency figures exclude the
check; in practice the check makes at most k_max × 2 = 6 calls: one Autoformalizer call and at most
one JSON repair per attempt). Acc is Eq. 2 on the assembled theorem. FPR_B = not_verified /
(verified + not_verified), where `budget_exhausted` counts as not_verified (the budget is part of the
method). A check that ended in `provider_error`, `budget_stop` or `error`, or a row marked `lean_down`,
is left out of FPR_B, counted apart and re-run once like a `provider_error` (§5); if it fails again
the same way it is reported as not checked. Problems C2 did not claim are not checked.