diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..c5e3688d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,49 @@ +# CORE-AI Agent Instructions + +## The Invariant (Read Before Touching Any Code) + +Every field state F must satisfy: + + ||F * reverse(F) - 1||_F < 1e-6 + +This is checked by algebra/versor.py::versor_condition(). + +## What You Must Never Add + +- Any normalization call outside ingest/gate.py +- Grade guards, grade monitors, or grade projection in the hot path +- Drift correction, correction thresholds, or correction timers +- ANN indexes, HNSW, cosine similarity, or approximate distance +- Field energy measurement or pseudoscalar accumulation checks +- Any function whose only job is to watch or repair another function + +If you think you need one of these, you have an unclosed operation upstream. +Find it and close it. + +## The Two Allowed Primitives + +Field transition: algebra/versor.py::versor_apply(V, F) -> V*F*reverse(V) +Distance metric: algebra/cga.py::cga_inner(X, Y) -> -d^2 / 2 + +These are the only primitives. Everything else is built from them. + +## Implementation Order + +Do not skip steps. Run the invariant test after each step before writing the next. + +1. algebra/cl41.py +2. algebra/versor.py -> tests/test_versor_closure.py must pass +3. algebra/cga.py -> tests/test_null_cone.py must pass +4. algebra/holonomy.py -> tests/test_holonomy.py must pass +5. ingest/gate.py +6. vocab/manifold.py +7. field/state.py + field/propagate.py +8. vault/store.py -> tests/test_vault_recall.py must pass +9. persona/motor.py -> tests/test_motor.py must pass +10. generate/stream.py +11. session/context.py + +## Architecture in One Sentence + +Raw input -> inject once -> versor on the manifold -> versor_apply every step -> +CGA inner product for recall and decoding -> persona motor for voicing -> done. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..307e24ac --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,33 @@ +# CORE-AI Agent Instructions + +## The Invariant (Read Before Touching Any Code) + +Every field state F must satisfy: + + ||F * reverse(F) - 1||_F < 1e-6 + +This is checked by algebra/versor.py::versor_condition(). + +## What You Must Never Add + +- Any normalization call outside ingest/gate.py +- Grade guards, grade monitors, or grade projection in the hot path +- Drift correction, correction thresholds, or correction timers +- ANN indexes, HNSW, cosine similarity, or approximate distance +- Field energy measurement or pseudoscalar accumulation checks +- Any function whose only job is to watch or repair another function + +If you think you need one of these, you have an unclosed operation upstream. +Find it and close it. + +## The Two Allowed Primitives + +Field transition: algebra/versor.py::versor_apply(V, F) -> V*F*reverse(V) +Distance metric: algebra/cga.py::cga_inner(X, Y) -> -d^2 / 2 + +These are the only primitives. Everything else is built from them. + +## Architecture in One Sentence + +Raw input -> inject once -> versor on the manifold -> versor_apply every step -> +CGA inner product for recall and decoding -> persona motor for voicing -> done. diff --git a/README.md b/README.md index 0a069cf4..8cb2af61 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ -# core -Versor Engine: a cognitive field system built on Cl(4,1) Clifford algebra. All state transitions are versor products — coherence is algebraic, never monitored. No grade guards, no drift correction, no ANN indexing. Memory recall via exact CGA inner product. Persona encoded as a rigid motor. +# CORE-AI: Versor Engine + +A cognitive field system built on Cl(4,1) Conformal Geometric Algebra. + +**Core invariant:** `||F * reverse(F) - 1||_F < 1e-6` at all times. + +All state is a versor. All transitions are versor products. +Coherence is algebraic by construction — not monitored, not corrected. + +## Quick Start + +```bash +pip install -e ".[dev]" +pytest tests/test_versor_closure.py # must pass before anything else +pytest tests/ +``` + +## Architecture + +``` +raw input -> ingest/gate.py (normalize once) + -> field/propagate.py (versor_apply every step) + -> generate/stream.py (nearest by cga_inner) + -> vault/store.py (store and recall by cga_inner) + -> persona/motor.py (rigid motor, not weight overlay) +``` + +## The Two Primitives + +- `versor_apply(V, F) = V * F * reverse(V)` — the only field transition +- `cga_inner(X, Y) = -d^2 / 2` — the only distance metric + +## Layers + +| Layer | Purpose | +|---|---| +| `algebra/` | Cl(4,1) multivector math, versor ops, CGA, holonomy | +| `ingest/` | Single injection gate — the only normalization site | +| `field/` | FieldState dataclass and propagation loop | +| `vocab/` | Word-to-versor manifold, edge rotors | +| `vault/` | Exact CGA inner product memory store | +| `persona/` | Persona as CGA motor (screw motion) | +| `generate/` | Token streaming loop | +| `session/` | Session binding: field + vault + vocab + persona | + +## Signature + +Cl(4,1): `(+, +, +, +, -)` — conformal model of 3D Euclidean space. +Multivectors: `float32` arrays of shape `(32,)`, ordered by grade. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..5a03ff7a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "core-ai" +version = "0.1.0" +description = "Versor Engine: cognitive field system on Cl(4,1) Conformal Geometric Algebra" +requires-python = ">=3.11" + +dependencies = [ + "numpy>=1.26", + "mlx>=0.18; sys_platform == 'darwin'", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "pytest-asyncio>=0.23", + "hypothesis>=6.100", +] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_holonomy.py b/tests/test_holonomy.py new file mode 100644 index 00000000..850ad6f3 --- /dev/null +++ b/tests/test_holonomy.py @@ -0,0 +1,48 @@ +import numpy as np +import pytest + +from algebra.versor import normalize_to_versor, versor_condition +from algebra.holonomy import holonomy_encode, holonomy_similarity + + +def _random_versors(n: int, seed: int = 0) -> list: + rng = np.random.default_rng(seed) + return [ + normalize_to_versor(rng.standard_normal(32).astype(np.float32)) + for _ in range(n) + ] + + +def test_holonomy_is_versor(): + words = _random_versors(5) + H = holonomy_encode(words) + assert versor_condition(H) < 1e-5 + + +def test_holonomy_bounded_short(): + words = _random_versors(1) + H = holonomy_encode(words) + norm = float(np.linalg.norm(H)) + assert 0.1 < norm < 10.0, f"Holonomy norm out of range: {norm}" + + +def test_holonomy_bounded_long(): + words = _random_versors(100) + H = holonomy_encode(words) + norm = float(np.linalg.norm(H)) + assert 0.1 < norm < 10.0, f"Long holonomy norm out of range: {norm}" + + +def test_holonomy_distinguishes_prompts(): + words_a = _random_versors(5, seed=0) + words_b = _random_versors(5, seed=99) + Ha = holonomy_encode(words_a) + Hb = holonomy_encode(words_b) + sim = abs(holonomy_similarity(Ha, Hb)) + assert sim < 0.99, f"Two random prompts should be geometrically distinct, got sim={sim}" + + +def test_holonomy_single_word(): + words = _random_versors(1) + H = holonomy_encode(words) + assert versor_condition(H) < 1e-5 diff --git a/tests/test_motor.py b/tests/test_motor.py new file mode 100644 index 00000000..d860cd95 --- /dev/null +++ b/tests/test_motor.py @@ -0,0 +1,47 @@ +import numpy as np +import pytest + +from algebra.versor import normalize_to_versor, versor_condition +from persona.motor import PersonaMotor + + +def _random_versor(seed=0) -> np.ndarray: + rng = np.random.default_rng(seed) + return normalize_to_versor(rng.standard_normal(32).astype(np.float32)) + + +def test_identity_motor_no_change(): + """Identity motor returns input unchanged.""" + motor = PersonaMotor.identity() + F = _random_versor(0) + result = motor.apply(F) + assert np.allclose(result, F, atol=1e-5) + + +def test_motor_application_stays_on_manifold(): + """Applying a motor keeps F on the versor manifold.""" + t = normalize_to_versor(_random_versor(1)) + r = normalize_to_versor(_random_versor(2)) + motor = PersonaMotor(t, r) + F = _random_versor(3) + result = motor.apply(F) + assert versor_condition(result) < 1e-4 + + +def test_motor_composition_on_manifold(): + """Composing two motors produces a motor on the manifold.""" + t1 = normalize_to_versor(_random_versor(0)) + r1 = normalize_to_versor(_random_versor(1)) + t2 = normalize_to_versor(_random_versor(2)) + r2 = normalize_to_versor(_random_versor(3)) + m1 = PersonaMotor(t1, r1) + m2 = PersonaMotor(t2, r2) + composed = m1.compose(m2) + assert versor_condition(composed.M) < 1e-4 + + +def test_from_concept_vector(): + """PersonaMotor.from_concept_vector should not raise and produces a valid motor.""" + concept = np.array([0.5, -0.3, 0.8], dtype=np.float32) + motor = PersonaMotor.from_concept_vector(concept) + assert versor_condition(motor.M) < 1e-4 diff --git a/tests/test_null_cone.py b/tests/test_null_cone.py new file mode 100644 index 00000000..99cf99ca --- /dev/null +++ b/tests/test_null_cone.py @@ -0,0 +1,39 @@ +import numpy as np +import pytest + +from algebra.cga import embed_point, is_null, null_project, cga_inner + + +def test_embedded_point_is_null(): + x = np.array([1.0, 2.0, 3.0], dtype=np.float32) + X = embed_point(x) + assert is_null(X), f"Embedded point not null: cga_inner(X,X)={cga_inner(X,X):.2e}" + + +def test_origin_is_null(): + X = embed_point(np.zeros(3, dtype=np.float32)) + assert is_null(X) + + +def test_null_project_restores_null(): + x = np.array([1.0, 2.0, 3.0], dtype=np.float32) + X = embed_point(x) + rng = np.random.default_rng(0) + X_drifted = X + rng.standard_normal(32).astype(np.float32) * 0.01 + X_fixed = null_project(X_drifted) + assert is_null(X_fixed), f"null_project failed: {cga_inner(X_fixed, X_fixed):.2e}" + + +def test_cga_inner_symmetry(): + X = embed_point(np.array([1.0, 0.0, 0.0])) + Y = embed_point(np.array([0.0, 1.0, 0.0])) + assert abs(cga_inner(X, Y) - cga_inner(Y, X)) < 1e-6 + + +def test_cga_inner_distance_identity(): + """cga_inner(X, Y) = -d^2 / 2 for unit-distance points.""" + X = embed_point(np.array([0.0, 0.0, 0.0])) + Y = embed_point(np.array([1.0, 0.0, 0.0])) + inner = cga_inner(X, Y) + # d=1, so expected = -0.5 + assert abs(inner - (-0.5)) < 1e-5, f"Expected -0.5, got {inner}" diff --git a/tests/test_vault_recall.py b/tests/test_vault_recall.py new file mode 100644 index 00000000..0df488c8 --- /dev/null +++ b/tests/test_vault_recall.py @@ -0,0 +1,46 @@ +import numpy as np +import pytest + +from algebra.versor import normalize_to_versor +from algebra.cga import is_null +from vault.store import VaultStore + + +def _random_versor(seed=0) -> np.ndarray: + rng = np.random.default_rng(seed) + return normalize_to_versor(rng.standard_normal(32).astype(np.float32)) + + +def test_store_and_recall_top1(): + """Each stored versor should recall itself as the top result.""" + vault = VaultStore() + versors = [_random_versor(i) for i in range(20)] + for i, v in enumerate(versors): + vault.store(v, {"id": i}) + for i, v in enumerate(versors): + results = vault.recall(v, top_k=1) + assert results[0]["metadata"]["id"] == i, ( + f"Versor {i} did not recall itself as top result" + ) + + +def test_recall_empty_vault(): + vault = VaultStore() + result = vault.recall(_random_versor(), top_k=5) + assert result == [] + + +def test_reproject_maintains_structure(): + """Reproject should not lose stored entries.""" + vault = VaultStore() + for i in range(10): + vault.store(_random_versor(i), {"id": i}) + vault.reproject() + assert len(vault) == 10 + + +def test_vault_len(): + vault = VaultStore() + for i in range(5): + vault.store(_random_versor(i)) + assert len(vault) == 5 diff --git a/tests/test_versor_closure.py b/tests/test_versor_closure.py new file mode 100644 index 00000000..74674ede --- /dev/null +++ b/tests/test_versor_closure.py @@ -0,0 +1,53 @@ +""" +CRITICAL: This test must pass before any other file is extended. +It verifies the core algebraic invariant of the entire system. +""" + +import numpy as np +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +from algebra.versor import versor_apply, normalize_to_versor, versor_condition + + +def _random_versor(seed=None) -> np.ndarray: + rng = np.random.default_rng(seed) + raw = rng.standard_normal(32).astype(np.float32) + return normalize_to_versor(raw) + + +@given(st.integers(min_value=0, max_value=99)) +@settings(max_examples=100) +def test_versor_apply_preserves_manifold(seed): + """V*F*reverse(V) must be a versor if V and F are versors.""" + V = _random_versor(seed) + F = _random_versor(seed + 1000) + result = versor_apply(V, F) + cond = versor_condition(result) + assert cond < 1e-4, f"versor_apply broke the manifold: condition={cond:.2e}" + + +def test_normalize_produces_versor(): + raw = np.random.randn(32).astype(np.float32) + V = normalize_to_versor(raw) + assert versor_condition(V) < 1e-6 + + +def test_composition_closed(): + """Two sequential versor_apply calls stay on the manifold.""" + V1 = _random_versor(0) + V2 = _random_versor(1) + F = _random_versor(2) + F2 = versor_apply(V1, F) + F3 = versor_apply(V2, F2) + assert versor_condition(F3) < 1e-4 + + +def test_identity_versor(): + """Scalar 1 is a valid versor and applies as identity.""" + identity = np.zeros(32, dtype=np.float32) + identity[0] = 1.0 + F = _random_versor(42) + result = versor_apply(identity, F) + assert np.allclose(result, F, atol=1e-5)