core/docs/adr/ADR-0131.G.0-probe-substrate.md
Shay 54e6bfc0d0
docs: reorganize docs landscape
Implements the 4-phase documentation reorganization master plan.

- Consolidation: Merged brief/, handoff/, planning/, and decisions/ into briefs/, handoffs/, plans/, and adr/ respectively (101 ADRs relocated)
- Root Cleanup: Relocated HANDOFF-gpt55-*.md and key top-level docs (runtime_contracts.md, etc.) to canonical folders. Added superseded alerts.
- Indices & Navigation: Created docs/README.md navigation document, docs/sessions/README.md index, docs/adr/README.md index
- Note: Also includes prior commit adding ADR-0200+ corpus hygiene governance (ADR-0225, dependency map, backfilled cross-references)
2026-06-30 16:59:36 -07:00

7.3 KiB

ADR-0131.G.0 — Probe Substrate: Candidate-Graph Pipeline

Status: Proposed Date: 2026-05-23 Author: CORE main agent (Opus 4.7) Depends on: ADR-0126 (candidate-graph parser), ADR-0131.G (GSM8K coverage probe + iteration discipline) Parent: ADR-0131.G Sibling iterations (in flight): ADR-0131.G.1 (verbs), ADR-0131.G.2 (comparatives), ADR-0131.G.3 (numerics), ADR-0131.G.4 (multi-clause)


Context

