Narrow product promotion boundary (`generate/derivation/product_bridge.py`)
wired into `generate/math_candidate_graph.py`: only complete pure-product
derivations with a product-target question and no known hazard surface lift
from the sealed pooled derivation reader into serving.
- Serving train_sample: 4/46/0 → 6/44/0, wrong=0; case 0050 still refused.
- Renumbered from the collided ADR-0194 (labeled-container, #499) to ADR-0195
and rebased onto current main.
CI: smoke + verify-pinned-lane-SHAs green on the merge commit.
GSM8K labels containers/regions with a trailing single-letter or short-numeric
label ('Jar A has 28 marbles', 'Section G has 10 cars', 'District 2 has 19
voters'); the initial-possession entity slot captured only 'Jar' and the label
broke the match. Adds a separate sibling pattern _INITIAL_HAS_LABELED_RE
(mirroring ADR-0136.S.4 localisation) that REQUIRES the label, so the global
_ENTITY is unchanged and bare subjects yield no duplicate candidate.
- Composes with ADR-0193 aggregate question: 'Jar A has 28 marbles. Jar B has
12 marbles. How many marbles are there in total?' -> 40.0.
- 0 real-corpus metric flip (honest substrate): the one real multi-container
aggregate additionally needs comparative + multiplicative + lowercase-ref.
- wrong=0 HOLDS full corpus (7,473 q); train_sample byte-identical 4/46/0;
synthetic-registry capability-axis gate + G5 lane green; smoke 67 passed.
- Label bounded by the possession verb: multi-word nouns ('Jar Apple') do NOT
match. wrong=0 held downstream by completeness + round-trip + disagreement.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Extends ADR-0131.G.5's total-across aggregate branch with the existential
verb frame 'How many <unit> are there <cue>?' over the SAME closed cue
vocabulary (no cue-set widening). The solver already sums entity=None
total-across; the wall was purely the question parser's verb-frame coverage.
- Composes end-to-end: 'Jamie has 28 marbles. Kyle has 12 marbles. How many
marbles are there in total?' -> 40.0 (load-bearing, not inert).
- 23 real GSM8K problems now parse the question (advance past the Q-wall to
the statement-parse wall).
- wrong=0 HOLDS on the full 7,473-q corpus; train_sample byte-identical
4/46/0; no metric delta (composition-wall lesson, third instance).
- Cross-ADR discipline: 'What is the total number of <unit>?' is DEFERRED,
not contradicted — ADR-0131.G.5 pins it as a refusal probe; promoting it
must amend that ADR's closed-cue contract. test_total_number_of_still_deferred
locks the boundary.
Firewall: completeness guard (ADR-0191) + question round-trip refuse conjoined
('dogs and cats') and derived ('animal legs') units. G5 lane + synthetic-registry
wrong=0 capability-axis gate green; smoke 67 passed.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The discrete_count matcher gated the counted noun on a CLOSED ratified set
(observed_counted_nouns): 'Betty has 24 marbles' matched, 'Randy has 60 mango
trees' / 'Sam has 12 red apples' did not — purely because the noun was unseen.
Open the single-anchor possession/acquisition path to an open noun phrase
(adjective* + 1-3 word head, bounded by a stop-word lookahead so it never
swallows a trailing PP), keeping every other narrowness layer (proper-noun
subject, verb whitelist, single numeric token, no clause-split). Closed
observed nouns still match (capitalized compounds preserved); compound
enumeration stays closed.
Safe because ADR-0191 moved the wrong=0 guarantee downstream: an open-vocab
mis-parse hits the completeness guard + round-trip + branch-disagreement.
Proof: full real corpus 61->494 discrete_count anchors (8x), wrong=0 HOLDS,
zero confabulations.
Substrate PR — 0 metric delta by design (train_sample byte-identical 4/46/0;
the problems still need composition downstream). Value: the foundation every
discrete_count flip consumes, and empirical proof open-vocab is firewall-safe.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(adr-0191): candidate-graph completeness guard — real-corpus wrong 5->0
The candidate-graph reader (serving) checked grounding + round-trip but had
no completeness obligation, so problems whose later clauses failed to parse
emitted a partial reading. Over the full 7,473-question real GSM8K train
split this confabulated 5 answers (wrong!=0) the 47-case train_sample hid;
2 were regressions from #488.
Add the missing admissibility leg (mirrors the derivation reader's verify.py):
every source quantity (all statements + question) must be consumed by the
chosen reading, else refuse. Refusal-only -> cannot create a wrong answer.
Number-sense is pack-authoritative (en_numerics_v1 parse_compound_cardinal +
lookup_multiplier + all 6 currency symbols) so it never disagrees with the
engine; aggregating initials expose consumed_value_tokens provenance.
Evidence: real-corpus wrong 5->0, correct held at 4; train_sample byte-
identical 4/46/0; G1-G5+S1+G3.1 green; smoke 67 passed; math_teaching_corpus
lane byte-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(adr-0191): committed full-corpus GSM8K microscope (standing wrong=0 + coverage instrument)
Promotes the throwaway tmp/ microscope that found the 5 confabulations into a
committed tool. Runs the canonical serving reader over any GSM8K corpus and
reports failures-first: correct/wrong/refused, every wrong answer by name,
refusal families, and the no-injection per-category coverage map that ranks
which injector to build next by real frequency.
Default corpus is the committed 47-case train_sample (always available);
--corpus path runs the full real split. This is the ADR-0191 follow-up: re-run
after every capability PR, not just train_sample — a flip is only real if it
does not widen the confabulation surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(adr-0189): comparative reading — anchor-verb widening + multi-word units
The candidate-graph comparative extractor (ADR-0131.G.2) read only has/have +
single-word units, so real-GSM8K comparatives ('Brooke does three times as many
jumping jacks as Sidney') didn't parse — a dark statement in 17 places blocking
15 of the 47 refused train_sample cases, despite the ADR-0123 solver already
supporting compare_additive/compare_multiplicative.
Widens the anchor-verb set (reusing legacy vetted lemmas + does/collected/
gained/studied…), EXCLUDING polarity-inverting verbs (lose/spend/give/sell/win)
to preserve wrong=0; admits 1-2 word units via the existing multi-word
_unit_grounds branch. Feeds the existing solver unchanged.
wrong=0 proven: G2_comparatives 29/29, G3 20/0, G4 32/32, train_sample 3/47/0
byte-identical; polarity-inverting verbs proven refused (failing-under-violation).
Chain composes correctly in isolation (146 -> 438). Flips 0 cases ALONE — every
comparative case needs a composing partner (aggregation / multi-word-noun
injection); this ships the component, not yet a flip.
- generate/math_candidate_parser.py: _comparison_anchor_verb widening + 1-2 word
unit slots in the two multiplicative comparative regexes.
- tests/test_adr_0131_G2a_*: 5 tests incl. polarity-inversion wrong=0 guards.
- docs/decisions/ADR-0189: gap, change, wrong=0 evidence, honest scope.
* feat(adr-0189a): first metric move 3/47/0 -> 4/46/0 (case 0024, comprehension-composed)
Case 0024 now SOLVES (answer 438) by composing three general comprehension
capabilities feeding the unchanged ADR-0123 solver:
1. day-of-week count enumeration: Sidney = 20+36+40+50 = 146
(_day_enumeration_candidates; derived sum grounds via first count token,
mirroring _embedded_quantifier; closed to the 7 day names)
2. comparative reading (ADR-0189): Brooke = 3 x Sidney
3. activity question 'How many <unit> did <Entity> <verb>?' (_Q_DID_RE)
Plus do/does/did added to the CandidateInitial anchor whitelist (production-
possession), admitted only via the closed day-enumeration shape.
wrong=0 PROVEN across every lane: all 8 capability axes wrong=0 (G2_comparatives
29/29, G3 20/0, G4 32/32, G5/S1/S3/S4 all pass), train_sample 4/46/0 wrong=0,
verify_lane_shas exit 0 (no pinned lane changed), generate_claims --check OK.
872 tests pass; new tests are failing-under-violation incl. wrong=0 guards
(non-day comma list not summed; polarity-inverting comparative verbs refused).
Re-baselined report.json + train_sample_coverage_report.json (latter also clears
pre-existing reason drift) + CLAIMS.md to the new 4/46/0 metric. Decode-not-guess:
0024 solved by READING its structure, not storing an answer. Remaining pre-existing
failures (G3 committed-report, telemetry) unrelated, fail on pristine main.
- generate/math_candidate_parser.py: day-enum extractor + _Q_DID_RE + does-anchor.
- tests/test_adr_0189a_day_enum_activity.py: 5 tests (incl. end-to-end 0024=438).
- docs/decisions/ADR-0189a + report.json/coverage/CLAIMS re-baseline.
* docs(adr-0186): sealed candidate-graph injector lane (resume ADR-0170 W2-W5 under ADR-0175 seal)
Topology audit found two disjoint GSM8K readers: the candidate-graph reader
owns the official 3/47/0 metric and already has divide/multiply/compare; its
wall is the recognizer->injection coverage gap (ADR-0170 W2-W5 backlog), not
arithmetic. The derivation reader (resolve_pooled) is a separate sealed organ
that cannot reach the goal without an unbuilt Phase-5 bridge.
ADR-0186 reconciles ADR-0170's injector roadmap with ADR-0175's serving seal:
develop W2-W5 injectors behind a default-off 'sealed' flag on inject_from_match,
measured on a new report_sealed.json, with frozen 3/47/0 byte-identical until a
reviewed Phase-5 promotion. wrong=0 gated on both paths.
Live-loop instrumentation found a schema-vs-extraction split: rate_with_currency
and temporal_aggregation matchers already extract full anchors (blocked on the
CandidateRate union / apply_rate primitive), while discrete_count/currency/
multiplicative are blocked on matcher extraction. And no refused case is one
injector away - every case is multi-statement, so the unit of measurable
progress is a target case's complete injector+composition set. Sequencing:
seal mechanism first, CandidateRate schema next, then a first complete
target-case unlock.
* feat(adr-0186): sealed candidate-graph injector lane mechanism (default-off)
Adds the seal mechanism for developing ADR-0170 W2-W5 injectors without
mutating the frozen serving metric:
- inject_from_match(match, sentence, *, sealed=False): when sealed=True,
consults the new _SEALED_INJECTORS table first; default-off never touches it.
- _SEALED_INJECTORS: empty at land (this PR ships the mechanism; the first
sealed capability is the CandidateRate schema, ADR-0186 §5.3).
- parse_and_solve(text, *, sealed=False): threads the flag to the per-statement
injection site. The seal is injector eligibility, not a forked reader.
wrong=0 guarantees (tests/test_adr_0186_sealed_injector_lane.py, failing-
under-violation): empty seal is a strict no-op; a registered sealed injector
admits ONLY under sealed=True and is invisible to the frozen path; train_sample
report stays 3/47/0. Verified: coverage probe 3/47/0 byte-identical, smoke +
architectural invariants green, 288 candidate-graph/recognizer tests pass (2
pre-existing failures unrelated, confirmed on unmodified main).
* docs(adr-0185): mark superseded by ADR-0186 (premise refuted by topology audit)
The topology audit proved ADR-0185's premise ('the engine cannot divide') is
true only of the derivation reader, which is disjoint from the candidate-graph
reader that owns the official 3/47/0 metric. The goal organ already divides;
its wall is injection coverage. ADR-0185 is retained as a record, not
implemented; cross-referenced from ADR-0186 §header.
* docs(adr-0184): scope the distinct-unit product rule — cut the product-of-all over-commit
The 47-refusal coverage diagnostic surfaced that the headline 3/47/0 (serving
recognizer) hides the sealed comprehension reader's real state: resolve_pooled over
the 50 real train_sample cases is 2 correct / 13 WRONG / 35 refused. The confuser
probe's wrong=0 was a misleading proxy — all 13 real wrongs are the whole-text
product-of-all, the unique complete candidate, committed unopposed.
Scopes the first lever, decided by MEASURING candidate refusal rules against the real
metric (correct up, wrong down on train_sample):
baseline 2 / 13 / 35
distinct-unit product (chosen) 2 / 8 / 40 <- cuts 5, zero coverage loss
product spans >1 clause 1 / 4 / 45 <- destroys correct 0003
drop all products 0 / 2 / 48
The distinct-unit rule: multiply/divide may compose DISTINCT units but a multiply
step whose operand repeats a non-empty unit already in the product (apples x apples,
cards x cards) is unit-incoherence -> refuse (unit^2 is never the answer). Empty-unit
operands exempt (0003 multiplies a blank-unit 0.75). Dimensional, not lexical
(ADR-0165-safe); refines verify.py clause 3 shared by self_verifies + classify.
Honest scope: 13->8, NOT 0. The remaining 8 are distinct-unit products in the wrong
shape (rate problems) = cue precision (ADR-0177 CP-2b), the next lever, NOT to be
faked with a per-case rule. Establishes the real scoreboard (resolve_pooled over
train_sample) and notes the ratification bridge (ADR-0175 Phase 5) as the separate
dependency for any of this to reach the serving headline.
Spec only; serving 3/47/0 untouched (verify is not on the serving path).
* feat(adr-0184): distinct-unit product rule — sealed reader real-GSM8K wrong 13->8
Cuts the over-eager product-of-all on real GSM8K. The sealed comprehension reader
(resolve_pooled over train_sample) was 2 correct / 13 WRONG / 35 refused; all 13 are
the whole-text product-of-all committed unopposed (0042->2.4M, 0048->19200,
0001->14400). This is the first lever measured against the REAL metric (resolve_pooled
over train_sample), not the curated confuser count.
Mechanism (verify._is_repeated_unit_product + classify_derivation downgrade):
a pure multiplicative chain whose operands repeat a non-empty unit forms unit^2
(apples x apples, cards x cards) -- never the answer; it is the product-of-all
multiplying independent groups. Such a product is classified `exempt`
(commit-INELIGIBLE), NOT removed. Empty units exempt (0003 multiplies blank-unit
0.75); divide exempt (feet/feet = legitimate count). Dimensional, not lexical
(ADR-0165-safe).
Implementation finding (folded into ADR §3.1): the naive version put the predicate
in the shared _base_reasons gate, which DROPPED the product and regressed the
confuser probe 1->3 -- the disguised-polarity 0001/0003 refuse only because the
coins x coins product DISAGREES with the coins + coins accumulation reading; dropping
it unmasked the additive reading as a unique wrong commit (80/30). The fix is the
downgrade: keep it as a commit-ineligible `exempt` candidate so it still forces the
disagreement. Pinned by test_downgrade_not_removal_preserves_disagreement_refusal.
Evidence (sealed lane; chat/ does not import verify -> serving frozen):
- resolve_pooled over train_sample: 2 correct / 8 wrong / 40 refused (was 2/13/35);
the 5 repeated-unit products (0001/0017/0042/0045/0048) now refuse, 0003/0021 kept.
- confuser probe: wrong unchanged (no 0001/0003 regression), positives still solve.
- serving train_sample 3/47/0 and practice (accumulation + search) 3/47/0
byte-identical; self_verifies/_base_reasons unchanged so search lanes are untouched.
- 171 derivation/pool/verify tests + 40 architectural invariants green.
Honest scope: 13->8, NOT 0. The remaining 8 (0011/0016/0018/0019/0025/0028/0032/0047)
are distinct-unit products in the wrong shape (rate problems) = cue precision
(ADR-0177 CP-2b), the next lever -- not to be faked with a per-case rule. Carries the
corrected ADR-0184 (supersedes the spec-only #484).
The lookback review of the four-PR stack (EX-6/pooling/prior-state/anchor-skip)
found the ADR-0182 spec under-predicted and under-scoped 0016. Records actuals
without editing the original spec (retained for provenance):
- Status -> Accepted/Implemented (PRs #476, #480, #481).
- §8 Realized results: confuser wrong spec-predicted 5->4->3, ACTUAL 5->2->1->0.
Pooling also caught disguised-polarity (bonus); 0016 needed anchor-skip PLUS
intra-clause accumulation (a conjunction split the spec framed as anchor-skip
alone) and yielded the bonus 0017 *solve*; the prior-state guard (#480) is a
distinct question-time lever sharing resolve_pooled.
- Folds in the lookback findings: solid items, the closed test-coverage gap
(anchor-skip refuse branches, #481 4205605), no live wrong=0 hazard (multi-actor
product-commit predates anchor-skip), and the open 0010 multi-referent spurious
(a graduation question, not a wrong).
Serving 3/47/0 byte-identical throughout. Doc only.
Regenerated the docs/decisions/README.md ## Index table from each ADR's own
title + Status line, naturally sorted by ADR id (handles 0114a, 0119.1,
0131.G.3.1, 0136.S2, etc.). The index had drifted to 51 of 229 ADRs; it now
lists every ADR through 0183. Only the Index table was replaced; the Current
frontier / chain notes / reasoning-capable-domains / session-log narrative is
untouched (it remains a curated, historically-scoped account, not the index).
Records the fork for getting words from audio WITHOUT a serving-time learned
model: (A) words-as-text, or (B) deterministic formant/phonetic decode + taught
vocabulary. Status: Proposed (stub) — deferred, not a committed design. Captures
the problem, the 0-param/decode-not-borrow/refuse-over-fabricate/reviewed-growth
constraints, and the open questions a full ADR must answer. Exists so the
serving path doesn't silently reach for Whisper. Cross-linked from
docs/audio_pipeline_overview.md §9.
A confuser's `expected: refuse` does not mean unanswerable. Every confuser carries
its true gold (`answer_numeric`) and is a solvable coverage target; `refuse` means
"the reader can't yet comprehend this category, so refusing is the honest outcome —
committing a WRONG value is the defect." Example: 0001 "buys a toy for 30 coins …
how many left?" is plain 50-30=20; it is refuse only because today's reader takes
`buys` as a gain.
Adds §2.1 (graduation protocol): when a general mechanism reads a category correctly
(validated on train_sample + the category, wrong=0 preserved), those cases graduate
refuse -> solve and committing their gold becomes a win. Reframes the `spurious`
verdict as "solved-before-graduation" (a graduation signal, with pair-consistency as
the genuine-reading-vs-surface-match discriminator), not automatically a defect.
Notes that no v1 category is degenerate, and how a truly-unanswerable case would be
labelled (answer_numeric: null + degenerate: true) so the two senses of "refuse"
stay distinguishable.
Spec only; no corpus/runner change (today's commits are wrong-reading, so spurious
stays flagged until a category genuinely graduates).
The confuser probe's two distractor-quantity wrongs (0014 ->300, 0016 ->3840)
have no clean tight fix: the `for` cue licenses both the 0014 distractor product
and the correct train-0021 product, and a target+foreign-unit refusal rule breaks
the canonical train-0003 boxes x erasers product. Telling a real multiplier from a
distractor by cue is the cue-precision problem ADR-0177 measured as not-yet-solvable
-- a tight rule here would be a reactive surface patch (the overfitting trap).
This ADR scopes the general, non-reactive alternative: let the wrong=0 DISAGREEMENT
rule do the refusing. Pool self-verifying candidates across composers so a blunt
product reading and a competing additive reading of the same problem disagree ->
refuse; legitimate products (0021/0003) have no competing reading, so they still
commit -- the structural property that distinguishes them, expressed without a cue.
Scoping surfaced two things a one-line plan would have missed:
- Completeness FORCES the distractor into every reading, so naive pooling changes
nothing. The mechanism needs a narrow, commit-INELIGIBLE completeness exemption
for isolated-foreign quantities -- it can only buy a refusal, never an answer, so
the commit-path completeness guarantee (ADR-0175's 9->2 multi-step fix) is intact.
- MS-1 Target exposes the union of body unit-shapes, NOT the question's asked-for
unit (verified). The exemption is therefore keyed to a reading's used-operand
units, not an asked unit that does not exist.
- 0016's distractor sits in the anchor-position clause, so it needs distractor-aware
anchor selection too: 0014 is the guaranteed win (wrong 5->4); 0016 lands (->3)
only when anchor-skip is built. Doc declines to claim both before that is proven.
Spec only, no code. Sealed lane; serving 3/47/0 untouched.
§1.5.3: content-addressed re-sort obligation is vacuous at compute_trace_hash
(folds vault_hits count, not contents); amend to apply at recall() result set
+ any future contents-bearing hash.
§2.2: Merge Kernel must content-address equal-score recall ties (multivector +
provenance bytes), not arrival/deque-index order — the general-path analog of
ADR-0181 §2.2's audio merge key.
Both sharpen the substrate ahead of the vault.rs LocalArena/SemilatticeDelta
refactor; neither blocks it. Sourced: docs/audit/ADR-0180-t1-t4-findings.md.
Flip ADR-0173 Proposed -> Accepted. This is the W0 gate for the
workbench-UI ratification wave: it amends ADR-0160's read-only stance
narrowly to admit operator-driven invocation of the three existing,
replay-gated Tier 1.5 handlers (apply_lexical_claim, apply_frame_claim,
apply_composition_claim) as a local keyboard accelerator over ADR-0161
Surface C. No UI code; implementation remains gated to the W1..W4
acceptance gates in the ADR.
Also lands the reconciled W3 ratification-corridor brief. It supersedes
the stale W3 section of WORKBENCH-UI-WAVE-BRIEF-PACK.md, which assumed a
'frontend zero' state (W1/W2 already merged via #295/#299/#329/#327/
#328/#303) and prescribed a Zustand store this repo never adopted. The
reconciled brief names the load-bearing facts the old one glossed:
- /ratify is advisory-only today (routes, never applies); W3's core
backend task is the advisory->in-process flip.
- live proposals carry shape_category='uncategorized', so the safe
category/polarity must be operator-supplied and allowlist-gated, not
hard-coded -- the exact silent-wrong-category path case 0050 guards.
Verification this session: read-only workbench v1 confirmed working
(API serves real deterministic data, honest 404s, traversal rejected,
ratify advisory/no-mutation; 31 python + 104 UI tests green; pnpm build
green).
No runtime/algebra/eval paths touched. Docs only.
Follow-on to ADR-0163 §F that corrects the metric exposed by the overfitting
finding (96/150 synthetic flips vs 1/50 real; the 0002 cable/fraction problem read
as accumulation -> 996). Specs a small, hand-curated, real-sourced set of hard
negatives + near-miss confusers (disguised-polarity verbs, pseudo-accumulation
fractions, multi-referent/multi-actor, distractor quantities, temporal/question-
scope, comparative-referent, unit confusers) with minimal-pair construction.
Scored the opposite way from a coverage lane: success = wrong=0 on confusers +
pair-consistency + honest refusal frontier, NEVER solved-count. Held-out by
contract (not a training-to-fit target); the CP ledger consumes the wrong attempts
as the hard negatives the templated corpus lacked. Guardrails forbid reactive vocab
growth and template expansion.
Grounded scope for GB-3b, the next Gap B move. Measured opportunity: 46/150
additive practice cases are sum-of-all==gold, all currently refusing, all the
single-referent accumulation shape ('X has N. He buys M more.' -> N+M). They
refuse correctly (GB-3a's multi-clause guard / the Alice+Bob hazard); the safe
ones differ by referent identity + a change-verb cue, which GB-3b reads.
Scope covers: the reading (anchor + change steps), the wrong=0-critical referent
model (minimal no-new-actor guard; cross-references ADR-0164.2/.3 + ADR-0174's
multi-actor hazard; deliberately does NOT resurrect the retired gender-blind
resolver), the proof-carrying wrong=0 obligations, the cue-precision coupling
(change cues should finally be reliable, closing CP-2a), the increments, and
honest flip expectations (big chunk in practice-additive; modest train_sample;
serving stays 3/47/0 until ratification).
CP-2a populates the CP-1 ledger from gold-labelled candidate readings and reports
per-pattern reliability — the measurement the cue-precision thesis rests on. Plus
the function-word unit filter, whose value this measurement makes concrete (clean
unit_shape labelling).
What landed (all sealed; serving 3/47/0 byte-identical):
- generate/cue_precision/trainer.py — train_from_cases(cases, enumerators): folds
gold-labelled candidate chains into the ledger via record_case. Decoupled (the
candidate enumerators are injected, so the package still imports nothing from
search). candidates_for dedupes a reading shared by two enumerators.
- generate/derivation/multistep.py — extracted the enumeration half of search_chain
into public candidate_chains(problem_text); search_chain now delegates (verified
byte-identical: ms3 tests + practice counts unchanged). CP-2 needs the readings
the search weighs, not just the one it resolves.
- generate/derivation/extract.py — function-word unit filter (_NON_UNIT_WORDS):
blanks spurious function-word units ($0.75 each -> "", 3/4 of -> "") that
corrupt same-unit detection and unit_shape. Closed lexeme set, ADR-0165-safe.
- evals/gsm8k_math/practice/v1/cue_precision_report.py — trains over 200 sealed
cases (50 train_sample + 150 ADR-0163-F additive) with the real enumerators and
prints the per-pattern reliability table.
- tests/test_adr_0177_cp2a_training.py — trainer obligations (credit/dedupe/
determinism/empty) via synthetic enumerators; real-measurement well-formedness;
search_chain parity.
Load-bearing finding (recorded in ADR-0177): over 200 cases EVERY (cue,op,unit_shape)
pattern floors at ~0.0 reliability (best: for-multiply-cross_unit 0.0116 at 2/34).
The blunt product/sum-of-all readings are almost always wrong vs gold, so the
conservative floor correctly trusts nothing. => CP-2b (trust reliable cues) is
blocked on candidate GENERATION, not the ledger: candidate readings must get less
crude (clause/referent structure, ADR-0178 GB-3b) before any cue earns reliability.
Cue-precision and compositional structure are coupled; structure comes first.
Verification: 107 targeted tests green (CP-2a/CP-1/extract/ms3/GB-1/2/3/MS-1/2) +
architectural invariants; serving CLAIMS.md sha unchanged; practice 4/1/45 and
0/1/149 unchanged. Inert: trains/reports only, consulted by no search/gate.
Maps the AssetOverflow audio-compiler proposal onto the existing
sensorium ProjectionHead boundary (ADR-0013) and the Delta-CRDT
sharded substrate (ADR-0180).
Core mapping decision: the audio chunk boundary IS the CRDT delta
boundary. In-chunk rotor composition (compile_events) is the explicit
serialization barrier ADR-0180 §1.5.2 requires for non-commutative
versor_apply; the resulting AudioCompilationUnit is the order-invariant
delta written at the only semilattice-eligible layer (vault/store).
The compiler's checksum chain supplies the content-addressed merge key
(canonical, ir, projection sha256) that ADR-0180 §1.5.3 demands, making
idempotence structural and the sequential==concurrent trace-hash proof
checkable.
Adds:
- docs/decisions/ADR-0181-audio-compiler-delta-crdt.md (decision + mapping)
- docs/plans/audio-compiler-spec.md (typed AudioIR, operator table,
manifest, numeric determinism, delta interface)
- docs/plans/audio-compiler-eval-plan.md (corpus, gates, A-1..A-6 CRDT
proof obligations, teacher-migration policy)
Docs only; no runtime/core mutation. PR-2..PR-6 substrate work sequenced
in the ADR, with PR-5 gated on ADR-0180 §1.5.4 (T-1..T-4) green on main.
* docs(handoff): parallel-work plan post-GB-3a (CP-1 / scale / EX-3 tracks)
Three disjoint-file tracks dispatchable in parallel with Claude's serial Gap-B
line; records the hard constraint that GB-3b/4/5 are serial on compose.py.
* docs(adr-0180): propose CRDT-sharded vault concurrency substrate
Drafts ADR-0180 (Proposed) for a Delta-CRDT sharded substrate to support
forthcoming multimodal ingestion (vision, kinematics) without serializing
on a global Vault lock.
§1.5 grounds the proof obligation against the existing single-threaded
Python ingest path (sensorium → ingest/gate → field → vault/store →
compute_trace_hash) and enumerates four pre-refactor test obligations
(T-1..T-4) that must be green on main before any change to
core-rs/src/vault.rs lands, per CLAUDE.md work-sequencing item 5.
§1.5.5 explicitly fences out: approximate recall (exact CGA recall is
non-negotiable), hidden background execution (Merge Kernel must be
explicitly mounted with telemetry), and MLX/UMA hardware optimization
(deferred to a follow-up ADR; CPU-only Rust path lands first).
Proposed only — no code changes to core-rs, sensorium, or field.
The mandated lookback review before GB-3 (CLAUDE.md §Lookback Review Discipline)
confirmed the audit's hazards H1/H2/H3 were LIVE: compose_sequential summed
same-unit quantities from the whole problem, merging unrelated referents/scopes
and admitting wrong structures whose value happened to ground:
H1 (second actor's apples) -> 6+4+2 = 12
H2 (comparative on other actor) -> (6+4)*2 = 20
H3 (later depletion event) -> 6+4+3 = 13
Root cause is the audit's G1/D2 drift: GB-2a re-extracts from the whole text and
ignores GB-1's clause structure. The fix is the GB-3 increment — make the composer
clause-scoped (consume segment_clauses), refusing when the licensed structure spans
clauses, because this slice cannot model referents:
- quantities must live in exactly ONE clause (0 or >1 -> refuse);
- a comparative outside that clause -> refuse (unmodelled referent binding).
All three hazards now refuse; all 7 GB-2 single-clause structures preserved
(list-sum, three-item, sum-then-scale, and the mixed-unit/disagreement/too-few
refusals). tests/test_adr_0178_gb3_referent_guard.py would fail against the
pre-guard code (12/20/13), so the obligation is proven, not decorative.
Scope/safety:
- compose_sequential is sealed substrate, not wired to a scorer -> serving
byte-identical 3/47/0 (lane-SHA 8/8, generate_claims --check OK); practice
unchanged 4/1/45. No new test failures (2 pre-existing on main).
- ADR-0178 amended: GB-2 relabelled GB-2a (list slice, drift G1 recorded);
GB-3 split into GB-3a (this referent guard, landed) and GB-3b (constructive
cross-clause chaining, next).
The actual coverage lever (ADR-0177 established cue-precision is its gate+prune,
not the unlock). Gap B = which quantities group via which ops in what order.
The reframe that resolves the rich-search-vs-uniqueness tension: structure-from-
READING, not enumeration. The text encodes its own structure — every gold case
fits a sequential clause-by-clause read (0021 one clause 15x10x3; 0003 48 ->x24
->x0.75; 0024 sum ->x3; 0033 seq chain + branch). So Gap B is comprehension: read
the problem, build the derivation structure as you read, hold alternatives on
ambiguity, reevaluate on lookback. This IS the project's original 'word-to-word
with lookback + problem-solving throughout' articulation, and the synthesis of
reader (0174) + solver/gate (0176) + learning (0177) + packs.
Decision: a comprehension-guided sequential composer — clause-local bounded
sub-derivations combined across clauses via relational cues (per/each/of->multiply,
and-list->sum, comparative->scale, more/older->add, fewer/less->subtract), held-
hypothesis on ambiguity + eliminate/reevaluate (REPOINT ADR-0174's inert reader
substrate from parse to STRUCTURE — where it finally becomes load-bearing), scored
by the 0176 self-verification gate + uniqueness, guided by 0177 cue-precision.
Locality bounds the search; relational cues constrain the cross-clause op so
uniqueness can RESOLVE (not just refuse). Coverage rises only as far as the reading
constrains structure; irreducible ambiguity refuses (wrong=0).
wrong=0 obligations: per-step self-verification, irreducible-ambiguity-refuses,
completeness+uniqueness over the whole structure, no-spurious-structure, determinism,
sealed. Honest hard parts: relational-cue precision (co-dependent with 0177 +
data-starved), clause segmentation, referent binding, branch/DAG (0033 quantity
reuse, GB-5), scale. This is the comprehension core — largest remaining capability;
serving lift materialises here, incrementally. Sub-phases GB-1..GB-6.
The lever MS-1->MS-3 proved: learn which (cue->op) readings are reliable from
practice eliminations, closing ADR-0175's self-verification 'necessary-not-
sufficient' gap before Phase 5.
Mechanism: per-(cue, op, unit_shape) reliability ledger (reuses ADR-0175 ClassTally
+ conservative_floor), fed by gold-labelling search candidate chains. Three uses:
U1 self-verification TRUST (near-term value: makes the Phase 5 proposal gate honest;
cold ledger => refuse, no junk proposals), U2 search guidance, U3 disagreement
resolution (coverage lever, hard-gated: margin+theta, ties refuse).
Load-bearing honesty (the bottleneck): a pattern earns POSITIVE signal only from a
gold-MATCHING chain; current blunt shapes match gold for ~4/50 cases, so the ledger
is starved (all-blame) AND structure failures (Gap B) pollute cue->op credit (a
correct op penalised for a product-of-ALL structure error). Plus data starvation
(50 cases << N_min). So cue-precision is COUPLED to richer guided shapes (Gap B) +
scale; it co-evolves (Gap B supplies gold-matching candidates -> cue-precision earns
signal -> prunes Gap B's search). It is the TRUST substrate + pruning engine, NOT
the coverage unlock by itself.
Recommended sequencing: CP-1/CP-2 (mechanism + self-verification trust, near-term
correctness value) now; richer guided shapes (Gap B) next as the flip lever; scale
makes it compound. wrong=0 obligations: cold=>no regression, ties refuse, theta-gated
serving, credit-noise can't flip serving (floor+N_min+margin+ratification+gold-tether),
determinism. Sub-phases CP-1..CP-4.
The dominant remaining lever for serving lift (79% need mul, median 3 steps;
single-step search + completeness flips only 0021). Grounded in gold step
structures: derivations are CHAINS with intermediate results as operands;
quantities come from body AND question (0033's '25'); several need comparatives
(half/N-times).
Decision: bounded, deterministic, TARGET-GUIDED multi-step grounded derivation
search, gated by ADR-0175's strengthened self-verification (grounding ∧ cue ∧
unit ∧ completeness) + uniqueness + a new question-target match. Sealed practice.
Two new ideas beyond 0175: question-targeting (turn the question into a target =
search-pruning + stopping criterion) and multi-step chaining (intermediates as
derived operands). Sub-phases MS-1..MS-5, wrong=0-first (gate/target before broad
search). Invariant #2 extended to chains. Honest hard part: search explosion +
uniqueness refuses most -> target-pruning + cue-guidance + depth-bound are the
tractability levers; low coverage initially; comparatives pack is a prerequisite;
serving lift still waits on 0175 Phase 5 ratification. Reuses solver +
question extraction + round-trip primitives.
Solid: 4 invariants proven by failing-under-violation tests; 84 green; seal
verified. No live hazards (9 search wrongs are sealed eliminations).
Drift: (1) 3a gate is PARTIAL vs spec — round-trip + no-contradiction not wired;
3b's necessary-not-sufficient finding follows -> a self-verification-strengthening
phase must precede Phase 5. (2) class taxonomy: Phase 2 uses gold operation-class
not capability-axis. (3) minor defensive-branch test gaps (no risk).
Lookback/cross-reference audit before any code. No hard blockers. Corrects 3
overclaims of reuse and records the audit:
- reliability ledger is NEW (calibration/ is a grid-search param tuner, not a ledger)
- 0174 eliminate/reevaluate/contemplate is reading-coupled -> 'repoint to solving'
needs generalization, not drop-in reuse
- teaching corridor evidence (MathReaderRefusalEvidence) is reader-refusal-coupled
-> solver-practice proposals need a new evidence type
Constraints recorded: wrong=0 pinned on serving lane by ~25 tests + train_sample
runner -> practice MUST be a separate lane (phasing updated). Alignments noted:
ADR-0165 (lexemes-not-grammar) fulfilled by thin-front-end/thick-solver split;
INV-07 governance = no-self-authorization pre-wired; MAX_TOTAL_BRANCHES precedent
for bounded deterministic search; seal exists.
Resolves Open Question #1. conservative_floor(s,k) = one-sided Wilson lower
bound over COMMITTED trials (k=correct+wrong; refusals excluded so coverage
never penalizes reliability). Constants: z=2.576 (single global pessimism
knob), N_min=10. Range [0,1) — never returns exactly 1.0. float64 rounded
half-to-even to 1e-9 for cross-backend replay. z (estimator skepticism) and
per-class theta (action's required reliability, human-set) are independent
dials; engine touches neither. Worked cost-to-clear table + asymmetry example
included.
Proposed ADR + session derivation doc capturing the 2026-05-28 design
discussion that took GSM8K Phase 5b from 'build another matcher' to a
self-calibrating problem solver.
Session doc (docs/sessions/SESSION-2026-05-28-...): the full journey —
problem (per-shape matchers can't compound; 79% need mul, 0% single-step),
dead-ends (brute-force spurious matches; 0021 is the only single-sentence
case and it's idiosyncratic), and the four pivots that converged on the
solution.
ADR-0175 (Proposed): the decision —
- two regimes: serving (wrong=0, unchanged) vs sealed practice
(attempt-and-eliminate; wrong is the learning signal)
- proof-carrying seal: practice never writes serving; ratification only
- deterministic attempt/refuse gate: reliability(C) / theta_required >= 1
(NOT RL; regimes collapse the reward side so only reliability is quantified)
- per-class calibration ledger of replayable COUNTS + conservative lower
bound; human-set theta ceilings raised only on evidence
- checkability ladder (gold > convergent self-verification > consistency-only),
privilege proportional to reversibility; provenance + gold tether against
correlated self-delusion
- diagnostic refusal routes skill vs knowledge vs ambiguity; three
compounding stores (vault/packs/pruning); self-proving acquisition narrows
human input without bypassing the gate
- five proof-obligation invariants (wrong=0 on serving, no spurious banking,
determinism, no self-authorization, retractability)
Supersedes the matcher-oriented ADR-0174 5b sub-phases; repoints the
0174 eliminate/reevaluate/contemplate substrate from reading to solving.
Open question: shape of conservative_floor + N_min.
Grounded in a ground-truth measurement of the 47 train_sample refusals
against GSM8K's own <<a*b=c>> calculator annotations:
- 37/47 (79%) use multiplication; 43/47 use mul-or-div; 0/47 single-step
- multiplication is the foundational general capability (breadth = the
anti-overfitting signal), but necessary-not-sufficient: no case flips
from an operation matcher alone
Solver already supports {add,subtract,transfer,multiply,divide,apply_rate,
compare_additive,compare_multiplicative}; the gap is the reader->injector->
Operation front-end (matcher extracts 0 anchors on real sentences).
Sub-phase sequence (biggest-chunk-first, measure-the-flip-gated):
- 5b.1 single-sentence multiplicative aggregate (cleanest proof, ~2-4)
- 5b.2 shallow 2-3 step composition (25/47, the real chunk)
- 5b.3 deep multi-step 4-7 (22/47) under held-hypothesis elimination
Generality guard: flipped cases must hold under ADR-0114a perturbation/OOD.
Explicitly NOT widening discrete_count injector (overfitting + wrong=0 hazard).
The recognizer/candidate-graph path is the single canonical reader.
Retires the flag-gated incremental-reader dispatch that admitted 0/50 on
train_sample and only added a dead fall-through:
- remove _try_comprehension_reader, _try_reader_for_question, _tokenize_sentence
and both dispatch blocks from generate/math_candidate_graph.py
- delete generate/comprehension/lifecycle_runtime_adapter.py (402 LOC,
used only by the question-reader dispatch)
- drop the comprehension_reader_questions config flag and the parse_and_solve
/ _score_one_candidate_graph config threading
- remove the --use-reader runner plumbing + flag-ON/OFF delta report from
the train_sample runner; refresh report.json (drops stale use_reader field
and a stale refusal-reason; verdicts unchanged at 3/47/0)
- remove the now-dead use_reader field from teaching/coverage.py
CoverageReport + the core teaching coverage CLI flag
- delete tests/test_reader_coexistence.py (flag-ON/OFF premise dissolved);
fix 3 ADR-0174 build_report calls and 2 subprocess invocations
lifecycle.py and audit.py are KEPT — they are load-bearing for the ADR-0172
math-contemplation teaching corridor (audit_problem -> teaching/math_*),
which a pre-deletion trace surfaced. The parent ADR's plan to delete
lifecycle.py was wrong; only its GSM8K scoring dispatch was inert.
Net -1,038 LOC (code + tests). Behavior-preserving:
- train_sample 3/47/0, byte-identical verdicts to pre-5a baseline
- determinism holds; smoke/packs/runtime/cognition/teaching lanes green
- contemplation corridor + lifecycle/audit tests pass
Pre-existing (NOT introduced here; reproduce on base with changes stashed):
5 out-of-curated-lane stale committed-artifact / stale-assertion failures
(test_math_evidence_e2e, test_adr_0126_runner_wiring, G3/coverage_probe
report-match, test_refusal_taxonomy_lane rebuild).
Pre-implementation investigation (lookback discipline) found the original
Phase 5 text inverted against shipped code:
- math_parser.py already out of runtime + candidate-graph scoring path
- lifecycle.py admits 0/50 (inert parallel parser, not the reader to promote)
- correct>=25 is a semantic gate structural collapse cannot meet
Decision (Invert + split): recognizer/candidate-graph path is the canonical
reader; lifecycle.py is retired. Phase 5a = structural retirement (net -LOC,
3/47/0 byte-identical, wrong=0). Phase 5b = semantic narrowness removal (the
real lift, own sub-phases, per-layer wrong=0 obligations).
All findings from the 2026-05-28 Phase 1-3a lookback review addressed
in one commit on the Phase 3a branch:
Wrong=0 hazard defense (the load-bearing fix):
- generate/math_candidate_graph.py: Phase 3a wiring now collects the
set of distinct proper-noun subjects seen in prior context. When
more than one exists, refuses with no_antecedent_ambiguous trace
event rather than guessing the most-recent (which was gender-blind
single-binding — wrong attribution in multi-actor problems).
- Refusals from the statement loop now preserve _statement_trace via
reader_trace in CandidateGraphResult (pre-existing latent issue:
Phase 2/3 trace events were dropped on early statement refusal).
- New tests assert: ambiguous case refuses with correct trace; single-
actor case still resolves normally.
Test coverage backfills (closes the 13 untested predicate-name gaps):
- TestCheckConstraintsInitialPredicateNames — 3 tests asserting the
exact predicate name on initial.value_grounds / initial.unit_grounds
/ initial.entity_grounds failure paths.
- TestCheckConstraintsOperationPredicateNames — 3 tests asserting
operation.verb_grounds / operation.value_grounds / operation.unit_grounds
failure-predicate-name parity.
- TestCheckConstraintsComposedInitialPath — 4 tests for the RAT-1
composed_initial path which was entirely untested in Phase 2
(parity manually verified during lookback review; now automated).
ADR amendment (honest doc vs impl drift):
- docs/decisions/ADR-0174-held-hypothesis-comprehension.md: appended
'Implementation Notes' section documenting:
- reevaluate signature differs from spec text (shipped is more
composable; treat as amended)
- Phase 2 wires per-candidate, not per-token (per-token is Phase 5)
- Lookback recompute is candidate-level, not token-level
- Hypothesis.constraint_state is never populated by Phase 2
- Multi-actor pronoun hazard defense rationale
- Honest LOC accounting: Phases 1-3a net +1,500 lines (Phase 5
delivers the projected net removal)
- Test coverage backfill summary
Cosmetic:
- lookback.py:297 unreachable raise — added # type: ignore[unreachable]
with comment explaining defensive future-proofing for Phase 3b.
Acceptance verified:
- 124/124 Phase 1+2+3a + reader tests pass (was 95/95 before backfills)
- Smoke 67/67, packs 141/141
- train_sample 3/47/0 preserved (wrong=0 invariant held)
- Multi-actor hazard live-tested: parse_and_solve refuses the
Alice/Bob/She case with no_antecedent_ambiguous trace event
See CLAUDE.md §Lookback Review Discipline and memory
feedback-lookback-review-discipline for the doctrine that surfaced
all of these issues at the right time.
Extends ADR-0164's incremental comprehension reader from single-committed
state to held-hypothesis state, adding lookback re-evaluation and in-loop
contemplation. Diagnoses why the ADR-0164 reader is wired but inert
(all-or-nothing refusal at first unknown token / unexpected category).
Architecture: apply_word produces ProblemReadingState.open_hypotheses
(small ranked set, HYPOTHESIS_CAP=4 initial). Three operators per token:
EMIT (extend compatible hypotheses), ELIMINATE (constraint violations
remove hypotheses immediately), HOLD (uncommitted hypotheses survive at
lower confidence). At finalize(), |survivors|=0 refuses, |survivors|=1
admits, |survivors|>=2 invokes in-loop contemplate() over vault + packs
+ audit history. Ambiguity contemplation cannot resolve refuses cleanly,
preserving wrong=0.
Collapses three parallel parsing systems into one held-hypothesis
reader: removes regex parser runtime path (math_parser.py),
per-category injector dispatch table (recognizer_anchor_inject._INJECTORS),
and duplicate per-sentence-choices scaffolding. Net ~1,900 lines
removed; reader grows by ~600 for hypothesis state + reevaluate +
contemplate.
Preserves ADR-0164 lexicon and category set, ADR-0165 regex scope,
ADR-0150/0152/0155/0161 HITL corridor, binding graph + solver
substrate, capability-axis lanes, replay-equivalence gate. Trust
boundary for in-loop contemplation: read-only over vault/packs/audit;
ratification still rides offline HITL.
Status: Proposed. Six phases (no timelines) gated on wrong=0 and
capability-axis 100% at each transition. Five open questions resolved
before Phase 1 PR.
Phase 1 and Phase 2 of the ADR-0164 reader are correctly implemented but
contribute zero eval admissions today. The bottleneck is lexicon coverage
(unknown verbs/nouns) and explicit Phase 2.1 scope gates (fractions), not
the all-or-nothing dispatch policy.
Produces COMPREHENSION-READER-AUDIT.md answering the five Brief C questions:
call trace, cognition-lane usage (none), bottleneck analysis, ADR promise
audit, and three falsifiable options (operationalize/relabel/retire).
Recommendation: relabel (status update) now; lexicon expansion next as the
highest-leverage first step toward actual eval lift.
Also updates ADR-0164 status from "Proposed" to "Partially implemented" with
a Current Status table and next-lift-path summary.
W0 of the workbench-UI wave per WORKBENCH-UI-WAVE-SCOPING.md.
Pure docs PR; no UI code.
Decision: the workbench is a local keyboard accelerator for the
existing local-CLI ratification surface (ADR-0161 Surface C), not a
fourth ratification surface and not a new trust boundary. Every
workbench-driven ratification action invokes the same Python
entrypoint as the corresponding CLI invocation, with identical
preconditions, exceptions, and append-only JSONL effects.
Amends ADR-0160 v1 read-only stance narrowly: admits driving the
three Tier 1.5 handlers (apply_lexical_claim, apply_frame_claim,
apply_composition_claim) and the existing core teaching review path
through the workbench API. Honors ADR-0161's surface set unchanged
(workbench is part of Surface C, not a new surface). Honors ADR-0162
no-go list and pins the keyboard contract referenced in §7.
Five open questions from the scoping brief resolved:
Q1 in-process via existing Python entrypoints
Q2 single-operator (multi-operator deferred)
Q3 same chat/telemetry.py JSONL sink, new event kinds
(operator_ratify / operator_reject / operator_defer /
operator_navigate); no parallel log
Q4 fonts and icons bundled locally; no CDN
Q5 dist/ gitignored; CI verifies build, does not commit artifact
Ratification record extended with ratifier_kind: "workbench" — audit
forensic discriminant only, not a permission gate. proposal_id + to
remain the only load-bearing replay fields (ADR-0161 §5 unchanged).
Compatibility audit covers ADR-0146, 0150, 0152, 0160, 0161, 0162,
0167, 0168/0168.1, 0169/0169.1, 0172. Forbidden surface enumerated:
no remote operator, no CORS relaxation, no new mutation paths, no
auto-ratify, no batch ratification, no engine_state writes outside
the existing checkpoint path, no parallel workbench-events log, no
mobile/responsive form factors (ratification stays on Surfaces A+B
for mobile per ADR-0161).
Acceptance gates pinned per W1..W4 of the scoping brief.
* docs(ADR-0172): math-domain corpus-decomposition mechanism (Learning Arc analog)
Scoping ADR for the math-domain analog of cognition's
`teaching/contemplation.py` corpus-decomposition loop (Learning Arc
milestone 2026-05-25).
## What this ADR scopes
A mechanism that reads the math audit corpus and emits
`MathReaderRefusalShapeProposal` records — structural commonalities
across N refusal cases, paired with the candidate mechanism change
that would resolve them (matcher extension, injector sub-shape,
vocabulary addition, frame reclassification).
Today the operator does this decomposition by hand (reads
audit_brief_11.md, identifies the commonality across 21 DCS
refusals, scopes the matcher/injector extension, files a focused PR).
ADR-0172 shifts the decomposition to the engine, with HITL
ratification preserved.
## Sequencing — explicit
ADR-0172 ships AFTER ADR-0170 (injector contract widening),
ADR-0168 (FrameClaim handler), and ADR-0169 (CompositionClaim
handler — reserved). Without those substrates, the decomposer can
identify patterns but cannot route them to a ratification handler
that knows how to materialize them. Cognition's learning arc
followed this same sequencing: substrate first, then decomposer.
## Why this matters
ADR-0167 LexicalClaim shipped the math-domain wire from refusal →
evidence → operator-ratification. ADR-0172 closes the gap to the
engine-decomposes loop — the moment cognition's learning arc
qualitatively shifted from "engine refuses + operator authors" to
"engine teaches itself through reviewed correction."
The Learning Arc memory entry (2026-05-25) names that moment as
when measurable progress accelerated. ADR-0172 makes the math-domain
trajectory toward the same loop explicit in the queue.
## Hard invariants preserved
- wrong=0 by construction (proposals are evidence-only)
- ADR-0166: no new eval lanes
- No teaching-store / pack mutation
- No non-deterministic mechanism (rule-based grouping, not learned
classification)
- Cross-domain partition (ADR-0167 W2-C) preserves cognition
contemplation behavior
No code, no test, no eval, no pack change in this PR.
## Cross-references
- ADR-0056/0057 — cognition contemplation/proposal substrate (template)
- ADR-0167 + FOLLOWUPS §1 — parent evidence wire
- ADR-0168 + ADR-0168.1 — FrameClaim (ratification target)
- ADR-0169 (reserved) — CompositionClaim (ratification target)
- ADR-0170 — injector contract widening (substrate prerequisite)
- Memory: Learning Arc Milestone 2026-05-25 — the moment to recreate
- Thesis: decoding, not generating — the principle preserved
* amend(ADR-0172): add Tier 2 — intensional contemplation with test-and-learn loop
Per operator feedback during ADR-0172 review: the corpus-decomposition
mechanism should not only emit explicit rules (extensional) but also
develop inference (intensional) — recognizing structural equivalence
classes across surface variations without enumerating them.
## Tier 2 — intensional contemplation
Engine recognizes that 'Sam has 5 apples' and 'Sam collected 5 apples'
carry the same canonical proposition structure, without an explicit
verb-list extension. Emits MathReaderInferenceProposal records that
name structural equivalence classes rather than enumerable rules.
This is the thesis word the original draft missed: rationalization.
Tier 1 ratifies rules; Tier 2 ratifies inference.
## Test-and-learn loop
Tier 2 proposals carry held-out test evidence:
1. Decomposer surfaces hypothesis
2. Held-out subset of corpus reserved
3. Bridge applied to held-out cases; admissibility gates run
4. Outcome scored (positive / negative / neutral)
5. Negative-evidence proposals auto-rejected before HITL
6. Operator reviews proposal + test result, not bare claim
This makes Tier 2 thesis-coherent: engine decodes a structural
pattern, tests it against unseen corpus cases, surfaces the test
result. Wrong=0 cannot leak through — held-out test failures reject
internally.
## Updated implementation outline
Tier 1 wave: W1-W4 (schema, decomposer, CLI, workbench integration)
Tier 2 wave: W5-W9 (schema, equivalence-class recognizer, test-and-learn
loop, HITL integration, bridge application path)
## Hard invariants preserved at both tiers
- wrong=0 by construction (Tier 1: evidence-only proposals; Tier 2:
held-out test rejects wrong-admitting bridges internally)
- ADR-0166: no new eval lanes
- No non-deterministic mechanism (rule-based grouping + deterministic
test-and-learn, not learned classification)
- Cross-domain partition preserves cognition contemplation behavior
* amend(ADR-0172): split Tier 2 test-and-learn into two-arm confirmation
Per operator feedback during ADR-0172 review: 'confirm against known
facts/prior solutions' is the missing arm. The Tier 2 test-and-learn
loop now has BOTH:
- Arm 1 (negative / wrong=0 on held-out refusals) — already drafted
- Arm 2 (positive / known-good preservation) — NEW
Arm 2 inherits ADR-0057's replay-equivalence contract: any
inferential bridge that would change a currently-correct outcome is
REJECTED INTERNALLY before reaching HITL, even if the new outcome is
defensible. Existing truth survives; new truth is gated.
Both arms must PASS or be neutral. Either arm rejecting → proposal
does not reach the operator. This makes the engine's reasoning
provably conservative: it confirms against truth it already knows AND
truth it hasn't yet decided.
The 5-step proposal lifecycle is updated to reflect both arms +
test-set partition + per-case verdict tables in the emitted proposal.
No code change. No runtime effect.
* amend(ADR-0172): add foundational reasoning-articulation substrate
Per operator feedback: for the engine to infer/test/learn from
feedback, it must first be able to ARTICULATE its own reasoning in a
structured, persistent, replayable form.
Articulation is the project thesis's 5th anchor ("listen → comprehend
→ recall → think → articulate → learn from reviewed correction →
replay"). Today CORE articulates SURFACE (templated realizer output)
but does not articulate REASONING — the inference chain that took the
engine from refusal corpus to hypothesis to proposal.
Without reasoning-articulation, none of the three loops can work:
- Loop 1 (self-test) has nothing to record about what it tested or why
- Loop 2 (HITL review) sees a black-box conclusion, not inference chain
- Loop 3 (feedback) has no specific step the operator can target with
a rejection rationale
## Substrate: ReasoningTrace schema
Every proposal carries a typed, content-addressable
ReasoningTrace recording each inference step:
ReasoningStep:
step_kind: observation | grouping | abstraction | hypothesis |
test_design | test_application | test_result | conclusion
input_pointers: prior steps + evidence rows
claim: human-readable assertion at this step
justification: why the engine made the claim
output_payload: type-discriminated by step_kind
The trace is byte-identical across replays of the same corpus +
verdict history. Inherits CORE's existing determinism discipline.
## Sequencing
Articulation ships FIRST (new W0 wave) — it is the prerequisite for
Tier 1 and Tier 2 and Loop 3. Each downstream wave emits or consumes
ReasoningTraces.
## Hard invariants preserved
- Deterministic-replay (trace byte-identical under same inputs)
- ADR-0057 replay-equivalence (trace IDs stable across reruns)
- No non-determinism added (rule-based step emission, not learning)
- ADR-0166: no new eval lanes
No code, no test, no eval, no pack change in this PR.
DCS-S1 (proper-noun possession sub-shape expansion) investigation
revealed that the recognizer-injector path's `CandidateInitial`-only
return type is a substrate-level constraint blocking four Wave-Next
sub-shape categories — not just one.
## Two artifacts
1. **`docs/handoff/DCS-S1-FINDING.md`** — investigation result. Of
the 21 DCS-refused GSM8K cases, zero are pure S1-only blockers.
Acquisition-verb expansion (`collected`, etc.) conflicts with
ADR-0131.G.1's branch-disagreement discipline. The right fix is
the DCS injector emitting `CandidateOperation(add)`, but the
`inject_from_match` return type doesn't allow that.
2. **`docs/decisions/ADR-0170-injector-contract-widening.md`** —
scoping ADR. Names the contract change, the four categories it
unblocks (DCS-S1 acquisition, A1 currency, A3 multiplicative,
A4 temporal), the three load-bearing rules it must preserve
(ADR-0131.G.1, SentenceChoice union, admissibility gates), and
a 5-step implementation outline.
## Pattern recognised
Wave-Next surfaced four schema gaps. All four trace to the same
constraint: per-category injectors can only emit `CandidateInitial`.
The right next-capability work is ADR-0170 ratification, then a
small no-behavior-change PR widening the contract, then per-injector
follow-up PRs against the widened contract.
That is the actual lift-per-risk path for GSM8K Round-1 closure.
## Test plan
Docs-only. No code, no test, no eval, no pack change.
## Cross-references
- ADR-0163.D.2 — original parsed_anchors → solver-state ADR
- ADR-0131.G.1 — branch-disagreement discipline ADR-0170 preserves
- ADR-0167 — parallel teaching-corridor mechanism (independent)
- ADR-0167-FOLLOWUPS §7 — Wave-Next findings backlog
- WAVE-NEXT-REVISED — parent plan; ADR-0170 is the upstream blocker
- PR #369 — A2's schema-refusal artifact (first observation of gap)
Two docs-only updates capturing the day's work:
1. Appended a "Status update — 2026-05-27 EOD" footer to the Brief 11
handoff doc with the completion table (11A/11B-step-1/11B-step-2
docs+lexicon/11D merged; 11C absorbed into W3-A; 11D candidate E ADR
merged) and the current post-#348 baseline taxonomy.
2. New session doc SESSION-2026-05-27-adr-0167-parallel-dispatch.md
alongside the existing SESSION-2026-05-26-comprehension-reader.md.
Captures the architectural pivot (audit-as-teaching-evidence vs the
rejected refusal-class dispatch table), the parallel-dispatch
experiment (5 operators / 3 waves / 6 PRs), what worked, what
surfaced as load-bearing (case 0050 hazard), and what's deferred.
No code changes. No runtime effect.
* docs(ADR-0167): audit-as-teaching-evidence (math reader → contemplation wire)
Scoping ADR for Brief 11D Candidate E. Routes math-reader refusal audit
rows into the existing contemplation/HITL teaching corridor as a new
candidate source (`MathReaderRefusalEvidence`).
Key decisions:
- Evidence-only — never directly admits a math fact; only ratification
through HITL queue can change runtime behaviour
- Five sub-types proposed (Lexical / Frame / Composition / Reference /
Slot claims) mapping to the audit taxonomy
- Scope first to LexicalClaim — lowest-risk, highest-count
- Six open questions called out for the implementation ADR
ADR-0166 three-question test passes; implementation passes only when
the six open questions are answered with LexicalClaim-first scope.
No code in this PR.
* docs(ADR-0167): parallel work plan — 6-PR/3-wave dispatch across 5 model operators
Closes the Brief 11 sequence with a decision artifact (not a roadmap)
selecting the next capability after GSM8K Phase 2 reader closure.
Four candidates compared against ADR-0166's three-question test:
- A. Continued GSM8K operator closure
- B. Cross-domain reader generalization
- C. Tool-use trace integration
- D. Workbench demo hardening
Recommendation: continued GSM8K operator closure, starting with the
`lexicon_entry` row of the Brief 11B audit. The only candidate that
passes Q1/Q2/Q3 cleanly today and has an explicit Round-3 finish line.
Docs-only. No code, no test, no eval, no pack change.
Extend the comprehension reader from question-only scope to whole-
problem scope. Phase 1 (Brief 8 / #326) implemented question_frame;
this brief implements initial_state_frame, operation_frame, and
descriptive_frame, plus finalize() projection into a strict
ADR-0115 MathProblemGraph.
Architecturally correct under ADR-0164.3; not yet productive on
GSM8K train_sample. Below-floor measurement documented; specific
bottlenecks tabled for Phase 2.1 follow-up.
What landed
- Frame-opener dispatch in lifecycle.py for the three new statement
frames, plus rule handlers (_rule_op_*, _rule_preframe_*,
_rule_descriptive_*).
- finalize(state) -> MathProblemGraph | ReaderRefusal: pure
projection with closure checks (entity registry non-empty,
unknown target bound, every op/initial references a known entity,
Decimal precision projects losslessly).
- _classify extended to 3-tuple (category, surface, decimal_value)
with possessive strip retry. Brief 8.2's sentence-initial
lookup-first + gender-skip preserved AND extended to mid-sentence
(gender is enrichment everywhere, never admission).
- Whole-problem coexistence dispatch in math_candidate_graph.py
(config.comprehension_reader_questions=True): reader attempts the
whole problem; on any ReaderRefusal falls through to existing
regex parser. All-or-nothing per the brief.
- Lexicon expansion (carried into renamed proper_noun_gender_*
files): +2 accumulation_verb (adopt, invest), +2 currency_unit_noun
(dollar, cent), +6 capacity_verb (fill, lift, play, work, finish,
drive), +5 female names (allison, brooke, jan, marion, sidney),
+14 male names (bart, fernando, georgie, jake, jed, jeremie, jose,
orlando, rex, rudolph, steve, troy, xavier, yun), +numerous
count_unit_noun, drain_token, time_unit_noun.
- ADR-0164.4-phase2-statement-frame-reader.md — the architectural
rationale and acceptance contract.
Measurement (reader_phase2_delta.json):
flag-OFF: correct=3 refused=47 wrong=0
flag-ON: correct=3 refused=47 wrong=0
delta: 0/0/0
Below the brief's floor of correct >= 4. Architecture is sound — the
reader admits cases as graphs when the structure resolves, refuses
cleanly otherwise, preserves wrong=0 across both flag states.
Bottleneck table (from per-case attribution):
count refusal_class dominant cause
----- ---------------------- ------------------------------------
18 incomplete_operation multi-quantity ops; no-quantity op
11 unknown_word "hundred", "presently", "one-hour",
non-math verbs (compound numerics,
lexicon gaps)
6 unexpected_category fraction / percentage literals;
multi-subject sentences
6 unresolved_pronoun "them", "their", "his" with no
compatible entity
5 unattached_quantity quantity never bound to a unit
1 no_question_target question parsed but slot never set
Closing the gate to mixed-bounded [4, 24] is Phase 2.1 scope: extend
composition rules for multi-quantity ops, add fraction/percentage
primitives (per ADR-0164.1 amendment), expand lexicon for the
remaining unknown_word cases, extend pronoun resolution.
Invariants preserved
- wrong = 0 in both flag states ✓
- flag-OFF byte-identical to today ✓
- determinism (50/50 identical runs) ✓
- Capability axes G1-G5, S1 unchanged ✓
- Reader tests: 19 (Phase 2) + 18 (Phase 1, post-update) + 53 (pack)
+ 76 (lexicon + primitives) = 166 specific to this change; all pass
- core test --suite smoke -q: 67 passed
Rebase note
This PR was authored against an older base; rebased onto current
main to incorporate #333 (Brief 8.2 universal proper_noun_token
primitive) and #334 (ADR-0166 measurement discipline). The rebase
required:
- Lexicon files renamed proper_noun_entity_* -> proper_noun_gender_*
(with the Phase 2 additions merged into the gender_* files)
- Compiled lexicon.jsonl unchanged from #333's 207-entry state
(Phase 2's per-category additions are runtime-visible via the
source loader, not via the compiled file)
- _classify reconciled with Brief 8.2's sentence-initial dispatch +
Phase 2's 3-tuple decimal-value return
- All dispatch tables and category checks updated to reference
proper_noun_token (singular) instead of proper_noun_entity_{f,m}
- Three Phase 1 test expectations updated to reflect Phase 2
behavior (proper noun at position 0 now opens statement pre-frame
instead of refusing; pronoun resolution applies per ADR-0164.2)
Per ADR-0166's three-question test, this PR is honest measurement:
capability exists, at least one case admits, lane distinguishes
presence from absence — which the bottleneck table demonstrates.
Refs ADR-0164.3 §Phasing Phase 2, ADR-0164.1 amendment (Brief 8.2),
ADR-0166 §"Mixed (notable but not blocking)" — except here, below
floor.