core/generate/result.py
Shay 3746f06898 fix: cohesive seam pass — frozen FieldState, GenerationResult, generation/vocab/algebra separation, normalization doctrine
- 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
2026-05-13 12:32:36 -07:00

35 lines
1.3 KiB
Python

"""
GenerationResult — the complete output of one generation pass.
Generate() must return the evolved field state, not only surface tokens.
The field state after generation is semantically different from the
field state before generation; discarding it means the vault stores
the prompt field, not the assistant response field.
Contracts:
tokens — the decoded token sequence in emission order
final_state — FieldState after the last propagation step
trajectory — optional ordered list of intermediate FieldStates;
None unless the caller explicitly requests it (expensive)
"""
from __future__ import annotations
from dataclasses import dataclass, field
from field.state import FieldState
@dataclass(frozen=True, slots=True)
class GenerationResult:
tokens: tuple # decoded token sequence, immutable
final_state: FieldState
trajectory: tuple | None = None # (FieldState, ...) or None
def __post_init__(self) -> None:
# Coerce list inputs to tuple for immutability.
object.__setattr__(self, "tokens", tuple(self.tokens))
if self.trajectory is not None:
object.__setattr__(self, "trajectory", tuple(self.trajectory))
def text(self, sep: str = " ") -> str:
"""Join tokens into a string for display."""
return sep.join(self.tokens)