init: ingest, field, vocab, vault, persona, generate, session layers
This commit is contained in:
parent
b80dd57a9b
commit
b5989f35ec
15 changed files with 384 additions and 0 deletions
2
field/__init__.py
Normal file
2
field/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .state import FieldState
|
||||
from .propagate import propagate_step
|
||||
21
field/propagate.py
Normal file
21
field/propagate.py
Normal file
|
|
@ -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)
|
||||
20
field/state.py
Normal file
20
field/state.py
Normal file
|
|
@ -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)
|
||||
1
generate/__init__.py
Normal file
1
generate/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .stream import generate, agenerate
|
||||
51
generate/stream.py
Normal file
51
generate/stream.py
Normal file
|
|
@ -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)
|
||||
1
ingest/__init__.py
Normal file
1
ingest/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .gate import inject
|
||||
38
ingest/gate.py
Normal file
38
ingest/gate.py
Normal file
|
|
@ -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)
|
||||
1
persona/__init__.py
Normal file
1
persona/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .motor import PersonaMotor
|
||||
79
persona/motor.py
Normal file
79
persona/motor.py
Normal file
|
|
@ -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),
|
||||
)
|
||||
1
session/__init__.py
Normal file
1
session/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .context import SessionContext
|
||||
49
session/context.py
Normal file
49
session/context.py
Normal file
|
|
@ -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)
|
||||
1
vault/__init__.py
Normal file
1
vault/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .store import VaultStore
|
||||
56
vault/store.py
Normal file
56
vault/store.py
Normal file
|
|
@ -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)
|
||||
1
vocab/__init__.py
Normal file
1
vocab/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .manifold import VocabManifold
|
||||
62
vocab/manifold.py
Normal file
62
vocab/manifold.py
Normal file
|
|
@ -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)
|
||||
Loading…
Reference in a new issue