diff --git a/generate/stream.py b/generate/stream.py index 9a7b3c33..b88fad0b 100644 --- a/generate/stream.py +++ b/generate/stream.py @@ -4,10 +4,10 @@ Generation loop — token streaming from the versor manifold. Every token: nearest non-current word to current F via CGA inner product. Every step: F <- versor_apply(V, F) where V = word_transition_rotor(A, B). -Generation is not a normalization boundary. Raw prompt normalization belongs -at ingest/gate.py; construction normalization belongs in algebra/vocab/persona. -If vault recall returns a non-operator-like field that cannot form a stable -transition, recall skips that hit instead of repairing it here. +Generation is not a raw prompt normalization boundary. Raw prompt normalization +belongs at ingest/gate.py; construction normalization belongs in algebra/vocab/persona. +The generation surface still owns its public result contract: the final field +returned to chat/cognition must satisfy the runtime versor invariant. """ from __future__ import annotations @@ -18,6 +18,7 @@ import numpy as np from field.state import FieldState from field.propagate import propagate_step from algebra.rotor import word_transition_rotor +from algebra.versor import unitize_versor from generate.attention import AttentionOperator from generate.result import GenerationResult from generate.salience import SalienceOperator @@ -115,6 +116,17 @@ def _voiced_state(state: FieldState, persona) -> FieldState: ) +def _close_final_state(state: FieldState) -> FieldState: + return FieldState( + F=unitize_versor(state.F), + node=state.node, + step=state.step, + holonomy=state.holonomy, + energy=state.energy, + valence=state.valence, + ) + + def _recall_state(state: FieldState, vault, top_k: int) -> tuple[FieldState, int]: if vault is None or top_k <= 0: return state, 0 @@ -252,7 +264,7 @@ def generate( return GenerationResult( tokens=tokens, - final_state=current, + final_state=_close_final_state(current), trajectory=trajectory, salience_top_k=salience_budget, candidates_used=candidates_used,