Implements the external auditor for ADR-0114a Obligation #6: "depth_curve.py produces a per-bucket curve; accuracy(N) >= accuracy(depth_1) * (1 - eps)^(N - 1) for eps = 0.05." Mirrors PR #189's auditor pattern (re-runs lane via the candidate- graph pipeline, aggregates over committed cases, emits deterministic report). Uses len(trace.steps) as the authoritative depth — the engine's actually-executed reasoning, not the case's declared depth. New module core/capability/depth_curve.py: - Bucket schema mirrors ADR-0119.6: depth_1, depth_2-3, depth_4-5, depth_6-8. Depth > 8 raises rather than silently extending. Depth == 0 (initial-only problems) skipped — nothing to decay. - representative_depth = min(bucket) — most permissive bound convention; tightening requires an ADR amendment. - epsilon = 0.05 pinned per ADR-0120 §Threshold rationale. - Two-axis verdict: obligation_6_mechanism_wired (always true if auditor ran), obligation_6_assertion_holds (every populated bucket satisfies the decay bound), coverage_sufficient (>=2 buckets populated AND >=3 cases each — required for the assertion to be statistically meaningful). CLI: core capability depth-curve (added to core/cli.py). Writes evals/obligation_6_depth_curve/<lane_id>.json. Empirical verdict on current main: lane: B3_bounded_grammar cases_total: 50 cases_solved: 22 mechanism_wired: True assertion_holds: True coverage_sufficient: False populated: [depth_1 (21/21=1.0000), depth_2-3 (1/1=1.0000)] Both populated buckets satisfy the decay bound. Coverage gap is honestly named in the refusal_reason: depth_2-3 has only 1 case, depth_4-5 and depth_6-8 have none. This is B3-owner work (case authoring under the existing grammar contract), not auditor work; reserved as a B3 v1.1 follow-up PR. Honest scope-limit: B3 only. B1 (algebra, no trace) and B2 (chain validation, not problem-solving) need different metrics — separate sub-ADRs. Trust boundary: read-only access to B3 cases + transitive pack reads via the pipeline; single deterministic write to artifact path. Tests: 24/24 covering bucket schema closure (depth 1..8 + raise on 9+), decay bound math (epsilon pinned, formula correct, depth_1 has no bound), coverage-sufficient policy (thresholds pinned), lane evaluation (passes on real B3 + refuses on missing cases), coverage-sufficient distinction (B3 today vs synthetic 5+5 fixture showing both pass), determinism (report identical + artifact byte-equal).
7.4 KiB
ADR-0114a.6 — Compositional-Depth Curve Auditor (Obligation #6 wired for B3)
Status: Accepted (mechanism); coverage gap deferred to B3-owner follow-up
Date: 2026-05-23
Author: CORE main agent (Opus 4.7)
Depends on: ADR-0114a (10 anti-overfitting obligations),
ADR-0119.6 (GSM8K depth-curve substrate — pattern source),
ADR-0131.3 (B3 bounded grammar lane),
ADR-0114a.10 (PR #189 — first obligation auditor; pattern this PR mirrors)
Parent: ADR-0114a
Sequencing: second of 5 remaining ADR-0114a obligations for
mathematics_logic. After this: #2 OOD ratio, #5 perturbation
(in flight, L14 / Opus#2), #8 adversarial.
Context
ADR-0114a Obligation #6:
depth_curve.pyproduces a per-bucket curve;accuracy(N) ≥ accuracy(depth_1) · (1 − ε)^(N − 1)for ε = 0.05.
A pattern-matcher's accuracy decays sharply with reasoning depth; a deterministic reasoning system stays approximately flat. The obligation pins that decay rate at most 5% per depth-step from the depth-1 anchor.
ADR-0119.6 shipped this measurement harness for GSM8K (lives at
evals/gsm8k_math/scoring/depth_curve.py). This PR ports the
pattern to the math composite gate's B3 lane.
Decision
core/capability/depth_curve.py — pure auditor. For each B3
case marked expected: solved_correct:
- Re-run the candidate-graph pipeline.
- Bucket by
len(trace.steps)(authoritative depth = the number of operations the solver actually executed). - Aggregate per-bucket accuracy.
- Check each populated bucket (≥1 case) against the decay bound anchored on depth_1's accuracy.
Bucket schema (closed, mirrors ADR-0119.6)
depth_1, depth_2-3, depth_4-5, depth_6-8. Depth > 8
raises DepthCurveError rather than silently extending the
schema. Depth == 0 (initial-only problems) is counted as
"skipped" — zero-depth has no reasoning to decay.
Representative depth convention
representative_depth(bucket) = min(bucket) — most permissive,
gives the bound the best chance of holding even when only the
shallow end of the bucket is populated. Tightening (e.g.,
max-depth-in-bucket) requires an ADR amendment.
Verdict shape — two orthogonal facts
The report distinguishes mechanism wiring from assertion meaningfulness:
| Field | Meaning |
|---|---|
obligation_6_mechanism_wired |
The auditor exists and ran end-to-end. |
obligation_6_assertion_holds |
Every populated bucket satisfies the decay bound. (Vacuously true when only depth_1 is populated.) |
coverage_sufficient |
≥2 buckets populated AND ≥3 cases per populated bucket. Required for the assertion to be statistically meaningful. |
CLI exits 0 iff obligation_6_assertion_holds. coverage_sufficient
flows as honest disclosure in the refusal_reason when the
assertion holds only vacuously.
Empirical verdict on current main
$ python3 -m core.cli capability depth-curve
lane: B3_bounded_grammar
cases_total: 50
cases_solved: 22
epsilon: 0.05
mechanism_wired: True
assertion_holds: True
coverage_sufficient: False
populated_buckets: ['depth_1', 'depth_2-3']
bucket total correct accuracy bound satisfied
depth_1 21 21 1.0000 (anchor) True
depth_2-3 1 1 1.0000 0.9500 True
depth_4-5 0 0 0.0000 (anchor) True
depth_6-8 0 0 0.0000 (anchor) True
refusal_reason: assertion holds but coverage insufficient — populated
buckets: ['depth_1', 'depth_2-3'] (need ≥2 buckets, ≥3 cases each)
Both populated buckets satisfy the decay bound (depth_1 anchors at 100%; depth_2-3 at 100% > 0.95 bound). The mechanism is wired correctly. But coverage is insufficient — depth_2-3 has only 1 case, depth_4-5 and depth_6-8 have none.
Honest scope-limit + named follow-up
For obligation #6 to meaningfully gate the full ADR-0120 expert promotion, B3 v1's case set needs more depth coverage:
- ≥3 cases at depth_2-3 (currently 1)
- ≥3 cases at depth_4-5 (currently 0)
- Ideally ≥3 cases at depth_6-8 (currently 0)
This is B3-owner work, not auditor work. The case authoring is
governed by ADR-0131.3's grammar contract; depth-4+ problems are
admissible by B3's grammar (sequence of additive/subtractive ops
with consistent units) but the v1 case set leaned heavily on
single-op problems for the lane-gate's correct_rate ≥ 0.95 target.
Reserved follow-up: a small B3 v1.1 PR that authors additional
multi-step cases under B3's existing grammar. After it lands,
coverage_sufficient flips to True and obligation #6 becomes
materially gating.
What this does NOT do
- Does NOT change the solver, the parser, or any B-lane runner.
- Does NOT modify B3's case set. (The case authoring is reserved follow-up, scoped to B3's owner.)
- Does NOT promote
mathematics_logictoexpert. - Does NOT wire B1 or B2 equivalents:
- B1 (symbolic equivalence): algebra-not-arithmetic, no solver trace, no depth notion. Different metric needed.
- B2 (teaching corpus): chain-validation surface, not problem-solving. Different metric needed.
- Both deferred to separate sub-ADRs.
Trust boundary
- Reads only:
evals/math_bounded_grammar/v1/cases.jsonl- The pipeline's own pack reads (transitively via
parse_and_solve→solve)
- Writes only: artifact path (default
evals/obligation_6_depth_curve/<lane_id>.json) - No dynamic imports, no shell passthrough, no network.
- Pure deterministic function — verified by
test_report_is_deterministicandtest_artifact_emission_byte_equal.
Tests
tests/test_adr_0114a_6_depth_curve.py — 24 tests:
| Group | Count | What it pins |
|---|---|---|
| bucket schema + classification | 12 | closed set; depth 1..8 maps correctly; depth ≥ 9 raises |
| decay bound math | 5 | ε = 0.05 pinned; bound = anchor · 0.95^(N-1); representative_depth = min(bucket); depth_1 has no bound |
| coverage policy | 1 | thresholds pinned (≥2 buckets, ≥3 cases each) |
| lane evaluation | 3 | passes on real B3; populates depth_1; refuses on missing cases |
| coverage-sufficient distinction | 2 | B3 today is coverage-insufficient with assertion-holds; synthetic 5+5 fixture is both |
| determinism | 2 | report identical across calls; artifact byte-equal |
All pass in 0.28s.
Composition with ADR-0131.4 and obligation #10
Orthogonal. The composite math gate (ADR-0131.4 / PR #188 / merged)
gates the benchmark portion of the contract. Obligation #10
(PR #189) audits pack provenance. Obligation #6 (this PR) audits
reasoning-depth decay. Each is its own evaluator; a future full
ADR-0120 wire-up composes them into a single signed expert_claims
artifact for ledger promotion.
CLAUDE.md PR-checklist
- Capability added: depth-curve auditor with honest two-axis verdict (mechanism wired + assertion holds vs. coverage sufficient) for B3.
- Invariant proving field validity: decay bound holds on every populated bucket; ε = 0.05 pinned; representative_depth convention pinned.
- CLI/eval proving the lane:
python3 -m core.cli capability depth-curve+pytest tests/test_adr_0114a_6_depth_curve.py. - Avoided hidden normalization / stochastic / approximate / unreviewed mutation: Yes. Pure deterministic auditor.
- Trust boundary: read-only inputs from documented paths; single deterministic write; no dynamic imports.