* docs: add semantic state transition blueprint * docs: add ADR-0184 semantic state transitions * docs: add RB-GSM solver design input notes
30 KiB
Semantic State Transition Blueprint
Status: development blueprint / handoff document
Branch: docs/semantic-state-transition-blueprint
Scope: docs only; no runtime code changes
Audience: lead engineer / reviewer / future ADR author
Purpose: define how to fit a scoped semantic-state-transition reader into the existing CORE math derivation lane without weakening wrong = 0.
0. Executive decision
CORE should not add another parser or another arithmetic composer as the next architectural move.
The next move is to promote the already-proven semantic transition behavior emerging in generate/derivation/accumulate.py into a small, sealed, reusable substrate:
text
-> lexeme extraction + clause segmentation
-> semantic frames
-> scoped entity-owned state transitions
-> semantic world / ledger candidates
-> GroundedDerivation replay
-> existing self-verification / classification
-> cross-composer pool
-> answer or refusal
The system already contains the pieces, but they are spread across composer-local helpers:
generate/derivation/extract.pylifts quantities and units.generate/derivation/clauses.pyprovides sentence-level clause segmentation and local clause results.generate/derivation/accumulate.pyimplements the first real state-transition reading: single-referent gain/loss accumulation.generate/derivation/target.pycontains early question-target and temporal-scope guards.generate/derivation/verify.pyowns the proof gate.generate/derivation/pool.pyowns cross-composer disagreement and commit eligibility.generate/comprehension/state.pycontains older, broader immutable reader-state types, held hypotheses, reader refusals, and canonical hashing.
The blueprint recommendation:
Add a sealed derivation-lane semantic-state package, initially backed by accumulation only, that emits the same
GroundedDerivationcandidates the current gate already knows how to verify.
This gives CORE a real place for entity scope, question scope, temporal scope, state mutation, and later transfer/rate/comparison/DAG logic without creating composer spaghetti.
1. Why this is needed
1.1 The present failure mode
Current successful logic is mostly candidate arithmetic plus a strong verifier. That is good, but it is not enough. The system needs a pre-arithmetic reading step that understands:
- who owns a quantity;
- whether a quantity is an initial state, delta, scalar, rate, comparison, distractor, or target;
- whether a later clause continues the same referent or introduces a new actor;
- whether a cue licenses gain, loss, multiplication, division, comparison, or no operation;
- whether the question asks for final state, prior state, difference, total, rate, or another relation;
- whether quantities are genuinely relevant or isolated foreign distractors;
- whether the derivation is a linear chain or needs branching / reuse.
The current local composers encode some of that, but not as a shared object language.
1.2 The exact architectural gap
GroundedDerivation is intentionally small:
start Quantity + ordered Step tuple
That is the right arithmetic proof object. It is not the right semantic reading object.
The missing layer is a semantic object language that can represent:
Sam.apples = 14
Sam.apples += 9
question target = final Sam.apples
before replaying the arithmetic as:
14 + 9 = 23
1.3 Why now
This is the right time because the repository has already proven three supporting facts:
accumulate.pyshows that semantic state transitions can flip useful cases while preserving refusal-first guards.pool.pyshows that multiple readings should be pooled and arbitrated by disagreement instead of composer priority.verify.pyis strong enough to remain the final commit gate; the semantic layer can stay as candidate generation.
If we keep extending one composer at a time, the same ideas will be reimplemented repeatedly: referent binding, cue polarity, target scoping, temporal refusal, distractor classification, and eventually DAG handling. That is the path to spaghetti.
2. Non-negotiable constraints
2.1 Serving stays untouched
The semantic-state substrate must live in the sealed derivation/practice lane first.
Do not import it from:
chat/**- serving response generation
- runtime surface selection
- shared grounding primitives unless separately gated
The first implementation should preserve the current two-regime contract:
serving: unchanged
practice/confuser lanes: allowed to attempt, measure, and eliminate
2.2 Existing verifier remains authoritative
Do not replace generate/derivation/verify.py.
The semantic layer should produce candidates and refusal reasons. It should not become a second answer gate.
Commit path should remain:
semantic candidate
-> GroundedDerivation replay
-> classify_derivation / self_verifies
-> pool uniqueness / disagreement
-> commit only if complete and unique
2.3 No hidden best-guessing
The semantic layer must refuse rather than infer silently when:
- referent binding is ambiguous;
- a pronoun has multiple possible antecedents;
- gain/loss polarity is ambiguous;
- a new actor appears in a single-referent chain;
- a temporal target is unsupported;
- a question target cannot be bound;
- more than one semantic world survives without eliminating evidence.
2.4 No grammar-template backslide
Keep ADR-0165 discipline:
- lexeme-level extraction is allowed;
- closed cue sets are allowed;
- deterministic clause/sub-clause splitting is allowed when scoped and tested;
- broad sentence-template parsing is not allowed.
This substrate should be a typed transition interpreter, not a new regex grammar parser.
2.5 Dead-code removal is part of the plan
Any path made obsolete by the semantic-state substrate must be marked and later removed. Do not leave parallel, inert, or duplicate readers unless explicitly retained as offline baselines.
3. Current repository map
3.1 generate/derivation/extract.py
Current role:
- lexeme-level quantity extraction;
- word numbers;
- list-unit inheritance;
- sentence-final numbers;
- unit hygiene;
- hyphen-bonded number-units;
- intentionally deferred multi-word-unit handling.
Future role:
- remains lexeme lifting only;
- should not learn semantic roles like initial state, delta, target, actor, or temporal scope;
- feeds semantic frame construction.
Keep.
3.2 generate/derivation/clauses.py
Current role:
- sentence-level splitting;
- local clause result calculation;
- local ambiguity holds/refuses.
Future role:
- remains the default deterministic clause stream;
- semantic-state package can consume
segment_clauses(); - local sub-clause splitting can exist in semantic-state modules only when tightly scoped and tested.
Keep, but do not overload it with semantic roles.
3.3 generate/derivation/accumulate.py
Current role:
- first real state-transition composer;
- anchor state + gain/loss changes;
- referent guard;
- polarity classification;
- cue selection;
- distractor skip / anchor skip candidates for pooling.
Future role:
- should become a thin public composer facade;
- semantic logic should move into reusable state modules;
- public functions should remain stable initially:
compose_accumulation(problem_text)accumulation_candidates(problem_text)
Keep, but slim.
3.4 generate/derivation/compose.py
Current role:
- same-unit list-sum/comparative-scale slice;
- clause-local guard after earlier whole-problem hazards.
Future role:
- eventually should emit or consume semantic frames for list aggregation and comparative scaling;
- should not independently grow a second referent/temporal/question model.
Keep, but prevent further semantic accretion without using the shared substrate.
3.5 generate/derivation/search.py and multistep.py
Current role:
- multiplicative product candidates;
- target-guided bounded chain candidates;
- blunt but useful candidate sources;
- intentionally wrong=0-safe through verification and pooling.
Future role:
- remain candidate sources;
- over time, semantically grounded rate/product frames should reduce dependence on product-of-all shapes;
- do not delete until semantic replacements are measured and gated.
Keep, but expect demotion.
3.6 generate/derivation/target.py
Current role:
- question quantities;
- aggregation hints;
- asked-unit intersection;
- prior-state question guard.
Future role:
- become a lexeme-level target extractor used by semantic
QuestionTarget; - temporal target scope should move into the semantic-state target model.
Keep, but wrap.
3.7 generate/derivation/verify.py
Current role:
- operand grounding;
- cue grounding;
- unit consistency;
- divide-by-zero;
- completeness;
- commit eligibility classification;
- uniqueness via
select_self_verified.
Future role:
- unchanged final gate;
- semantic-state layer should add preconditions, not replace this.
Keep as authoritative.
3.8 generate/derivation/pool.py
Current role:
- union candidate readings across composers;
- classify as complete/exempt/invalid;
- refuse on disagreement;
- commit only complete, unique answers.
Future role:
- should become the stable arbitration seam;
- semantic-world candidates should enter through this path.
Keep as the integration point.
3.9 generate/comprehension/state.py
Current role:
- older broad reader-state substrate;
- immutable dataclasses;
- entity/quantity/question/refusal/hypothesis types;
- canonical bytes / hashes;
- still useful as a design precedent.
Future role:
- do not wire directly into scoring yet;
- reuse discipline and possibly some types if import direction is clean;
- avoid resurrecting old all-or-nothing / ambiguous pronoun hazards.
Keep, but treat as adjacent / partially inert.
3.10 Potential dead weight / caution zones
The following should be reviewed for eventual retirement or demotion:
-
Old lifecycle reader runtime assumptions
The amended ADR-0174 notes thatlifecycle.pyis not the reader to promote if it admits0/50and remains inert relative to the scoring path. Do not build the semantic-state transition system by trying to revive that whole path wholesale. -
Legacy parser runtime paths
If any legacy parser path remains in runtime scoring, it should be removed only after a lane-SHA and wrong=0 proof. If it is already only an offline baseline, leave it alone until the cleanup phase. -
Composer-local semantic helpers
Helpers like referent binding, polarity classification, cue selection, temporal target guards, and foreign-distractor logic should not keep spreading across composers. Extract once. -
Priority-ordered composer resolution
Any runner or scorer that picks the first non-Nonecomposer result is suspect. Pooling should be preferred wherever multiple readings are possible.
4. Target architecture
4.1 New package
Add:
generate/derivation/state/
__init__.py
model.py
frames.py
bind.py
change.py
target.py
ledger.py
replay.py
refusals.py
This package is derivation-lane scoped. It must not be imported by serving.
4.2 Model objects
Initial minimal model:
@dataclass(frozen=True, slots=True)
class EntityMention:
surface: str
canonical: str
clause_index: int
token_index: int
kind: str # "proper", "pronoun", "implicit", "unknown"
@dataclass(frozen=True, slots=True)
class QuantityMention:
value: float
unit: str
source_token: str
clause_index: int
role: str # "state", "delta", "scalar", "rate", "target", "unknown"
@dataclass(frozen=True, slots=True)
class CueMention:
surface: str
cue_kind: str # "gain", "loss", "aggregate", "multiplicative", "temporal", ...
clause_index: int
@dataclass(frozen=True, slots=True)
class StateKey:
entity: str
unit: str
@dataclass(frozen=True, slots=True)
class StateTransition:
key: StateKey
op: str # "set", "gain", "loss"
quantity: QuantityMention
cue: CueMention
clause_index: int
@dataclass(frozen=True, slots=True)
class SemanticLedger:
transitions: tuple[StateTransition, ...]
@dataclass(frozen=True, slots=True)
class SemanticWorld:
ledger: SemanticLedger
question_target: object | None
unresolved: tuple[str, ...]
refusal_reasons: tuple[str, ...]
The first version can keep some fields as closed strings rather than enums if that matches current style, but closed sets should live near the model and be tested.
4.3 Public surfaces
Initial public surfaces:
def accumulation_world_candidates(problem_text: str) -> tuple[SemanticWorld, ...]: ...
def replay_world(world: SemanticWorld) -> GroundedDerivation | None: ...
def semantic_state_candidates(problem_text: str) -> tuple[GroundedDerivation, ...]: ...
First implementation:
semantic_state_candidates(problem_text) == accumulation_candidates(problem_text)
Behavior should be equivalent before expanding scope.
4.4 Integration flow
Final intended flow:
resolve_pooled(problem_text)
-> pooled_candidates(problem_text)
-> semantic_state_candidates(problem_text)
-> multiplicative_candidates(problem_text)
-> candidate_chains(problem_text)
-> classify_derivation(...)
-> disagreement / commit eligibility
Initially, pool.py can keep calling accumulation_candidates; later swap to semantic_state_candidates after equivalence tests pass.
5. Implementation phases
Phase S0 — ADR / blueprint ratification
Goal
Convert this blueprint into an ADR or ADR-scope document before code begins.
Why
This touches several ADR lines:
- ADR-0174 held-hypothesis comprehension;
- ADR-0178 compositional structure;
- ADR-0182 pooling;
- ADR-0165 regex scope;
- ADR-0176/0177 derivation search and cue precision.
A small ADR prevents future code from treating this as another local composer tweak.
Deliverable
Suggested file:
docs/decisions/ADR-0183-scoped-semantic-state-transitions.md
If ADR numbering is already occupied, use the next available number.
Acceptance
- ADR explicitly says serving remains untouched.
- ADR explicitly says semantic worlds replay to
GroundedDerivationand use existing gates. - ADR explicitly identifies
accumulate.pyas the first migration target. - ADR explicitly forbids reviving the old lifecycle reader as a scoring path without a separate proof.
Phase S1 — Extract proven helpers without behavior change
Where
From:
generate/derivation/accumulate.py
To:
generate/derivation/state/bind.py
generate/derivation/state/change.py
What moves
Move / rename:
_subject_token -> leading_subject_token
_same_referent -> continues_anchor_referent
_polarity -> classify_change_polarity
_cue -> select_change_cue
Why
These helpers are no longer accumulation-specific. They are the beginning of semantic reading.
How
- Copy behavior exactly.
- Preserve ordering and cue selection.
- Preserve tests.
- Add direct unit tests for the new helper names.
- Keep
accumulate.pypublic behavior unchanged.
Acceptance
tests/test_adr_0178_gb3b1_accumulation.pyunchanged and green.tests/test_adr_0182_pool.pyunchanged and green.- No new imports from serving.
- No change to practice counts unless explicitly measured as byte-identical.
Dead-weight check
If any old helper remains in accumulate.py after extraction, it should be a thin import alias or deleted.
Phase S2 — Add minimal semantic ledger for accumulation
Where
New files:
generate/derivation/state/model.py
generate/derivation/state/ledger.py
generate/derivation/state/replay.py
What
Represent accumulation as:
SET_STATE(entity, unit, value)
GAIN(entity, unit, value)
LOSS(entity, unit, value)
Why
This is the first true transition layer. It decouples semantic reading from arithmetic replay.
How
Internal flow for accumulation:
problem_text
-> quantity-bearing clauses
-> anchor state transition
-> change transitions
-> SemanticLedger
-> GroundedDerivation replay
-> existing select_self_verified / classify_derivation
compose_accumulation() should still return Resolution | None.
accumulation_candidates() should still return tuple[GroundedDerivation, ...].
Acceptance
- Clean gain/loss fixtures still resolve.
- New actor still refuses.
- No-change-cue still refuses.
- Multi-change in one clause still refuses.
- Anchor must still be single quantity for strict accumulation.
- Distractor-skip and anchor-skip candidates still classify as expected under pooling.
Dead-weight check
After this phase, direct manual construction of accumulation GroundedDerivation inside accumulate.py should be minimal or gone. Replay should own that.
Phase S3 — Add semantic question target wrapper
Where
New file:
generate/derivation/state/target.py
Existing file remains:
generate/derivation/target.py
What
Wrap existing Target with semantic target fields:
entity: optional
unit: optional
time_scope: final | prior | unknown
relation: count | difference | aggregate | unknown
First implementation can be conservative:
- detect prior-state question using existing
asks_prior_state(); - detect final/net questions only when safe;
- leave entity binding as unknown unless the question clearly names the anchor entity;
- refuse unsupported prior targets before replay.
Why
Temporal and question-scope logic should not live in pool.py or in individual composers forever.
How
Initially:
resolve_pooled() prior-state guard
-> semantic target refuses prior-state worlds before candidate replay
Keep old asks_prior_state() as a compatibility helper until all callers migrate.
Acceptance
- Prior-state minimal pair still refuses:
- “How much did Lisa have before lunch?” refuses.
- Forward/net twin still resolves:
- “How much money does Lisa have left?” resolves.
- Body narrative “before” does not trip prior-state refusal.
- “used to make” false positive stays guarded.
Dead-weight check
Once all prior-state checks route through semantic target, remove direct prior-state guard from pool.py or reduce it to a compatibility call.
Phase S4 — Introduce semantic_state_candidates() and pool integration
Where
New public surface:
generate/derivation/state/__init__.py
Modified:
generate/derivation/pool.py
generate/derivation/__init__.py
What
Add:
def semantic_state_candidates(problem_text: str) -> tuple[GroundedDerivation, ...]:
...
Initial implementation delegates to accumulation-backed worlds.
Then change pool ordering from:
accumulation_candidates, multiplicative_candidates, candidate_chains
to:
semantic_state_candidates, multiplicative_candidates, candidate_chains
Why
pool.py should not need to know every semantic composer. It should ask for semantic-state readings as one candidate source.
How
Perform this only after equivalence tests prove the candidate set is unchanged for existing fixtures.
Acceptance
pooled_candidates()de-duplicates as before.- All ADR-0182 pool tests remain green.
- Clean accumulation still commits.
- Distractor cases still refuse through disagreement.
- Exempt-only still never commits.
Dead-weight check
Once pool.py calls semantic_state_candidates, direct import of accumulation_candidates from pool.py should be removed.
Phase S5 — Add transfer events
Where
generate/derivation/state/transfer.py
or inside:
generate/derivation/state/change.py
if small.
What
Support:
Sam gives Tom 3 apples.
as:
Sam.apples -= 3
Tom.apples += 3
Why
Transfer is the first multi-entity state transition. It should be implemented only after entity-owned ledgers exist.
How
Rules:
- require source entity;
- require target entity;
- require grounded quantity;
- require unit;
- require transfer cue;
- refuse if source/target ambiguous;
- refuse if question target does not identify which resulting state is requested.
Acceptance
Tests:
Sam has 10 apples. Sam gives Tom 3 apples. How many apples does Sam have? -> 7
Tom has 2 apples. Sam gives Tom 3 apples. How many apples does Tom have? -> 5
Sam gives Tom 3 apples. How many apples does Sam have? -> refuse, no initial state
Sam gives Tom 3 apples. How many apples total? -> refuse until aggregate target exists
Dead-weight check
Do not patch transfer into accumulate.py. If transfer needs new helpers, they belong in the semantic-state package.
Phase S6 — Add comparison / difference frames
Where
generate/derivation/state/compare.py
What
Support safe cases:
Sam has 10 apples. Tom has 7 apples. How many more apples does Sam have than Tom?
as:
difference(Sam.apples, Tom.apples) = 10 - 7
Why
Difference questions require target relation binding, not just state replay.
How
Rules:
- both entity states must be known;
- units must match;
- question must request difference / “more than” / “less than”;
- relation direction must be explicit;
- ambiguous direction refuses.
Acceptance
Sam more than TomresolvesSam - Tom.Tom fewer than SamresolvesSam - Tomonly if direction is unambiguous.- Unknown relation direction refuses.
- Same-unit aggregate question does not accidentally become difference.
Phase S7 — Add rate / container frames
Where
generate/derivation/state/rate.py
What
Support safe product structures:
24 boxes, 12 erasers each -> 24 * 12 erasers
60 miles per hour for 2 hours -> 60 * 2 miles
Why
This is how CORE should eventually distinguish legitimate multiplicative binders from distractor products.
How
Rules:
- rate must bind two dimensions;
- container count must bind to rate denominator;
- output unit must be rate numerator;
- unrelated foreign state quantities must not be consumed;
- ambiguous “for” adjuncts should remain candidates only if structurally bound.
Acceptance
- Legitimate rate/container products commit.
- Distractor duration with unrelated target refuses via disagreement or lack of binding.
- Existing correct product cases do not regress.
- Product-of-all fallback remains until semantic rate coverage is measured sufficient.
Phase S8 — Add temporal target replay
Where
generate/derivation/state/time.py
What
Support:
before
initially
originally
at first
after
now
left
finally
as target scope over ledger history.
Why
Current forward composers compute final/net state. Prior-state questions are correctly refused. The semantic ledger makes prior-state replay possible.
How
Ledger replay must support:
state at transition index N
state before event E
state after event E
final state
initial state
Acceptance
- “How much did Lisa have before lunch?” returns the pre-spend value only when the event boundary is unambiguous.
- “How much does Lisa have left?” returns final/net state.
- Ambiguous temporal target refuses.
Phase S9 — Held semantic worlds and DAGs
Where
generate/derivation/state/world.py
generate/derivation/state/dag.py
What
Represent more than one possible semantic world, and eventually derivations where quantities are reused across branches.
Why
Some GSM8K problems cannot be represented as one left-fold chain. They require branch/reuse structures.
How
Only after earlier layers are stable:
SemanticWorld candidates
-> eliminate by constraints
-> if one survives, replay
-> if multiple survive and disagree, refuse
-> if no linear replay possible, emit DAG candidate behind a new gate
Acceptance
- Existing left-fold cases unchanged.
- DAG cases remain sealed until verifier supports them.
- No DAG candidate can commit without a completeness/grounding/target proof equivalent to current
GroundedDerivationrequirements.
6. Testing strategy
6.1 Unit tests
Add tests under:
tests/test_semantic_state_*.py
or ADR-numbered tests once an ADR exists.
Required categories:
- model validation;
- referent binding;
- cue polarity;
- semantic ledger construction;
- replay to
GroundedDerivation; - prior-state refusal;
- pool equivalence;
- deterministic replay.
6.2 Regression tests
Do not weaken existing tests:
tests/test_adr_0178_gb3b1_accumulation.pytests/test_adr_0182_pool.pytests/test_adr_0175_phase3b_mult_search.pytests/test_adr_0177_cp2a_training.py
6.3 Lane tests
Every implementation PR must state whether it affects:
- serving;
- sealed practice;
- confuser probe;
- train_sample;
- cue precision reports.
Default for early phases:
serving: unchanged
practice: equivalent until new semantic capability phase
confuser: equivalent until pool candidate source changes
6.4 Determinism tests
Every semantic object must either:
- be frozen and directly comparable, or
- expose canonical bytes / canonical hash.
Avoid unordered set iteration in emitted outputs. Sets may be used only for boolean membership or cardinality checks, never output ordering.
7. Documentation strategy
7.1 ADR sequence
Suggested:
ADR-0183— scoped semantic-state transitions.ADR-0183.S1— accumulation extraction/refactor into semantic-state substrate.ADR-0183.S2— semantic target wrapper.ADR-0183.S3— transfer events.ADR-0183.S4— comparison/difference frames.ADR-0183.S5— rate/container frames.ADR-0183.S6— temporal replay.ADR-0183.S7— semantic-world hypotheses / DAGs.
7.2 Required ADR language
Each ADR must include:
- why this is not a regex grammar template;
- how it preserves wrong=0;
- which composer-local logic it replaces;
- which tests fail if the guard is removed;
- what remains out of scope;
- dead-code/deprecation notes.
8. Dead weight / cleanup plan
8.1 Composer-local semantic helpers
After Phase S1/S2, the following should not remain as private accumulation-only concepts:
- subject-token extraction;
- same-referent check;
- change polarity;
- cue selection;
- anchor-state construction;
- state-change replay.
They should live in semantic-state modules.
8.2 Direct prior-state guard in pool
pool.py currently performs a prior-state guard because forward composers cannot compute prior target scope. Once semantic targets own temporal scope, this guard should move out of pool.py.
8.3 Old broad comprehension lifecycle
Do not delete casually. But do not treat it as the active path to promote unless a separate audit proves it has become load-bearing.
Potential future state:
generate/comprehension/state.py keep / share types / canonical hashing
generate/comprehension/lifecycle.py retire or demote if still inert
generate/math_parser.py baseline only, eventual delete after gate
generate/math_candidate_graph.py thin dispatcher / eventual simplification
8.4 Product-of-all fallback
Do not delete early. It still protects known product cases. But semantic rate/container frames should eventually reduce reliance on blunt product-of-all candidate generation.
Retirement condition:
- semantic rate/container frames cover protected positives;
- confuser probe remains wrong=0;
- train_sample protected correct cases do not regress;
- cue precision report confirms better candidate readings.
9. Risks and mitigations
Risk 1 — resurrecting old pronoun hazards
Mitigation:
- do not use gender-blind most-recent antecedent as a resolver;
- new actor or multiple possible actors should refuse;
- same-referent continuation must be tested with minimal pairs.
Risk 2 — semantic layer bypasses verifier
Mitigation:
- semantic worlds emit
GroundedDerivation; - verifier remains authoritative;
- no semantic world commits directly.
Risk 3 — hidden composer priority
Mitigation:
- pool candidates across composers;
- refuse on disagreement;
- do not choose first non-
Nonewhere multiple readings exist.
Risk 4 — grammar-template creep
Mitigation:
- closed lexeme sets only;
- scoped clause splitting only;
- every multi-token cue must be documented as a cue phrase, not a sentence template;
- no broad natural-language parse regexes.
Risk 5 — line-count growth without payoff
Mitigation:
- every semantic-state phase must identify what it makes obsolete;
- refactor phases must be behavior-equivalent;
- capability phases must show either correct-count increase or wrong-count decrease/refusal improvement.
10. Recommended immediate next PR
Branch:
feat/adr-0183-semantic-state-accumulation-substrate
Scope:
- Add ADR-0183.
- Add
generate/derivation/state/__init__.py. - Add
generate/derivation/state/bind.py. - Add
generate/derivation/state/change.py. - Move proven helper logic from
accumulate.pyinto those modules. - Keep
compose_accumulation()andaccumulation_candidates()behavior unchanged. - Add tests for extracted helpers.
- Do not touch serving.
- Do not change runners.
- Do not delete old reader files yet.
Acceptance:
existing accumulation tests pass
existing pool tests pass
no serving imports
no behavior change
new helper tests prove referent/polarity guards are non-vacuous
11. Final doctrine
CORE should read word problems as scoped semantic state, not as number bags.
The correct internal progression is:
lexemes
-> frames
-> entity-bound state transitions
-> question-targeted ledger replay
-> arithmetic proof object
-> existing verifier
-> pooled uniqueness/disagreement
accumulate.py is the proof that this works. pool.py is the proof that competing readings should be arbitrated by disagreement. verify.py is the proof that wrong=0 can remain the floor.
The next engineering task is to stop letting those ideas remain composer-local and promote them into a clean semantic-state substrate before transfer, comparison, rate, temporal, and DAG logic arrive.