1. session/context.py — dialogue blade accumulation is now magnitude-preserving
via EMA (α=0.15). Running blade grows stronger each turn a concept is
confirmed rather than resetting to unit magnitude on every record_dialogue().
2. generate/stream.py — vault recall transitions are now score-weighted.
Each recalled rotor is scaled by softmax(scores)[i] before application so
high-confidence vault hits dominate and stale low-score entries barely move
the field.
3. session/context.py — anchor pull added after _hemisphere_consistent_field().
A mild α=0.05 slerp toward _anchor_field is applied at finalize_turn() to
provide continuous conjugate correction against angular drift within the
hemisphere. Unitized before writing back to state.
The realize_semantic / realize_target pipeline in realizer.py was fully
implemented but never called from chat/runtime.py. The hot path only called
realize() from articulation.py, which returns raw S-P-O word tokens with no
intent, tense, negation, quantifier or rhetorical-move awareness. This
disconnected the 13-construction realizer from every live chat turn.
New module generate/intent_bridge.py:
- classify_intent_from_input() runs the rule-based classifier against the
raw input text to obtain a DialogueIntent
- articulate_with_intent() builds a PropositionGraph from that intent,
grounds the <pending> obj slots with recalled vocabulary from the
generation result, plans articulation via plan_articulation(), and calls
realize_semantic() for the intent-specific template path
- Falls back cleanly to the existing ArticulationPlan surface when the
realizer returns an empty plan (OOV-heavy or UNKNOWN intent)
chat/runtime.py change:
- Import and call articulate_with_intent() after the existing realize() call
- Replace articulation.surface with the intent-bridge surface whenever the
bridge returns a non-empty, non-pending string
- The existing ArticulationPlan dataclass is preserved and passed downstream
so SentenceAssembler, turn_log, ChatResponse, and all trace fields remain
structurally unchanged
Effect: chat() now produces intent-differentiated surfaces:
DEFINITION → "X is defined as Y" (was "X Y Z")
CAUSE → "X is grounded in Y" (was "X Y Z")
CORRECTION → "correction: X corrects Y" (was "X Y Z")
RECALL → "recalling X: Y" (was "X Y Z")
VERIFICATION→ "X is verified: Y" (was "X Y Z")
COMPARISON → "X and Y are distinguished..." (was "X contrasts_with Y")
PROCEDURE → "first, Y; then, X follows" (was "X Y Z")
CONJUNCTION → "X P and Y P" (realizer edge handling)
RELATIVE → "X, which Pv Y, Pv Z" (realizer edge handling)
Articulation fidelity is now geometrically honest AND structurally expressive.
The surface corresponds to internal intent state, not a generic S-P-O join.
- Fix running_dialogue_blade grade explosion: replace outer_product
accumulation (which pushed past grade-5 in Cl(4,1), silently zeroing
the blade from turn 3 onward) with CGA-inner-oriented blade tracking
that preserves grade-2 across arbitrary turn counts.
- Add versor_condition guard at session composition boundary: cross-turn
field composition via versor_apply now fails closed (threshold 1e-2,
matching algebra construction residue tolerance) instead of silently
propagating degraded fields into vault and generation.
- Replace VaultStore list with deque(maxlen=max_entries): eliminates
O(N) list.pop(0) on every bounded eviction; deque auto-evicts in O(1).
- Replace O(N) vocab scan in generate/stream.py stop_nodes construction
with O(1) try/except index lookup per stop token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds referent tracking, session graph traversal, unknown-domain gating, correction propagation, compositional surface assembly, and regression coverage.
Follow-up fixes included before merge:
- split probe/commit/finalize turn flow so unknown-domain checks run before current-query vault writes
- record real input tokens and input versors for sync and async session paths
- return true graph distances from backward walks and consume them in correction decay
- synchronize corrected graph outputs into vault-backed recall and live referent state
- regenerate correction responses from corrected context rather than correction text
- keep coreference pronouns lowercase in question bodies
- centralize elaboration-string construction to avoid plan/surface drift
- add targeted dialogue fluency regression tests
- remove normalization and unitization calls from generation path
- skip invalid recalled fields instead of repairing them in generation
- punctuate selected articulation surfaces
- stabilize assertive dialogue roles
- anchor proposition slots to live field
- preserve session anchor orientation for coherence
- restore articulation surface as ChatResponse.surface while retaining walk_surface telemetry
- calibrate moderate E2 energy boundary
- reclose generated field states after propagation and recall
- restore pytest-safe REPL parsing and field_walk helper
- anchor proposition predicate selection to prompt field
- make vault exact self-recall deterministic
- align chat telemetry regression with restored surface contract
- calibrate identity threshold and per-axis telemetry
- keep walk surfaces visible when identity flags are telemetry
- report real vault recall hits through generation/runtime logs
- record selected surface in TurnEvent
- fix async chat persona reference
- add regression coverage for chat telemetry
agenerate() skipped _recall_state() entirely, meaning async streaming
responses were disconnected from session memory. This patch brings
agenerate() to full parity with the synchronous path:
- Accepts vault and recall_top_k parameters (default 3, matching generate())
- Calls _recall_state(_voiced_state(current, persona), vault, recall_top_k)
at each step before nearest-node selection
- Does not add stop_nodes or salience (those remain sync-only for now;
the core correctness gap is vault recall)
The async return value is still token-by-token via yield. Callers that
want final_state should use the synchronous path or wrap in a collector.
IdentityCheck runs after generation in ChatRuntime and must travel
forward with the result without requiring a second pass or a wrapper.
The field is Optional so all existing call sites that don't supply it
continue to work unmodified.
Key issues fixed:
- `CORE_BACKEND=numpy` was ignored, so tests mixed Python CGA embedding with Rust metric behavior.
- Dense construction seeds were being rejected by strict `unitize_versor()`, while sparse dirty inputs still needed to fail closed.
- Holonomy needed a construction-boundary path for raw/dense vocab fixtures and rare null final accumulators.
- Proposition storage polluted vault recall by storing the live field instead of the proposition’s subject versor.
- Dialogue qualitative frames rendered the same surface as assertive copular frames.
- Repeated session prompts could collapse into the same deterministic response path.
- Two proof fixtures were stale: one hand-built a non-null “null” vector, and one alignment proof omitted the English “with” anchor used by the resonance proof.
Verification:
`CORE_BACKEND=numpy CORE_STRICT_MLX_ON_APPLE=0 uv run core test -- -q`
Result: `277 passed in 59.52s`
Add geometry-backed ArticulationPlan and realize(), wire articulation into ChatRuntime and trace output, expose proposition relation_norm, and add articulation/runtime/CLI tests.
Add RuntimeConfig with English default output policy, wire output language through runtime/frame selection/generation/CLI, preserve language metadata in mounted manifolds, and add runtime/CLI policy tests.
- field/state.py: FieldState is now frozen+slotted; constructor copies and
enforces float32 shape (32,); advance() updated to pass raw arrays.
np.ndarray inside frozen dataclass is ref-frozen — copy() at construction
is the explicit contract boundary.
- generate/result.py: NEW — GenerationResult frozen dataclass carrying
tokens + final_state. Async variant yields tokens and exposes final_state
on completion.
- generate/stream.py: generate() now returns GenerationResult, not list[str].
vocab.edge_rotor() call replaced with:
A = vocab.get_versor_at(current.node)
B = vocab.get_versor_at(word_idx)
V = word_transition_rotor(A, B)
agenerate() updated to yield tokens and surface final_state.
- vocab/manifold.py: added get_versor_at(idx) and get_word_at(idx) indexed
accessors. VocabManifold stores points; algebra constructs operators.
normalize_to_versor() call-site in docstring clarified: callers must call
unitize_versor() (algebra construction primitive) before add(), not
normalize_to_versor() directly.
- algebra/versor.py: unitize_versor() added as the explicit construction-time
primitive. normalize_to_versor() kept but marked internal/gate-only.
Distinction encoded in docstrings and __all__.
- persona/motor.py + ingest/gate.py: SessionContext.respond() is not yet in
the repo as a separate file; gate.py docstring updated to reflect the
three-tier normalization doctrine:
unitize_versor() — algebra construction only
inject() — gate, once per raw input
normalization — forbidden in propagate/generate/vault recall