Three issues in the drift-fix landing (922bddc) addressed:
1. algebra/rotor.py: add rotor_power(R, alpha) — slerp on the rotor manifold
via the rotor's exp/log decomposition. Handles both rotation planes
(cos/sin) and boost planes (cosh/sinh); falls back to identity for
non-simple bivectors or null cases.
2. generate/stream.py: the score-weighted vault recall previously did
`weight*V + (1-weight)*np.eye(V.shape[0])`. Two bugs:
- np.eye produced a 32x32 matrix for a 1D multivector, crashing
versor_apply with a broadcasting error (2 cognition tests failing
on main).
- The linear blend produced multivectors with versor_condition up to
2.2e-2, violating the non-negotiable 1e-6 invariant declared in
CLAUDE.md. Now uses rotor_power(V, weight) which stays on the
manifold by construction (versor_condition <= 1.1e-16).
3. session/context.py: respond() now re-binds result.final_state to
self.state after finalize_turn's anchor pull, restoring the
"respond returns the same object that was vaulted" contract
(test_engine_loop_proof regression).
Verification:
- 41 new tests in tests/test_rotor_power.py covering closure preservation,
alpha=0/1 boundaries, half-angle composition, and word-transition rotors.
- Empirical multi-turn versor_condition stays at machine epsilon with
anchor pull, max 9.4e-7 without (under threshold either way after fix).
- Full suite: 609 passed, 4 skipped, 0 failed.
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.
- 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>
- 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.
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