* docs: add semantic state transition blueprint * docs: add ADR-0184 semantic state transitions * docs: add RB-GSM solver design input notes
1196 lines
30 KiB
Markdown
1196 lines
30 KiB
Markdown
# 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
|
|
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.py` lifts quantities and units.
|
|
- `generate/derivation/clauses.py` provides sentence-level clause segmentation and local clause results.
|
|
- `generate/derivation/accumulate.py` implements the first real state-transition reading: single-referent gain/loss accumulation.
|
|
- `generate/derivation/target.py` contains early question-target and temporal-scope guards.
|
|
- `generate/derivation/verify.py` owns the proof gate.
|
|
- `generate/derivation/pool.py` owns cross-composer disagreement and commit eligibility.
|
|
- `generate/comprehension/state.py` contains 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 `GroundedDerivation` candidates 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Sam.apples = 14
|
|
Sam.apples += 9
|
|
question target = final Sam.apples
|
|
```
|
|
|
|
before replaying the arithmetic as:
|
|
|
|
```text
|
|
14 + 9 = 23
|
|
```
|
|
|
|
### 1.3 Why now
|
|
|
|
This is the right time because the repository has already proven three supporting facts:
|
|
|
|
1. `accumulate.py` shows that semantic state transitions can flip useful cases while preserving refusal-first guards.
|
|
2. `pool.py` shows that multiple readings should be pooled and arbitrated by disagreement instead of composer priority.
|
|
3. `verify.py` is 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. **Old lifecycle reader runtime assumptions**
|
|
The amended ADR-0174 notes that `lifecycle.py` is not the reader to promote if it admits `0/50` and remains inert relative to the scoring path. Do not build the semantic-state transition system by trying to revive that whole path wholesale.
|
|
|
|
2. **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.
|
|
|
|
3. **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.
|
|
|
|
4. **Priority-ordered composer resolution**
|
|
Any runner or scorer that picks the first non-`None` composer result is suspect. Pooling should be preferred wherever multiple readings are possible.
|
|
|
|
---
|
|
|
|
## 4. Target architecture
|
|
|
|
### 4.1 New package
|
|
|
|
Add:
|
|
|
|
```text
|
|
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:
|
|
|
|
```python
|
|
@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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```text
|
|
semantic_state_candidates(problem_text) == accumulation_candidates(problem_text)
|
|
```
|
|
|
|
Behavior should be equivalent before expanding scope.
|
|
|
|
### 4.4 Integration flow
|
|
|
|
Final intended flow:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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 `GroundedDerivation` and use existing gates.
|
|
- ADR explicitly identifies `accumulate.py` as 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:
|
|
|
|
```text
|
|
generate/derivation/accumulate.py
|
|
```
|
|
|
|
To:
|
|
|
|
```text
|
|
generate/derivation/state/bind.py
|
|
generate/derivation/state/change.py
|
|
```
|
|
|
|
### What moves
|
|
|
|
Move / rename:
|
|
|
|
```text
|
|
_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.py` public behavior unchanged.
|
|
|
|
### Acceptance
|
|
|
|
- `tests/test_adr_0178_gb3b1_accumulation.py` unchanged and green.
|
|
- `tests/test_adr_0182_pool.py` unchanged 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:
|
|
|
|
```text
|
|
generate/derivation/state/model.py
|
|
generate/derivation/state/ledger.py
|
|
generate/derivation/state/replay.py
|
|
```
|
|
|
|
### What
|
|
|
|
Represent accumulation as:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
generate/derivation/state/target.py
|
|
```
|
|
|
|
Existing file remains:
|
|
|
|
```text
|
|
generate/derivation/target.py
|
|
```
|
|
|
|
### What
|
|
|
|
Wrap existing `Target` with semantic target fields:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
generate/derivation/state/__init__.py
|
|
```
|
|
|
|
Modified:
|
|
|
|
```text
|
|
generate/derivation/pool.py
|
|
generate/derivation/__init__.py
|
|
```
|
|
|
|
### What
|
|
|
|
Add:
|
|
|
|
```python
|
|
def semantic_state_candidates(problem_text: str) -> tuple[GroundedDerivation, ...]:
|
|
...
|
|
```
|
|
|
|
Initial implementation delegates to accumulation-backed worlds.
|
|
|
|
Then change pool ordering from:
|
|
|
|
```text
|
|
accumulation_candidates, multiplicative_candidates, candidate_chains
|
|
```
|
|
|
|
to:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
generate/derivation/state/transfer.py
|
|
```
|
|
|
|
or inside:
|
|
|
|
```text
|
|
generate/derivation/state/change.py
|
|
```
|
|
|
|
if small.
|
|
|
|
### What
|
|
|
|
Support:
|
|
|
|
```text
|
|
Sam gives Tom 3 apples.
|
|
```
|
|
|
|
as:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
generate/derivation/state/compare.py
|
|
```
|
|
|
|
### What
|
|
|
|
Support safe cases:
|
|
|
|
```text
|
|
Sam has 10 apples. Tom has 7 apples. How many more apples does Sam have than Tom?
|
|
```
|
|
|
|
as:
|
|
|
|
```text
|
|
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 Tom` resolves `Sam - Tom`.
|
|
- `Tom fewer than Sam` resolves `Sam - Tom` only if direction is unambiguous.
|
|
- Unknown relation direction refuses.
|
|
- Same-unit aggregate question does not accidentally become difference.
|
|
|
|
---
|
|
|
|
## Phase S7 — Add rate / container frames
|
|
|
|
### Where
|
|
|
|
```text
|
|
generate/derivation/state/rate.py
|
|
```
|
|
|
|
### What
|
|
|
|
Support safe product structures:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
generate/derivation/state/time.py
|
|
```
|
|
|
|
### What
|
|
|
|
Support:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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 `GroundedDerivation` requirements.
|
|
|
|
---
|
|
|
|
## 6. Testing strategy
|
|
|
|
### 6.1 Unit tests
|
|
|
|
Add tests under:
|
|
|
|
```text
|
|
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.py`
|
|
- `tests/test_adr_0182_pool.py`
|
|
- `tests/test_adr_0175_phase3b_mult_search.py`
|
|
- `tests/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:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. `ADR-0183` — scoped semantic-state transitions.
|
|
2. `ADR-0183.S1` — accumulation extraction/refactor into semantic-state substrate.
|
|
3. `ADR-0183.S2` — semantic target wrapper.
|
|
4. `ADR-0183.S3` — transfer events.
|
|
5. `ADR-0183.S4` — comparison/difference frames.
|
|
6. `ADR-0183.S5` — rate/container frames.
|
|
7. `ADR-0183.S6` — temporal replay.
|
|
8. `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:
|
|
|
|
```text
|
|
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-`None` where 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:
|
|
|
|
```text
|
|
feat/adr-0183-semantic-state-accumulation-substrate
|
|
```
|
|
|
|
Scope:
|
|
|
|
1. Add ADR-0183.
|
|
2. Add `generate/derivation/state/__init__.py`.
|
|
3. Add `generate/derivation/state/bind.py`.
|
|
4. Add `generate/derivation/state/change.py`.
|
|
5. Move proven helper logic from `accumulate.py` into those modules.
|
|
6. Keep `compose_accumulation()` and `accumulation_candidates()` behavior unchanged.
|
|
7. Add tests for extracted helpers.
|
|
8. Do not touch serving.
|
|
9. Do not change runners.
|
|
10. Do not delete old reader files yet.
|
|
|
|
Acceptance:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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.
|