Three briefs in two PRs: - CC-1 (PR-α): ADR-0169 + ADR-0169.1 doctrine docs only - CC-2 + CC-3 (PR-β bundled): handler impl + new composition_reclassification change_kind + decomposer heuristic tightening Covers 20 of 47 audit refusals (quantity_extraction 12 + multi_quantity_composition 8) — the highest-leverage missing handler. Also retires the over-aggressive frame_reclassification routes from PR #386 surfaced by the 2026-05-27 end-to-end workbench demo.
16 KiB
CompositionClaim Wave + Heuristic Tightening — Brief Pack
Goal: Build the next Tier 1.5 handler (CompositionClaim) covering the
20 highest-leverage refusals in audit_brief_11.json
(quantity_extraction 12 + multi_quantity_composition 8), then tighten
the decomposer dispatch heuristic so the workbench stops emitting
handler-mismatched proposals.
Context. End-to-end demo on 2026-05-27 revealed PR #386's dispatch
heuristic is too aggressive — both frame_reclassification proposals
from the audit are semantic mismatches for FrameClaim's
SAFE_FRAME_CATEGORIES (which are quantity/ownership frames, not
structural decompositions). HITL caught it (architecture working as
designed) but the operator burden is high. Build the next genuine
handler (CompositionClaim) AND fix the heuristic to stop emitting
proposals whose dispatch can never be ratified.
Bundling rule: feedback-batch-during-research. Three briefs, two PRs:
- PR-α (CC-1): ADR-0169 + ADR-0169.1 doctrine docs only
- PR-β (CC-2 + CC-3 bundled): CompositionClaim impl + heuristic
tightening + new
change_kindLiteral extension
CC-3 must bundle with CC-2 because the new composition_reclassification
change_kind is what CC-3 routes to.
Dependency DAG
PR-α: CC-1 (ADR-0169 + 0169.1 docs)
│
└→ PR-β: CC-2 (handler impl) + CC-3 (heuristic tightening)
CC-1 ships first because CC-2 references the ratified doctrine.
Brief CC-1 — ADR-0169 CompositionClaim doctrine + adapter
Operator profile: Opus (substantial design work; load-bearing for
wrong=0 hazard scope)
Branch: docs/adr-0169-compositionclaim-doctrine
Base: origin/main
Style: Pure docs PR. No runtime mutation.
Outcome
Two new ADRs, modeled on ADR-0168 + ADR-0168.1:
-
docs/decisions/ADR-0169-compositionclaim-ratification.md— the doctrine. Same section structure as ADR-0168:- Status / Date / Author / Parent (ADR-0167) / Related
- Context — why CompositionClaim is qualitatively different from LexicalClaim AND from FrameClaim
- Prior ADR compatibility audit (matrix vs ADR-0056, 0057, 0114a, 0164, 0165, 0166, 0167, 0168) — confirm each compatibility result
- Decision — CompositionClaim is permitted only as a deterministic, replay-equivalent, operator-reviewed proposal surface with explicit hazard pins and category allowlists
- Definition of a CompositionClaim — canonical shape:
(surface_pattern, composition_category, polarity) - Why CompositionClaim is dangerous — arithmetic chains are upstream of solver invocation; wrong composition admits arithmetic errors, not noise
- Initial safe category scope — propose the initial allowlist;
multiplicative_composition(each <count>×<one_unit_cost>),additive_composition(sum of independent named quantities),subtractive_composition(initial − removed). Defer the rest (distributive, ratio, comparative) until proven. - Mutation boundary — may mutate reviewed composition-pattern registries; may NOT mutate solver / parser / decomposer / graph verifier semantics
- Replay obligations — claim-signature determinism, replay equivalence, queue-order independence, idempotency
- Refusal stability — previously-refusing cases may only transition to (a) correctly admitted or (b) still refused. NO ambiguous / partial / non-deterministic admissions.
- Partition guarantees — math-domain only; cross-domain prohibited
- Refusal-first doctrine —
refuse > speculatepreserved - Non-goals — list explicitly
- Sequencing — ADR-0166 Q1/Q2/Q3 answered
- Acceptance gates for implementation PR
-
docs/decisions/ADR-0169.1-math-compositionclaim-proposal-adapter.md— the adapter pattern (analogous to ADR-0168.1):- Why ADR-0057's reviewed-evidence floor requires a math-specific proposal type
- Data shape —
MathCompositionClaimProposalfrozen dataclass:@dataclass(frozen=True, slots=True) class MathCompositionClaimProposal: proposal_id: str claim_signature: str surface_pattern: str # the regex / structural pattern matched composition_category: str # must be in SAFE_COMPOSITION_CATEGORIES polarity: Literal["affirms", "falsifies"] evidence: tuple[MathReaderRefusalEvidence, ...] replay_evidence: AdmissibilityReplayEvidence | None review_state: Literal["pending", "accepted", "rejected", "withdrawn"] operator_note: str provenance: MathProposalProvenance | None - Trip-wires that close the ADR-0057 compatibility gap
Hard requirements
- No runtime code in this PR. Pure ADR authoring.
- Must explicitly state the initial SAFE_COMPOSITION_CATEGORIES so CC-2 has a fixed allowlist to implement against.
- Must explicitly enumerate case 0050 hazard pin in the acceptance gates section.
- Cross-reference
docs/handoff/ADR-0167-FOLLOWUPS.md §1priority hint (CompositionClaim is named there as 8+11 = 19 cases; the actual count inaudit_brief_11.jsonis 20 — note the correction).
Deliverables
docs/decisions/ADR-0169-compositionclaim-ratification.mddocs/decisions/ADR-0169.1-math-compositionclaim-proposal-adapter.md- No code changes. No test changes.
Reads required before starting
docs/decisions/ADR-0168-frameclaim-ratification.md(the exact template — every section in ADR-0169 should mirror this structure)docs/decisions/ADR-0168.1-math-frameclaim-proposal-adapter.md(the adapter template)docs/handoff/ADR-0167-FOLLOWUPS.md §1(priority hint context)docs/decisions/ADR-0172-math-corpus-decomposition-mechanism.md(Tier 1.5 context — why CompositionClaim is the next handler)- The 20 audit rows under
quantity_extraction+multi_quantity_compositioninevals/gsm8k_math/train_sample/v1/audit_brief_11.json(the data the doctrine must cover)
Brief CC-2 — CompositionClaim handler implementation
Operator profile: Opus (load-bearing wrong=0 surface; ratification
handler discipline)
Branch: feat/adr-0169-compositionclaim-handler
Base: origin/main (post-PR-α merge)
Outcome
Three new/modified modules and the dispatch wire:
-
teaching/math_composition_proposal.py—MathCompositionClaimProposaldataclass per ADR-0169.1 §"Data shape", with canonical_bytes, compute_claim_signature, build_composition_proposal. Mirror theteaching/math_frame_proposal.pystructure exactly. -
teaching/math_composition_ratification.py—apply_composition_claim()handler. Mirrorteaching/math_frame_ratification.pyexactly. Mutates ONLYlanguage_packs/data/en_core_math_v1/compositions/{category}.jsonlfiles. Does NOT touch solver / parser / decomposer / runtime. -
workbench/readers.py— extend_HANDLER_DISPATCH:_HANDLER_DISPATCH: dict[str, str] = { "vocabulary_addition": "LexicalClaim", "frame_reclassification": "FrameClaim", "composition_reclassification": "CompositionClaim", # NEW }Plus a
suggested_clibranch forCompositionClaim. -
teaching/math_contemplation_proposal.py— extend theproposed_change_kindLiteral:proposed_change_kind: Literal[ "matcher_extension", "injector_sub_shape", "vocabulary_addition", "frame_reclassification", "composition_reclassification", # NEW ]And update
to_jsonl_record/from_jsonl_recordto round-trip the new kind (canonical_bytes is unchanged — it already serializes any string).
Hard requirements
-
SAFE_COMPOSITION_CATEGORIESallowlist (from ADR-0169 §"Initial safe category scope"):SAFE_COMPOSITION_CATEGORIES: Final[frozenset[str]] = frozenset({ "multiplicative_composition", "additive_composition", "subtractive_composition", })Any other category →
WrongCompositionCategoryexception. -
Case 0050 hazard pin mandatory (mirror
tests/test_math_frame_ratification.py::test_case_0050_hazard_pin). Pin intests/test_math_composition_ratification.py. -
All replay obligations from ADR-0169 must be mechanically pinned:
- Deterministic claim signature
- Cross-process replay equivalence (subprocess test)
- Queue-order independence (A→B == B→A ratify)
- Duplicate idempotency (
AlreadyRatifiedon second call) - Evidence-tampering rejection
-
Refusal stability across the full
audit_brief_11.jsoncorpus — before/after ratifying a synthetic CompositionClaim, no case enters a partial/ambiguous state. -
Partition — cognition TeachingChainProposal flow MUST NOT see math CompositionClaims and vice versa.
-
No corpus laundering — audit evidence is NOT
source="corpus". Adapter has its own evidence floor. -
composition_reclassificationLiteral extension is the ONLY schema-level change toMathReaderRefusalShapeProposal. No other fields change.
Tests (tests/test_math_composition_ratification.py)
Mirror tests/test_math_frame_ratification.py exactly:
test_safe_categories_allowlist_pinned— exactly the 3 categoriestest_unsafe_category_rejected_with_wrong_category— e.g.distributive_composition→WrongCompositionCategorytest_claim_signature_canonicalization_deterministictest_claim_signature_replay_equivalence_cross_processtest_ratification_advances_pending_proposaltest_duplicate_ratification_raises_already_ratifiedtest_evidence_tampering_rejectedtest_case_0050_hazard_pintest_refusal_stability_audit_brief_11test_partition_cognition_proposals_not_seentest_audit_evidence_not_laundered_as_corpustest_queue_order_independencetest_workbench_dispatch_composition_reclassification— routes toCompositionClaim, not 501test_proposal_change_kind_literal_accepts_composition_reclassificationtest_jsonl_round_trip_with_composition_reclassification— extends the W1 round-trip test for the new kind
Deliverables
teaching/math_composition_proposal.py(new file)teaching/math_composition_ratification.py(new file)workbench/readers.py— extend_HANDLER_DISPATCH+suggested_cliteaching/math_contemplation_proposal.py— Literal extension + serializer round-triplanguage_packs/data/en_core_math_v1/compositions/.gitkeep(new dir)tests/test_math_composition_ratification.py(new file, 15 tests)tests/test_adr_0172_w1_shape_proposal.py— add round-trip test forcomposition_reclassificationcore/cli.py— add test file toteachingsuite tuplecore test --suite teaching -qANDcore test --suite runtime -qgreen
Forbidden
- Mutating any compiled artifact
- Inventing categories outside
SAFE_COMPOSITION_CATEGORIES - Touching
matcher_extensionorinjector_sub_shapedispatch - Importing from
chat/,field/,generate/,algebra/ - Removing the existing
frame_reclassificationdispatch entry
Brief CC-3 — Decomposer heuristic tightening
Operator profile: Same operator as CC-2 (bundled in same PR — needs
the new composition_reclassification Literal value from CC-2)
Bundled into PR-β with CC-2.
Outcome
Two changes to teaching/math_contemplation.py::decompose_audit():
-
Add CompositionClaim routes — extend the pair-based dispatch table to route the 20 composition cases:
(incomplete_operation, quantity_extraction) → composition_reclassification # was injector_sub_shape (incomplete_operation, multi_quantity_composition) → composition_reclassification # was injector_sub_shape -
Remove the over-aggressive frame_reclassification routes — demonstrated at the 2026-05-27 end-to-end demo to be handler mismatches:
(unexpected_category, multi_subject_sentence) → injector_sub_shape # was frame_reclassification — WRONG: needs ReferenceClaim/CompositionClaim, not FrameClaim (unexpected_category, descriptive_frame_question) → injector_sub_shape # was frame_reclassification — WRONG: needs SlotClaim, not FrameClaimThese go back to
injector_sub_shape(the catch-all) until SlotClaim / ReferenceClaim handlers ship. Add an inline comment explaining why.
Hard requirements
-
Hypothesis step in the reasoning trace MUST be updated to reflect the new dispatch table — the justification text in the
hypothesisstep is the operator-facing audit of the heuristic. -
Re-running
core eval math-contemplationpost-merge MUST yield:- 2
composition_reclassificationproposals (covering 20 cases) - 3
matcher_extensionproposals (unchanged) - 0
frame_reclassificationproposals from this audit (the aggressive routes are gone; no real frame_opener refusals inaudit_brief_11.jsonexist) - 3
injector_sub_shapeproposals (the multi_subject_sentence and descriptive_frame_question cases land here pending SlotClaim / ReferenceClaim)
Verify this in the PR body.
- 2
Tests
Update tests/test_adr_0172_w2_decomposer.py:
test_decompose_audit_change_kind_dispatch— extend with assertions for the new routes + the demoted frame_reclassification routes- Add
test_quantity_extraction_routes_to_composition_reclassification - Add
test_multi_quantity_composition_routes_to_composition_reclassification - Add
test_multi_subject_sentence_routes_to_injector_sub_shape - Add
test_descriptive_frame_question_routes_to_injector_sub_shape
Forbidden
- Adding new change_kinds beyond
composition_reclassification - Changing the heuristic for any pair not listed above
- Touching the LexicalClaim or FrameClaim dispatch
Anti-regression invariants (all three briefs)
wrong == 0oncore eval gsm8k_math— preserved- ADR-0166 — no new eval lanes
- ADR-0057 replay-equivalence — inherited
- Pinned-lane SHAs — should not require updates
engine_state/*— never committed- Case 0050 hazard — pinned in CC-2's test suite
Memory pointers
- feedback-batch-during-research — bundling justification
- feedback-no-self-dispatch-of-subagents — Shay dispatches
- feedback-wrong-zero-hazard-case-0050 — CC-2 mandatory pin
- feedback-parallel-agent-worktrees — fresh worktree per brief
- feedback-cleanup-as-you-find — remove dead frame_reclassification test fixtures touched by CC-3
- feedback-production-line-pattern — dispatch protocol
- milestone-adr-0172-tier1-2026-05-27 — Tier 1 milestone
- adr-0167-audit-as-evidence-wave — parent ADR
What ships when both PRs land
- CC-1 (ADR-0169 doctrine): Doctrine on main; CompositionClaim becomes the next ratifiable surface scope; sub-type follow-on queue in ADR-0167-FOLLOWUPS §1 advances.
- CC-2 (handler): First handler that can actually ratify the
highest-leverage refusals (20 of 47). Workbench
POST /math-proposals/{id}/ratifyon the 2 composition_reclassification proposals returns 200/routed with a realapply_composition_claim()command. - CC-3 (heuristic tightening): Workbench stops emitting handler-mismatched proposals. The 2 previously-misrouted frame_reclassification proposals now route to injector_sub_shape (still 501 until SlotClaim/ReferenceClaim ship, but the dispatch is honest).
Together: the compounding loop runs for real on 20 of 47 audit
refusals. Run core eval math-contemplation, ratify either
composition_reclassification proposal, execute the suggested CLI,
re-run core eval gsm8k_math — those previously-refused cases admit.