From b5989f35ec73516dee297aace3f1550a95abdd0d Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 12 May 2026 19:14:22 -0700 Subject: [PATCH] init: ingest, field, vocab, vault, persona, generate, session layers --- field/__init__.py | 2 ++ field/propagate.py | 21 ++++++++++++ field/state.py | 20 +++++++++++ generate/__init__.py | 1 + generate/stream.py | 51 ++++++++++++++++++++++++++++ ingest/__init__.py | 1 + ingest/gate.py | 38 +++++++++++++++++++++ persona/__init__.py | 1 + persona/motor.py | 79 ++++++++++++++++++++++++++++++++++++++++++++ session/__init__.py | 1 + session/context.py | 49 +++++++++++++++++++++++++++ vault/__init__.py | 1 + vault/store.py | 56 +++++++++++++++++++++++++++++++ vocab/__init__.py | 1 + vocab/manifold.py | 62 ++++++++++++++++++++++++++++++++++ 15 files changed, 384 insertions(+) create mode 100644 field/__init__.py create mode 100644 field/propagate.py create mode 100644 field/state.py create mode 100644 generate/__init__.py create mode 100644 generate/stream.py create mode 100644 ingest/__init__.py create mode 100644 ingest/gate.py create mode 100644 persona/__init__.py create mode 100644 persona/motor.py create mode 100644 session/__init__.py create mode 100644 session/context.py create mode 100644 vault/__init__.py create mode 100644 vault/store.py create mode 100644 vocab/__init__.py create mode 100644 vocab/manifold.py diff --git a/field/__init__.py b/field/__init__.py new file mode 100644 index 00000000..871038aa --- /dev/null +++ b/field/__init__.py @@ -0,0 +1,2 @@ +from .state import FieldState +from .propagate import propagate_step diff --git a/field/propagate.py b/field/propagate.py new file mode 100644 index 00000000..fec10263 --- /dev/null +++ b/field/propagate.py @@ -0,0 +1,21 @@ +""" +Field propagation — the generation heartbeat. + +Each step: F <- versor_apply(V, F) + +V is the rotor for the current node's outgoing edge in the vocab manifold. +No correction. No normalization. No conditional branching. The loop is tight. +""" + +from algebra.versor import versor_apply +from field.state import FieldState + + +def propagate_step(state: FieldState, V) -> FieldState: + """ + Apply one versor transition. + V is the edge rotor from the current node. + Returns a new FieldState one step forward on the manifold. + """ + new_F = versor_apply(V, state.F) + return FieldState(F=new_F, node=state.node, step=state.step + 1) diff --git a/field/state.py b/field/state.py new file mode 100644 index 00000000..b1484a1c --- /dev/null +++ b/field/state.py @@ -0,0 +1,20 @@ +""" +FieldState — the complete cognitive field at one moment. + +Invariant: versor_condition(F) < 1e-6 always. +This is checked at injection and maintained structurally by versor_apply(). +""" + +from dataclasses import dataclass +import numpy as np + + +@dataclass +class FieldState: + F: np.ndarray # shape (32,) — Cl(4,1) multivector on the versor manifold + node: int = 0 # current node index in the vocabulary manifold + step: int = 0 # number of propagation steps taken + + def advance(self, new_F: np.ndarray, new_node: int) -> "FieldState": + """Return a new FieldState after one propagation step.""" + return FieldState(F=new_F, node=new_node, step=self.step + 1) diff --git a/generate/__init__.py b/generate/__init__.py new file mode 100644 index 00000000..0f426560 --- /dev/null +++ b/generate/__init__.py @@ -0,0 +1 @@ +from .stream import generate, agenerate diff --git a/generate/stream.py b/generate/stream.py new file mode 100644 index 00000000..0ef0cb88 --- /dev/null +++ b/generate/stream.py @@ -0,0 +1,51 @@ +""" +Generation loop — token streaming from the versor manifold. + +Every token: nearest word to current F via CGA inner product. +Every step: F <- versor_apply(V, F) where V is the edge rotor. + +No confidence gates. No IDK fallback. No attractor clamping. +F is always on the manifold. nearest() is always exact. +""" + +import numpy as np +from field.state import FieldState +from field.propagate import propagate_step + + +def generate(state: FieldState, vocab, persona, max_tokens: int = 128) -> list: + """ + Generate a token sequence from an initial FieldState. + + Loop: + 1. Apply persona motor to current field + 2. Find nearest vocab node via CGA inner product + 3. Emit token + 4. Get edge rotor from current node to nearest node + 5. Propagate: F <- versor_apply(V, F) + 6. Advance node pointer + """ + tokens = [] + current = state + + for _ in range(max_tokens): + F_voiced = persona.apply(current.F) + word, word_idx = vocab.nearest(F_voiced) + tokens.append(word) + V = vocab.edge_rotor(current.node, word_idx) + current = propagate_step(current, V) + current = FieldState(F=current.F, node=word_idx, step=current.step) + + return tokens + + +async def agenerate(state: FieldState, vocab, persona, max_tokens: int = 128): + """Async streaming version — yields one token at a time.""" + current = state + for _ in range(max_tokens): + F_voiced = persona.apply(current.F) + word, word_idx = vocab.nearest(F_voiced) + yield word + V = vocab.edge_rotor(current.node, word_idx) + current = propagate_step(current, V) + current = FieldState(F=current.F, node=word_idx, step=current.step) diff --git a/ingest/__init__.py b/ingest/__init__.py new file mode 100644 index 00000000..94460d7f --- /dev/null +++ b/ingest/__init__.py @@ -0,0 +1 @@ +from .gate import inject diff --git a/ingest/gate.py b/ingest/gate.py new file mode 100644 index 00000000..0430e93f --- /dev/null +++ b/ingest/gate.py @@ -0,0 +1,38 @@ +""" +The single injection gate. + +The ONLY point where raw data enters the versor manifold. +normalize_to_versor() is called here and nowhere else in production code. + +Contract: + Input: raw token sequence + VocabManifold + Output: FieldState with F satisfying versor_condition(F) < 1e-6 +""" + +from algebra.versor import normalize_to_versor, versor_condition +from algebra.holonomy import holonomy_encode +from field.state import FieldState + + +def inject(tokens: list, vocab) -> FieldState: + """ + Encode a token sequence and inject into the versor manifold. + + Steps: + 1. Look up each token's versor in the vocab manifold + 2. Encode via holonomy walk + 3. Normalize to versor (the single allowed normalization call) + 4. Assert versor condition before returning + """ + word_versors = [vocab.get_versor(t) for t in tokens] + H = holonomy_encode(word_versors) + F = normalize_to_versor(H) + + cond = versor_condition(F) + if cond > 1e-5: + raise RuntimeError( + f"Injection produced non-versor field: condition={cond:.2e}. " + "Check holonomy_encode() and normalize_to_versor()." + ) + + return FieldState(F=F, node=0, step=0) diff --git a/persona/__init__.py b/persona/__init__.py new file mode 100644 index 00000000..05a7f9f4 --- /dev/null +++ b/persona/__init__.py @@ -0,0 +1 @@ +from .motor import PersonaMotor diff --git a/persona/motor.py b/persona/motor.py new file mode 100644 index 00000000..543811dd --- /dev/null +++ b/persona/motor.py @@ -0,0 +1,79 @@ +""" +Persona as a CGA motor — a rigid screw motion on the generation manifold. + +M = T * R where: + T = translator versor (persona's position in concept space) + R = rotor (persona's characteristic rotation) + +Applying persona: F_voiced = M * F * reverse(M) +This is a versor product. Persona application is algebraically closed. +No weight overlay. No post-hoc bias. No separate correction pass. +""" + +import numpy as np +from algebra.versor import versor_apply, normalize_to_versor +from algebra.cl41 import geometric_product, reverse, basis_vector, N_COMPONENTS + + +class PersonaMotor: + def __init__(self, translator: np.ndarray, rotor: np.ndarray): + """ + translator: a versor encoding translational bias in CGA + rotor: a versor encoding rotational character + Both must satisfy versor_condition < 1e-6. + """ + self.M = normalize_to_versor( + geometric_product( + np.asarray(translator, dtype=np.float32), + np.asarray(rotor, dtype=np.float32), + ) + ) + + def apply(self, F: np.ndarray) -> np.ndarray: + """Apply persona to field F. Returns M * F * reverse(M).""" + return versor_apply(self.M, F) + + def compose(self, other: "PersonaMotor") -> "PersonaMotor": + """ + Compose two persona motors: M_combined = self.M * other.M + Used to blend persona layers (base persona + session persona). + """ + result = PersonaMotor.__new__(PersonaMotor) + result.M = normalize_to_versor(geometric_product(self.M, other.M)) + return result + + @classmethod + def identity(cls) -> "PersonaMotor": + """The identity motor — applies no transformation.""" + inst = cls.__new__(cls) + inst.M = np.zeros(N_COMPONENTS, dtype=np.float32) + inst.M[0] = 1.0 + return inst + + @classmethod + def from_concept_vector(cls, concept: np.ndarray) -> "PersonaMotor": + """ + Build a persona motor from a 3D concept vector in R^3. + Embeds as a CGA translator: T = 1 + (1/2) * t * e_inf + where e_inf = e+ + e- (the point at infinity in CGA). + """ + concept = np.asarray(concept, dtype=np.float32) + assert len(concept) == 3 + + e_inf = basis_vector(3) + basis_vector(4) # e+ + e- + + t_blade = np.zeros(N_COMPONENTS, dtype=np.float32) + for i in range(3): + t_blade += concept[i] * geometric_product(basis_vector(i), e_inf) + + translator = np.zeros(N_COMPONENTS, dtype=np.float32) + translator[0] = 1.0 + translator += 0.5 * t_blade + + rotor = np.zeros(N_COMPONENTS, dtype=np.float32) + rotor[0] = 1.0 + + return cls( + normalize_to_versor(translator), + normalize_to_versor(rotor), + ) diff --git a/session/__init__.py b/session/__init__.py new file mode 100644 index 00000000..2f62c487 --- /dev/null +++ b/session/__init__.py @@ -0,0 +1 @@ +from .context import SessionContext diff --git a/session/context.py b/session/context.py new file mode 100644 index 00000000..d46eab2e --- /dev/null +++ b/session/context.py @@ -0,0 +1,49 @@ +""" +SessionContext — binds field, vault, vocab, and persona for one session. + +One session = one field trajectory on the manifold. +The vault accumulates versors across turns. +The persona motor is fixed per session (or composable across sessions). +""" + +from field.state import FieldState +from vault.store import VaultStore +from persona.motor import PersonaMotor +from ingest.gate import inject +from generate.stream import generate, agenerate + + +class SessionContext: + def __init__(self, vocab, persona=None, vault=None): + self.vocab = vocab + self.persona = persona or PersonaMotor.identity() + self.vault = vault or VaultStore() + self.state: FieldState = None + self.turn: int = 0 + + def ingest(self, tokens: list) -> FieldState: + """Inject a prompt. Sets self.state. Stores field in vault.""" + self.state = inject(tokens, self.vocab) + self.vault.store(self.state.F, {"turn": self.turn, "role": "user"}) + return self.state + + def respond(self, max_tokens: int = 128) -> list: + """Generate a response from current state. Stores result in vault.""" + assert self.state is not None, "Call ingest() before respond()." + tokens = generate(self.state, self.vocab, self.persona, max_tokens) + self.vault.store(self.state.F, {"turn": self.turn, "role": "assistant"}) + self.turn += 1 + return tokens + + async def arespond(self, max_tokens: int = 128): + """Async streaming response.""" + assert self.state is not None, "Call ingest() before arespond()." + async for token in agenerate(self.state, self.vocab, self.persona, max_tokens): + yield token + self.vault.store(self.state.F, {"turn": self.turn, "role": "assistant"}) + self.turn += 1 + + def recall(self, query_tokens: list, top_k: int = 5) -> list: + """Recall relevant past versors for a query.""" + query_state = inject(query_tokens, self.vocab) + return self.vault.recall(query_state.F, top_k=top_k) diff --git a/vault/__init__.py b/vault/__init__.py new file mode 100644 index 00000000..7b28ee2d --- /dev/null +++ b/vault/__init__.py @@ -0,0 +1 @@ +from .store import VaultStore diff --git a/vault/store.py b/vault/store.py new file mode 100644 index 00000000..eae4b342 --- /dev/null +++ b/vault/store.py @@ -0,0 +1,56 @@ +""" +VaultStore — exact memory via CGA inner product scan. + +No HNSW. No approximate nearest neighbor. No index rebuild. +Recall is exact: argmax_i { cga_inner(query, X_i) } over stored versors. +Periodic null_project() prevents floating-point null-cone drift in long sessions. +""" + +import numpy as np +from algebra.cga import cga_inner, null_project + + +class VaultStore: + def __init__(self, reproject_interval: int = 100): + self._versors: list = [] + self._metadata: list = [] + self._store_count: int = 0 + self._reproject_interval = reproject_interval + + def store(self, F: np.ndarray, metadata: dict = None) -> int: + """Store a versor. Returns its index. Auto-reprojects every N stores.""" + self._versors.append(np.asarray(F, dtype=np.float32).copy()) + self._metadata.append(metadata or {}) + self._store_count += 1 + if self._store_count % self._reproject_interval == 0: + self.reproject() + return len(self._versors) - 1 + + def recall(self, query: np.ndarray, top_k: int = 5) -> list: + """ + Return top_k closest stored versors by CGA inner product. + Each result: {versor, score, metadata, index} + """ + if not self._versors: + return [] + scores = [cga_inner(query, v) for v in self._versors] + top_indices = sorted(range(len(scores)), key=lambda i: -scores[i])[:top_k] + return [ + { + "versor": self._versors[i], + "score": scores[i], + "metadata": self._metadata[i], + "index": i, + } + for i in top_indices + ] + + def reproject(self) -> None: + """ + Re-project all stored versors onto the null cone. + Corrects floating-point drift. Run between turns or asynchronously. + """ + self._versors = [null_project(v) for v in self._versors] + + def __len__(self) -> int: + return len(self._versors) diff --git a/vocab/__init__.py b/vocab/__init__.py new file mode 100644 index 00000000..82764128 --- /dev/null +++ b/vocab/__init__.py @@ -0,0 +1 @@ +from .manifold import VocabManifold diff --git a/vocab/manifold.py b/vocab/manifold.py new file mode 100644 index 00000000..7e1d109d --- /dev/null +++ b/vocab/manifold.py @@ -0,0 +1,62 @@ +""" +VocabManifold — the geometric vocabulary. + +Each word is a versor in Cl(4,1). nearest(F) finds the closest word +by CGA inner product — no cosine similarity, no ANN index. +""" + +import numpy as np +from algebra.cga import cga_inner +from algebra.versor import normalize_to_versor +from algebra.cl41 import geometric_product, reverse + + +class VocabManifold: + def __init__(self): + self._words: list = [] + self._versors: list = [] # each shape (32,) + + def add(self, word: str, versor: np.ndarray) -> None: + """Register a word-versor pair.""" + self._words.append(word) + self._versors.append(np.asarray(versor, dtype=np.float32).copy()) + + def get_versor(self, word: str) -> np.ndarray: + """Look up a word's versor. Raises KeyError if not found.""" + try: + idx = self._words.index(word) + return self._versors[idx].copy() + except ValueError: + raise KeyError(f"Word '{word}' not in vocabulary.") + + def nearest(self, F: np.ndarray, exclude_idx: int = -1) -> tuple: + """ + Find the word whose versor is closest to F by CGA inner product. + Returns (word, index). O(|vocab|), exact, no approximation. + cga_inner(X, Y) = -d^2 / 2 for null vectors: maximizing = minimizing distance. + """ + best_score = -np.inf + best_idx = 0 + for i, v in enumerate(self._versors): + if i == exclude_idx: + continue + score = cga_inner(F, v) + if score > best_score: + best_score = score + best_idx = i + return self._words[best_idx], best_idx + + def edge_rotor(self, from_idx: int, to_idx: int) -> np.ndarray: + """ + Compute the rotor that rotates from_versor toward to_versor. + R = normalize(1 + B * reverse(A)) + """ + A = self._versors[from_idx] + B = self._versors[to_idx] + R = geometric_product(B, reverse(A)) + R = R.copy() + R[0] += 1.0 + return normalize_to_versor(R) + + def __len__(self) -> int: + return len(self._words)