ADR-0131.G (PR #181) pinned a GSM8K coverage probe as a diff-able admission number with capability-first iteration discipline. Each subsequent ADR-0131.G. extends a single capability axis of the parser layer and re-runs the probe, with the gate that admission_rate strictly increases (or a refused-reason family deliberately shrinks).

But the probe currently consults evals.gsm8k_math.runner.run_lane, which scores each case via _score_oneparse_problem — the legacy first-match-wins parser that pre-dates ADR-0126. Every ADR-0131.G. iteration extends the candidate-graph parser (ADR-0126 topology), via _score_one_candidate_graphparse_and_solve. The probe and the iteration work measure two different parsers.

The mismatch was discovered during ADR-0131.G.3 development: the G.3 axis lane (which routes through the candidate-graph pipeline via _score_one_candidate_graph) showed full end-to-end correctness on 20 curated money + hyphenated-cardinal cases, while the GSM8K probe (legacy substrate) showed zero admission delta. The G.3 ADR documented the limit honestly and reserved this follow-up.

Decision

Switch evals/gsm8k_math/train_sample/v1/run_coverage_probe.py to call _score_one_candidate_graph instead of run_lane. The probe now measures the parser layer the iteration discipline extends.

Mechanically, the probe builds its own thin _score_lane aggregator that mirrors run_lane's shape (cases_total / correct / wrong / refused / rates / wrong_count_is_zero) but invokes _score_one_candidate_graph per case. No changes to runner.py itself.

Report schema gains one field — "substrate": "candidate_graph" — so the audit trail records which pipeline produced the numbers. Every other field (metrics, refused_reasons_top, per_case) keeps its shape; the 8 contract tests in tests/test_adr_0131_G_gsm8k_coverage_probe.py pass without modification.

Why this is a separate (small) PR

ADR-0131.G's discipline explicitly forbids bundling probe-infra changes into capability-axis PRs ("Reject any expansion that only moves GSM8K admission and not B3 / curated coverage — that's the smell test for a template in disguise"). The mirror image applies here: a substrate swap that moves admission numbers must be a standalone PR so the delta is attributable to the substrate change, not silently absorbed into a capability iteration.

Today the substrate swap is zero-delta — both _score_one (legacy) and _score_one_candidate_graph produce 0/50 admission on the current main baseline. That makes this the cleanest-possible substrate PR: behavior unchanged, audit trail shifted, future iterations attributable.

What changed in code

evals/gsm8k_math/train_sample/v1/run_coverage_probe.py

  • Import switched: run_lane_score_one_candidate_graph (and the existing LaneReport no longer needed at the probe layer).
  • New private _score_lane aggregator mirrors run_lane's output shape via _score_one_candidate_graph per case.
  • build_report adds "substrate": "candidate_graph" to the report root.
  • Docstring updated to document the substrate choice.

evals/gsm8k_math/train_sample/v1/train_sample_coverage_report.json

Regenerated. Top-level "substrate" field added. All metrics byte-identical to the prior baseline (both substrates produce the same 0/50 numbers today). refused_reasons_top text changes because the candidate-graph pipeline emits slightly different refusal strings (prefix candidate_graph: instead of parser:); this is expected and is part of the substrate audit-trail shift.

No changes to

  • evals/gsm8k_math/runner.pyrun_lane, _score_one, _score_one_candidate_graph all unchanged.
  • tests/test_adr_0131_G_gsm8k_coverage_probe.py — the 8 contract tests pin invariants, not specific numbers, and all pass after the substrate swap.
  • Any G. iteration's lane code or axis cases.

Evidence

  • Probe: python3 -m evals.gsm8k_math.train_sample.v1.run_coverage_probeadmission_rate: 0/50 = 0.0%, wrong: 0, safety_rail_intact: True, exit code 0.
  • Contract tests: 8/8 pass in 0.18s.
  • Substrate equivalence on main baseline: both pipelines produce 0/50 admission. The substrate swap is zero behavior delta today.

Effect on in-flight iterations

Iteration Effect of this PR
ADR-0131.G.1 (verbs, Gemini) After landing, probe admission will rise on cases the new verbs unlock (e.g., Nicole collected 400 Pokemon cards).
ADR-0131.G.2 (comparatives, PR #182) Refused-reason family for comparatives moves at the probe layer for the first time.
ADR-0131.G.3 (numerics, PR #183) Money + hyphenated cases the candidate-graph pipeline already admits start showing as probe admission (combined with G.1 unlocks Tina makes $18.00 an hour etc.).
ADR-0131.G.4 (multi-clause, Opus#2) Conjoined-subject cases like Aaron and his brother Carson each saved up $40 become probe-admittable (combined with G.3 for the money literal).

Reconciliation order after merge: each G. PR rebases onto main after this PR lands, runs the probe, refreshes its committed train_sample_coverage_report.json. The refreshed report becomes the iteration's load-bearing evidence on the probe, complementing the axis lane.

What this does NOT do

  • Does NOT change the legacy parser path. parse_problem and _score_one remain available for other lanes that depend on them.
  • Does NOT change the GSM8K case set, the sample, or the answer vocabulary.
  • Does NOT change the GSM8K probe's role: it remains a probe (honest disclosure of what the architecture can/can't admit), not a gate for the math-expert promotion (that's still B1+B2+B3 per ADR-0131).
  • Does NOT modify any G. capability work in flight. Each rebase is mechanical (one merged report file refresh).
  • Does NOT introduce stochastic behavior, opaque fallbacks, or approximate recall anywhere in the probe.

CLAUDE.md PR-checklist answers

  • Capability / performance / security added or protected: Aligns the probe's measurement substrate with the parser layer the iteration discipline extends. Audit-trail correctness for every future capability iteration.
  • Invariant proving the field remains valid: admitted_wrong == 0 preserved on the probe; safety_rail_intact: True; 8/8 contract tests pass.
  • CLI/eval lane proving the lane: python3 -m evals.gsm8k_math.train_sample.v1.run_coverage_probe and pytest tests/test_adr_0131_G_gsm8k_coverage_probe.py.
  • Avoided hidden normalization / stochastic / approximate / unreviewed mutation: Yes. Pure deterministic substrate swap, same case set, same scoring rules, different parser entry point.
  • Trust boundary: No new user-input surface, no new filesystem paths, no new dynamic imports. The probe still reads only cases.jsonl and writes only train_sample_coverage_report.json under evals/gsm8k_math/train_sample/v1/.