cortexm 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- context_m.py +17 -0
- cortexm/__init__.py +45 -0
- cortexm/accel.py +403 -0
- cortexm/api/__init__.py +0 -0
- cortexm/api/chaos.py +118 -0
- cortexm/api/memory.py +635 -0
- cortexm/bench/__init__.py +0 -0
- cortexm/bench/abilities.py +311 -0
- cortexm/bench/baselines.py +89 -0
- cortexm/bench/beam_loader.py +317 -0
- cortexm/bench/generator.py +376 -0
- cortexm/bench/harness.py +211 -0
- cortexm/bench/messy.py +218 -0
- cortexm/bench/micro.py +251 -0
- cortexm/bench/ood.py +443 -0
- cortexm/bench/run.py +137 -0
- cortexm/bridge/__init__.py +0 -0
- cortexm/bridge/dates.py +178 -0
- cortexm/bridge/decoders.py +204 -0
- cortexm/bridge/enrich.py +255 -0
- cortexm/bridge/extractor.py +316 -0
- cortexm/bridge/fallback.py +332 -0
- cortexm/bridge/onnx_runtime.py +158 -0
- cortexm/bridge/patterns.py +760 -0
- cortexm/bridge/ppr.py +104 -0
- cortexm/bridge/prefilter.py +188 -0
- cortexm/bridge/query_extract.py +420 -0
- cortexm/bridge/reader.py +1174 -0
- cortexm/bridge/rerank.py +204 -0
- cortexm/bridge/writer.py +492 -0
- cortexm/cli.py +295 -0
- cortexm/cognition/__init__.py +53 -0
- cortexm/cognition/abstraction.py +192 -0
- cortexm/cognition/analogy.py +159 -0
- cortexm/cognition/engine.py +204 -0
- cortexm/cognition/gaps.py +365 -0
- cortexm/cognition/scanner.py +204 -0
- cortexm/config.py +375 -0
- cortexm/cortexm.py +8 -0
- cortexm/enterprise/__init__.py +0 -0
- cortexm/enterprise/audit.py +178 -0
- cortexm/enterprise/governance.py +239 -0
- cortexm/errors.py +35 -0
- cortexm/features/__init__.py +0 -0
- cortexm/features/git.py +204 -0
- cortexm/features/prefetch.py +88 -0
- cortexm/features/zk.py +105 -0
- cortexm/federation/__init__.py +39 -0
- cortexm/federation/crdt.py +275 -0
- cortexm/federation/fabric.py +109 -0
- cortexm/federation/hlc.py +80 -0
- cortexm/federation/node.py +145 -0
- cortexm/federation/schema_report.py +73 -0
- cortexm/federation/transport.py +164 -0
- cortexm/index/__init__.py +19 -0
- cortexm/index/nsg.py +386 -0
- cortexm/mcp/__init__.py +0 -0
- cortexm/mcp/server.py +985 -0
- cortexm/metrics.py +62 -0
- cortexm/migrate/__init__.py +0 -0
- cortexm/migrate/importers.py +192 -0
- cortexm/provenance/__init__.py +78 -0
- cortexm/provenance/agent.py +214 -0
- cortexm/provenance/cose.py +201 -0
- cortexm/provenance/scitt.py +258 -0
- cortexm/provenance/vc.py +250 -0
- cortexm/security/__init__.py +0 -0
- cortexm/security/crypto.py +162 -0
- cortexm/security/hashes.py +140 -0
- cortexm/security/injection.py +149 -0
- cortexm/security/mind.py +154 -0
- cortexm/security/pii.py +265 -0
- cortexm/security/rbac.py +169 -0
- cortexm/security/sandbox.py +131 -0
- cortexm/security/zk_hamming.py +142 -0
- cortexm/security/zk_sql.py +485 -0
- cortexm/server/__init__.py +0 -0
- cortexm/server/metrics.py +88 -0
- cortexm/server/rest.py +936 -0
- cortexm/server/sparql.py +984 -0
- cortexm/text/__init__.py +0 -0
- cortexm/text/dissim.py +252 -0
- cortexm/text/embedder.py +155 -0
- cortexm/text/fuzzy.py +218 -0
- cortexm/text/idiolect.py +253 -0
- cortexm/text/labse.py +374 -0
- cortexm/text/tokenizer.py +79 -0
- cortexm/trace/__init__.py +0 -0
- cortexm/trace/blob_arena.py +277 -0
- cortexm/trace/consolidate.py +337 -0
- cortexm/trace/contradictions.py +69 -0
- cortexm/trace/dedup.py +114 -0
- cortexm/trace/edges.py +214 -0
- cortexm/trace/fact.py +121 -0
- cortexm/trace/fade.py +245 -0
- cortexm/trace/lifecycle.py +112 -0
- cortexm/trace/rebuild.py +173 -0
- cortexm/trace/rules.py +171 -0
- cortexm/trace/store.py +680 -0
- cortexm/trace/structural.py +183 -0
- cortexm/trace/tmt.py +335 -0
- cortexm/util.py +148 -0
- cortexm/vsa/__init__.py +0 -0
- cortexm/vsa/attribution.py +149 -0
- cortexm/vsa/cleanup.py +161 -0
- cortexm/vsa/codecs.py +397 -0
- cortexm/vsa/hologram_overlay.py +139 -0
- cortexm/vsa/index.py +163 -0
- cortexm/vsa/ops.py +149 -0
- cortexm/vsa/palace.py +446 -0
- cortexm/vsa/role_vectors.py +236 -0
- cortexm/vsa/slb.py +78 -0
- cortexm/vsa/tlsh_trie.py +137 -0
- cortexm/vsa/working_memory.py +249 -0
- cortexm-0.3.0.dist-info/METADATA +482 -0
- cortexm-0.3.0.dist-info/RECORD +120 -0
- cortexm-0.3.0.dist-info/WHEEL +5 -0
- cortexm-0.3.0.dist-info/entry_points.txt +2 -0
- cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
- cortexm-0.3.0.dist-info/top_level.txt +2 -0
context_m.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Backward-compat shim. The canonical module is now ``cortexm``.
|
|
2
|
+
|
|
3
|
+
This file exists so existing scripts that did::
|
|
4
|
+
|
|
5
|
+
from context_m import Memory
|
|
6
|
+
|
|
7
|
+
keep working after `pip install cortexm`. New code should use::
|
|
8
|
+
|
|
9
|
+
from cortexm import Memory
|
|
10
|
+
|
|
11
|
+
The shim will be removed in a future major release; migrate at your
|
|
12
|
+
leisure. The shim imports lazily so it adds ~0ms to cold-start when
|
|
13
|
+
nobody uses the old name.
|
|
14
|
+
"""
|
|
15
|
+
from cortexm import Memory, Config, __version__
|
|
16
|
+
|
|
17
|
+
__all__ = ["Memory", "Config", "__version__"]
|
cortexm/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Context-M — The Universal Neuro-Symbolic Memory Fabric.
|
|
2
|
+
|
|
3
|
+
Layer 1 Symbolic Trace : bi-temporal fact graph with contradiction
|
|
4
|
+
resolution, temporal edges, Datalog-lite rules.
|
|
5
|
+
Layer 2 VSA Memory Palace: holographic reduced representations (HRR) with
|
|
6
|
+
INT8 / Binary-HRR / RaBitQ / PQ codecs, a
|
|
7
|
+
page-clustered tree index and a semantic
|
|
8
|
+
lookaside buffer (SLB).
|
|
9
|
+
Bridge : μ=0 deterministic ingest (zero LLM calls), neuro-symbolic read
|
|
10
|
+
path with cryptographic provenance on every retrieval.
|
|
11
|
+
|
|
12
|
+
Mem0-compatible surface: ``from cortexm import Memory``
|
|
13
|
+
Alias (plan naming): ``from cortexm import Memory``
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
# μ=0 protocol counter: number of LLM invocations used by this process.
|
|
21
|
+
# The BEAM-honest protocol requires this to stay 0 during ingest & retrieval.
|
|
22
|
+
LLM_CALLS = 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _lazy_memory():
|
|
26
|
+
from cortexm.api.memory import Memory
|
|
27
|
+
|
|
28
|
+
return Memory
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def __getattr__(name: str):
|
|
32
|
+
if name == "Memory":
|
|
33
|
+
return _lazy_memory()
|
|
34
|
+
if name == "Config":
|
|
35
|
+
from cortexm.config import Config
|
|
36
|
+
|
|
37
|
+
return Config
|
|
38
|
+
if name == "LLM_CALLS":
|
|
39
|
+
from cortexm import metrics
|
|
40
|
+
|
|
41
|
+
return metrics.llm_calls()
|
|
42
|
+
raise AttributeError(name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ["Memory", "Config", "LLM_CALLS", "__version__"]
|
cortexm/accel.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Opportunistic Rust acceleration.
|
|
2
|
+
|
|
3
|
+
The Python/NumPy implementation is the REFERENCE and always works. If the
|
|
4
|
+
compiled wheels are installed (`pip install ./rust/cortexm-core` and
|
|
5
|
+
`pip install ./rust/quadrant`), the hot paths route through them:
|
|
6
|
+
|
|
7
|
+
* ``hashing.h64`` — keyed BLAKE2b (byte-exact parity, tested)
|
|
8
|
+
* ``vsa.bind/unbind`` — permutation gather with injected perms
|
|
9
|
+
* ``vsa.encode_fact`` — fused bind×3 + bundle + lexical mix + norm
|
|
10
|
+
* ``slb`` — L1-resident lookaside buffer
|
|
11
|
+
* ``quadrant`` — page-clustered log-depth vector index
|
|
12
|
+
|
|
13
|
+
Design rule: the Rust side never GENERATES randomness — permutations and
|
|
14
|
+
role vectors are injected from Python's deterministic VSA state, so a
|
|
15
|
+
mixed Python/Rust deployment produces bit-identical holograms. Every
|
|
16
|
+
accelerated path has a NumPy fallback and a parity test
|
|
17
|
+
(``tests/test_rust_accel.py``).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
# --- optional wheels -------------------------------------------------------
|
|
26
|
+
try: # pragma: no cover
|
|
27
|
+
import cortexm_core as _core # type: ignore
|
|
28
|
+
except Exception: # pragma: no cover
|
|
29
|
+
_core = None
|
|
30
|
+
|
|
31
|
+
try: # pragma: no cover
|
|
32
|
+
import quadrant as _quadrant # type: ignore
|
|
33
|
+
except Exception: # pragma: no cover
|
|
34
|
+
_quadrant = None
|
|
35
|
+
|
|
36
|
+
RUST_AVAILABLE = _core is not None
|
|
37
|
+
QUADRANT_AVAILABLE = _quadrant is not None
|
|
38
|
+
_env = os.environ.get("CONTEXTM_RUST", "auto")
|
|
39
|
+
RUST_ENABLED = (_env == "1") or (_env == "auto" and RUST_AVAILABLE)
|
|
40
|
+
QUADRANT_ENABLED = (_env == "1") or (_env == "auto" and QUADRANT_AVAILABLE)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rust_status() -> dict[str, Any]:
|
|
44
|
+
return {
|
|
45
|
+
"cortexm_core": bool(RUST_AVAILABLE and RUST_ENABLED),
|
|
46
|
+
"quadrant": bool(QUADRANT_AVAILABLE and QUADRANT_ENABLED),
|
|
47
|
+
"env": _env,
|
|
48
|
+
"hint": ("" if RUST_AVAILABLE else
|
|
49
|
+
"build with: pip install ./rust/cortexm-core "
|
|
50
|
+
"./rust/quadrant (requires cargo + maturin)"),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# --- hashing ---------------------------------------------------------------
|
|
55
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
56
|
+
def h64(feature: str, seed: int = 0) -> int:
|
|
57
|
+
return _core.h64(feature, seed)
|
|
58
|
+
else: # pragma: no cover
|
|
59
|
+
from cortexm.util import h64 # noqa: F401 (NumPy reference)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# --- VSA acceleration wrapper ----------------------------------------------
|
|
63
|
+
class RustVSA:
|
|
64
|
+
"""Binds a Python VSA to compiled bind/unbind/encode_fact.
|
|
65
|
+
|
|
66
|
+
The authoritative perms live in the Python VSA; Rust receives them
|
|
67
|
+
once via set_perm and accelerates the per-fact gathers afterwards.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, vsa) -> None:
|
|
71
|
+
if not (RUST_AVAILABLE and RUST_ENABLED):
|
|
72
|
+
raise RuntimeError("rust acceleration disabled or unavailable")
|
|
73
|
+
self.vsa = vsa
|
|
74
|
+
self._pb = _core.PermBindings(vsa.dims)
|
|
75
|
+
self._injected: set[str] = set()
|
|
76
|
+
|
|
77
|
+
def _inject(self, role: str) -> None:
|
|
78
|
+
if role not in self._injected:
|
|
79
|
+
self._pb.set_perm(role, self.vsa.perm(role).tolist())
|
|
80
|
+
self._injected.add(role)
|
|
81
|
+
|
|
82
|
+
def bind(self, role: str, filler):
|
|
83
|
+
import numpy as np
|
|
84
|
+
self._inject(role)
|
|
85
|
+
f = np.ascontiguousarray(filler, dtype=np.float32)
|
|
86
|
+
return self._pb.bind(role, f)
|
|
87
|
+
|
|
88
|
+
def unbind(self, role: str, h):
|
|
89
|
+
import numpy as np
|
|
90
|
+
self._inject(role)
|
|
91
|
+
hv = np.ascontiguousarray(h, dtype=np.float32)
|
|
92
|
+
return self._pb.unbind(role, hv)
|
|
93
|
+
|
|
94
|
+
def encode_fact(self, s_vec, r_vec, v_vec):
|
|
95
|
+
import numpy as np
|
|
96
|
+
for role in ("S", "R", "V"):
|
|
97
|
+
self._inject(role)
|
|
98
|
+
return self._pb.encode_fact(
|
|
99
|
+
np.ascontiguousarray(s_vec, dtype=np.float32),
|
|
100
|
+
np.ascontiguousarray(r_vec, dtype=np.float32),
|
|
101
|
+
np.ascontiguousarray(v_vec, dtype=np.float32),
|
|
102
|
+
self.vsa.lam)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# --- quadrant index wrapper --------------------------------------------------
|
|
106
|
+
class QuadrantANN:
|
|
107
|
+
"""Page-clustered log-depth index over holograms (approximate top-k).
|
|
108
|
+
|
|
109
|
+
Falls back to exact numpy when the wheel is absent — callers get the
|
|
110
|
+
same interface either way.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, vectors, page_capacity: int = 64) -> None:
|
|
114
|
+
import numpy as np
|
|
115
|
+
self.vectors = np.ascontiguousarray(vectors, dtype=np.float32)
|
|
116
|
+
if QUADRANT_AVAILABLE and QUADRANT_ENABLED:
|
|
117
|
+
self._idx = _quadrant.QuadrantIndex.build(
|
|
118
|
+
self.vectors, page_capacity)
|
|
119
|
+
self._exact = False
|
|
120
|
+
else: # pragma: no cover
|
|
121
|
+
self._idx = None
|
|
122
|
+
self._exact = True
|
|
123
|
+
|
|
124
|
+
def search(self, query, k: int = 10, max_leaves: int = 16):
|
|
125
|
+
import numpy as np
|
|
126
|
+
q = np.ascontiguousarray(query, dtype=np.float32)
|
|
127
|
+
if self._exact: # pragma: no cover
|
|
128
|
+
sims = self.vectors @ q
|
|
129
|
+
part = np.argpartition(-sims, k)[:k]
|
|
130
|
+
order = part[np.argsort(-sims[part])]
|
|
131
|
+
return order.tolist(), sims[order].tolist()
|
|
132
|
+
ids, scores = self._idx.search(q, k, max_leaves)
|
|
133
|
+
return list(ids), list(scores)
|
|
134
|
+
|
|
135
|
+
def stats(self) -> dict:
|
|
136
|
+
if self._exact: # pragma: no cover
|
|
137
|
+
n = self.vectors.shape[0]
|
|
138
|
+
return {"n_vectors": n, "mode": "exact-numpy",
|
|
139
|
+
"note": "quadrant wheel not installed"}
|
|
140
|
+
return {"mode": "quadrant", "detail": self._idx.stats()}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# --- explicit binary/FP32 tiering ------------------------------------------
|
|
144
|
+
# User concern (architectural fix #1): "Binary HRR + FFT doesn't work →
|
|
145
|
+
# Separate tiers: binary for edge, FP32 for cloud."
|
|
146
|
+
# The codec stack already supports both (binary/RaBitQ for edge, PQ/INT8
|
|
147
|
+
# for cloud); this makes the routing EXPLICIT based on deployment tier.
|
|
148
|
+
|
|
149
|
+
EDGE_CODECS = ("binary", "rabitq") # 96 B/v — fits on Raspberry Pi 5
|
|
150
|
+
CLOUD_CODECS = ("pq", "int8") # 8 B/v or 770 B/v — bandwidth-dense
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def detect_tier() -> str:
|
|
154
|
+
"""Auto-detect deployment tier from environment signals.
|
|
155
|
+
|
|
156
|
+
Returns 'edge' or 'cloud'. Override via CONTEXTM_TIER env var.
|
|
157
|
+
"""
|
|
158
|
+
explicit = os.environ.get("CONTEXTM_TIER", "").lower()
|
|
159
|
+
if explicit in ("edge", "cloud"):
|
|
160
|
+
return explicit
|
|
161
|
+
# heuristic: cloud = multi-core + >4GB RAM + has scipy
|
|
162
|
+
try:
|
|
163
|
+
import multiprocessing
|
|
164
|
+
if multiprocessing.cpu_count() >= 4:
|
|
165
|
+
import os as _os
|
|
166
|
+
mem = _os.sysconf("SC_PAGE_SIZE") * _os.sysconf("SC_PHYS_PAGES")
|
|
167
|
+
if mem >= 4 * 1024**3:
|
|
168
|
+
return "cloud"
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
return "edge"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def recommend_codec(tier: str | None = None, tmr: bool = True) -> str:
|
|
175
|
+
"""Recommend a codec for the deployment tier.
|
|
176
|
+
|
|
177
|
+
Edge: binary (with TMR if self-healing required) — 96 B/v.
|
|
178
|
+
Cloud: pq for bandwidth-dense, int8 for accuracy-critical.
|
|
179
|
+
"""
|
|
180
|
+
t = tier or detect_tier()
|
|
181
|
+
if t == "edge":
|
|
182
|
+
return "binary" if tmr else "rabitq"
|
|
183
|
+
return "pq"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def tier_status() -> dict:
|
|
187
|
+
"""Report current tier + recommended + active codec info."""
|
|
188
|
+
tier = detect_tier()
|
|
189
|
+
rec = recommend_codec(tier)
|
|
190
|
+
return {
|
|
191
|
+
"tier": tier,
|
|
192
|
+
"recommended_codec": rec,
|
|
193
|
+
"edge_codecs": list(EDGE_CODECS),
|
|
194
|
+
"cloud_codecs": list(CLOUD_CODECS),
|
|
195
|
+
"rust_enabled": RUST_ENABLED,
|
|
196
|
+
"quadrant_enabled": QUADRANT_ENABLED,
|
|
197
|
+
"note": (f"Auto-detected {tier} tier; recommend '{rec}' codec. "
|
|
198
|
+
f"Override with CONTEXTM_TIER=edge|cloud"),
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# --- SIMD kernel wrappers (Task 6-simd) ------------------------------------
|
|
203
|
+
# The Rust crate exposes `dot / dot_i8_f32 / cosine / l2_sq /
|
|
204
|
+
# batch_dot / batch_dot_i8 / topk / argmax` — each is a thin pyo3
|
|
205
|
+
# wrapper around the runtime-dispatched kernels in `rust/cortexm-core/
|
|
206
|
+
# src/simd.rs` (AVX-512 → AVX2+FMA → NEON → scalar). When the wheel is
|
|
207
|
+
# absent we fall back to numpy so callers always get a working answer.
|
|
208
|
+
#
|
|
209
|
+
# Design rule (mirrors RustVSA): the kernels NEVER allocate randomness
|
|
210
|
+
# and produce bit-compatible results across the Rust / NumPy paths (≤1e-5
|
|
211
|
+
# FP32 noise, asserted by `tests/test_rust_accel.py::TestSimdKernels`).
|
|
212
|
+
# Use the free-function form (`accel.cosine(a, b)`) for one-off queries;
|
|
213
|
+
# use the class form (`accel.SimdKernels().batch_dot(...)`) when you
|
|
214
|
+
# want to gate behaviour on `RUST_ENABLED` without re-checking globals.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _as_f32(x) -> "np.ndarray":
|
|
218
|
+
import numpy as np
|
|
219
|
+
return np.ascontiguousarray(x, dtype=np.float32)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def dot(a, b) -> float:
|
|
223
|
+
"""SIMD dot product. NumPy fallback: `np.dot(a, b)`."""
|
|
224
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
225
|
+
import numpy as np
|
|
226
|
+
return float(_core.dot(np.ascontiguousarray(a, dtype=np.float32),
|
|
227
|
+
np.ascontiguousarray(b, dtype=np.float32)))
|
|
228
|
+
import numpy as np
|
|
229
|
+
return float(np.dot(_as_f32(a), _as_f32(b)))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def dot_i8_f32(q8, q) -> float:
|
|
233
|
+
"""INT8 × f32 dot product. NumPy fallback casts int8 → f32 first."""
|
|
234
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
235
|
+
import numpy as np
|
|
236
|
+
return float(_core.dot_i8_f32(
|
|
237
|
+
np.ascontiguousarray(q8, dtype=np.int8),
|
|
238
|
+
np.ascontiguousarray(q, dtype=np.float32)))
|
|
239
|
+
import numpy as np
|
|
240
|
+
q8 = np.ascontiguousarray(q8, dtype=np.int8)
|
|
241
|
+
return float((q8.astype(np.float32) @ _as_f32(q)))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def cosine(a, b) -> float:
|
|
245
|
+
"""Cosine similarity via SIMD-accelerated dot + L2 norms.
|
|
246
|
+
|
|
247
|
+
Identical vectors return exactly 1.0 (bit-exact through the
|
|
248
|
+
SIMD self-dot path); distinct vectors agree with numpy's
|
|
249
|
+
`a @ b / (|a|·|b|)` to within 1e-5 (FP32 lane-reduction noise).
|
|
250
|
+
"""
|
|
251
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
252
|
+
import numpy as np
|
|
253
|
+
return float(_core.cosine(
|
|
254
|
+
np.ascontiguousarray(a, dtype=np.float32),
|
|
255
|
+
np.ascontiguousarray(b, dtype=np.float32)))
|
|
256
|
+
import numpy as np
|
|
257
|
+
a = _as_f32(a)
|
|
258
|
+
b = _as_f32(b)
|
|
259
|
+
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-12))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def l2_sq(a, b) -> float:
|
|
263
|
+
"""Squared L2 distance `sum((a-b)**2)`. 0.0 for identical vectors."""
|
|
264
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
265
|
+
import numpy as np
|
|
266
|
+
return float(_core.l2_sq(
|
|
267
|
+
np.ascontiguousarray(a, dtype=np.float32),
|
|
268
|
+
np.ascontiguousarray(b, dtype=np.float32)))
|
|
269
|
+
import numpy as np
|
|
270
|
+
d = _as_f32(a) - _as_f32(b)
|
|
271
|
+
return float(d @ d)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def batch_dot(rows, q, n_rows: int, dims: int) -> list[float]:
|
|
275
|
+
"""Matrix-vector product over a flat `[n_rows × dims]` f32 slice.
|
|
276
|
+
|
|
277
|
+
Far more cache-friendly than calling `dot()` per row from Python —
|
|
278
|
+
one boundary crossing for the whole batch. Returns a plain list so
|
|
279
|
+
callers can `np.asarray(...)` if they want a contiguous array.
|
|
280
|
+
"""
|
|
281
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
282
|
+
import numpy as np
|
|
283
|
+
r = np.ascontiguousarray(rows, dtype=np.float32).reshape(-1)
|
|
284
|
+
q = np.ascontiguousarray(q, dtype=np.float32)
|
|
285
|
+
return list(_core.batch_dot(r, q, n_rows, dims))
|
|
286
|
+
import numpy as np
|
|
287
|
+
r = np.asarray(rows, dtype=np.float32).reshape(n_rows, dims)
|
|
288
|
+
q = _as_f32(q)
|
|
289
|
+
return (r @ q).tolist()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def batch_dot_i8(packed, q, n_rows: int, dims: int) -> list[float]:
|
|
293
|
+
"""INT8-packed rows × f32 query. Raw int8·f32 dot products — callers
|
|
294
|
+
apply per-row scales (the codec's aux array) themselves."""
|
|
295
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
296
|
+
import numpy as np
|
|
297
|
+
p = np.ascontiguousarray(packed, dtype=np.int8).reshape(-1)
|
|
298
|
+
q = np.ascontiguousarray(q, dtype=np.float32)
|
|
299
|
+
return list(_core.batch_dot_i8(p, q, n_rows, dims))
|
|
300
|
+
import numpy as np
|
|
301
|
+
p = np.asarray(packed, dtype=np.int8).reshape(n_rows, dims)
|
|
302
|
+
q = _as_f32(q)
|
|
303
|
+
return (p.astype(np.float32) @ q).tolist()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def topk(scores, k: int) -> list[tuple[int, float]]:
|
|
307
|
+
"""Top-`k` (idx, score) tuples in descending order. O(N) via
|
|
308
|
+
`select_nth_unstable`, then O(k log k) for the prefix sort."""
|
|
309
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
310
|
+
import numpy as np
|
|
311
|
+
s = np.ascontiguousarray(scores, dtype=np.float32)
|
|
312
|
+
return [(int(i), float(v)) for i, v in _core.topk(s, k)]
|
|
313
|
+
import numpy as np
|
|
314
|
+
s = np.asarray(scores, dtype=np.float32)
|
|
315
|
+
k = max(0, min(k, s.size))
|
|
316
|
+
if k == 0:
|
|
317
|
+
return []
|
|
318
|
+
part = np.argpartition(-s, k - 1)[:k]
|
|
319
|
+
part = part[np.argsort(-s[part])]
|
|
320
|
+
return [(int(i), float(s[i])) for i in part]
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def argmax(scores) -> tuple[int, float]:
|
|
324
|
+
"""Return (idx, value) of the max element. Ties → first occurrence."""
|
|
325
|
+
if RUST_AVAILABLE and RUST_ENABLED:
|
|
326
|
+
import numpy as np
|
|
327
|
+
s = np.ascontiguousarray(scores, dtype=np.float32)
|
|
328
|
+
i, v = _core.argmax(s)
|
|
329
|
+
return (int(i), float(v))
|
|
330
|
+
import numpy as np
|
|
331
|
+
s = np.asarray(scores, dtype=np.float32)
|
|
332
|
+
if s.size == 0:
|
|
333
|
+
return (0, 0.0)
|
|
334
|
+
i = int(np.argmax(s))
|
|
335
|
+
return (i, float(s[i]))
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class SimdKernels:
|
|
339
|
+
"""Container class that mirrors the Rust kernel surface so callers
|
|
340
|
+
can wire `accel.SimdKernels()` once and dispatch uniformly. The
|
|
341
|
+
NumPy fallbacks produce bit-identical results within 1e-5 (FP32
|
|
342
|
+
lane-reduction noise, asserted by parity tests).
|
|
343
|
+
|
|
344
|
+
Mirrors the existing `RustVSA` pattern: the constructor raises
|
|
345
|
+
`RuntimeError` if Rust is explicitly disabled (`CONTEXTM_RUST=0`)
|
|
346
|
+
— when Rust is *available* but the user opted out, callers should
|
|
347
|
+
use the free functions above instead.
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
def __init__(self) -> None:
|
|
351
|
+
if not (RUST_AVAILABLE and RUST_ENABLED):
|
|
352
|
+
raise RuntimeError(
|
|
353
|
+
"SimdKernels requires Rust acceleration enabled "
|
|
354
|
+
"(CONTEXTM_RUST=auto or =1, and the cortexm_core wheel built)")
|
|
355
|
+
|
|
356
|
+
def dot(self, a, b) -> float:
|
|
357
|
+
import numpy as np
|
|
358
|
+
return float(_core.dot(
|
|
359
|
+
np.ascontiguousarray(a, dtype=np.float32),
|
|
360
|
+
np.ascontiguousarray(b, dtype=np.float32)))
|
|
361
|
+
|
|
362
|
+
def dot_i8_f32(self, q8, q) -> float:
|
|
363
|
+
import numpy as np
|
|
364
|
+
return float(_core.dot_i8_f32(
|
|
365
|
+
np.ascontiguousarray(q8, dtype=np.int8),
|
|
366
|
+
np.ascontiguousarray(q, dtype=np.float32)))
|
|
367
|
+
|
|
368
|
+
def cosine(self, a, b) -> float:
|
|
369
|
+
import numpy as np
|
|
370
|
+
return float(_core.cosine(
|
|
371
|
+
np.ascontiguousarray(a, dtype=np.float32),
|
|
372
|
+
np.ascontiguousarray(b, dtype=np.float32)))
|
|
373
|
+
|
|
374
|
+
def l2_sq(self, a, b) -> float:
|
|
375
|
+
import numpy as np
|
|
376
|
+
return float(_core.l2_sq(
|
|
377
|
+
np.ascontiguousarray(a, dtype=np.float32),
|
|
378
|
+
np.ascontiguousarray(b, dtype=np.float32)))
|
|
379
|
+
|
|
380
|
+
def batch_dot(self, rows, q, n_rows: int, dims: int) -> list[float]:
|
|
381
|
+
import numpy as np
|
|
382
|
+
r = np.ascontiguousarray(rows, dtype=np.float32).reshape(-1)
|
|
383
|
+
q = np.ascontiguousarray(q, dtype=np.float32)
|
|
384
|
+
return list(_core.batch_dot(r, q, n_rows, dims))
|
|
385
|
+
|
|
386
|
+
def batch_dot_i8(self, packed, q, n_rows: int,
|
|
387
|
+
dims: int) -> list[float]:
|
|
388
|
+
import numpy as np
|
|
389
|
+
p = np.ascontiguousarray(packed, dtype=np.int8).reshape(-1)
|
|
390
|
+
q = np.ascontiguousarray(q, dtype=np.float32)
|
|
391
|
+
return list(_core.batch_dot_i8(p, q, n_rows, dims))
|
|
392
|
+
|
|
393
|
+
def topk(self, scores, k: int) -> list[tuple[int, float]]:
|
|
394
|
+
import numpy as np
|
|
395
|
+
s = np.ascontiguousarray(scores, dtype=np.float32)
|
|
396
|
+
return [(int(i), float(v)) for i, v in _core.topk(s, k)]
|
|
397
|
+
|
|
398
|
+
def argmax(self, scores) -> tuple[int, float]:
|
|
399
|
+
import numpy as np
|
|
400
|
+
s = np.ascontiguousarray(scores, dtype=np.float32)
|
|
401
|
+
i, v = _core.argmax(s)
|
|
402
|
+
return (int(i), float(v))
|
|
403
|
+
|
cortexm/api/__init__.py
ADDED
|
File without changes
|
cortexm/api/chaos.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Chaos mode — EAM-inspired zero-config auto-ingest.
|
|
2
|
+
|
|
3
|
+
arXiv insight (EAM / HeatherDB): the UX is "dump text in, intelligence
|
|
4
|
+
emerges." EAM conflates storage with inference — we reject that as
|
|
5
|
+
a black box. But the UX itself is the right default: most users
|
|
6
|
+
shouldn't have to tune patterns, configure idiolect dictionaries,
|
|
7
|
+
or wire bridges. They should just dump text in.
|
|
8
|
+
|
|
9
|
+
This module provides a one-call auto-ingest path:
|
|
10
|
+
|
|
11
|
+
chaos_ingest(mem, list_of_texts, user_id="default")
|
|
12
|
+
|
|
13
|
+
That runs the FULL pipeline with sensible defaults:
|
|
14
|
+
- per-user idiolect normalizer (built-in text-speak escape hatch)
|
|
15
|
+
- DisSim recursive simplification (splits compound sentences so
|
|
16
|
+
the pattern extractor sees one fact per clause)
|
|
17
|
+
- pattern extractor (μ=0, deterministic)
|
|
18
|
+
- lifecycle / contradiction / palace encoding / edges
|
|
19
|
+
|
|
20
|
+
The user can LATER opt into deterministic mode (call mem.add()
|
|
21
|
+
directly) when they want explicit control over the pipeline. This
|
|
22
|
+
matches EAM's "magic" UX without inheriting EAM's opacity — every
|
|
23
|
+
fact has full provenance, the Memory Git ancestry is preserved,
|
|
24
|
+
and the audit log records which sub-module extracted what.
|
|
25
|
+
|
|
26
|
+
Why the name: "chaos mode" because raw chaotic text → structured
|
|
27
|
+
facts, no user configuration required. Let EAM sell the magic;
|
|
28
|
+
Context-M keeps the receipts.
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import Iterable
|
|
33
|
+
|
|
34
|
+
from cortexm.config import Config
|
|
35
|
+
from cortexm.text.dissim import DisSimSplitter
|
|
36
|
+
from cortexm.text.embedder import HashingEmbedder
|
|
37
|
+
from cortexm.text.idiolect import PerUserIdiolectNormalizer
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Module-level singletons per Memory instance — idiolect + dissim are
|
|
41
|
+
# stateful (idiolect accumulates per-user slang observations; dissim
|
|
42
|
+
# is stateless but expensive to construct because it compiles regex).
|
|
43
|
+
# We cache one of each per Memory so the host doesn't pay construction
|
|
44
|
+
# cost on every chaos_ingest call.
|
|
45
|
+
_CACHE_ATTR = "_chaos_cache"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_cache(mem):
|
|
49
|
+
"""Lazy-init the chaos-mode cache on a Memory instance."""
|
|
50
|
+
if not hasattr(mem, _CACHE_ATTR):
|
|
51
|
+
embedder = HashingEmbedder(mem.palace.dims, mem.palace.cfg.seed)
|
|
52
|
+
setattr(mem, _CACHE_ATTR, {
|
|
53
|
+
"idiolect": PerUserIdiolectNormalizer(embedder),
|
|
54
|
+
"dissim": DisSimSplitter(max_depth=2),
|
|
55
|
+
"embedder": embedder,
|
|
56
|
+
})
|
|
57
|
+
return getattr(mem, _CACHE_ATTR)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def chaos_ingest(mem, texts, *, user_id: str = "default",
|
|
61
|
+
agent_id: str | None = None,
|
|
62
|
+
run_id: str | None = None) -> dict:
|
|
63
|
+
"""Auto-ingest raw text via the full unmess + dissim + writer pipeline.
|
|
64
|
+
|
|
65
|
+
For each text:
|
|
66
|
+
1. observe idiolect (per-user slang dictionary accumulates)
|
|
67
|
+
2. normalize via idiolect (text-speak + kNN slang → canonical)
|
|
68
|
+
3. split compound sentences via DisSim (recursive syntactic)
|
|
69
|
+
4. for each clause: writer.add() — chunk insert + pattern
|
|
70
|
+
extract + quarantine + lifecycle + palace encoding + edges
|
|
71
|
+
|
|
72
|
+
Returns a stats dict matching Memory.add()'s shape (so callers
|
|
73
|
+
can drop chaos_ingest in place of mem.add() without changes).
|
|
74
|
+
"""
|
|
75
|
+
cache = _get_cache(mem)
|
|
76
|
+
idiolect = cache["idiolect"]
|
|
77
|
+
dissim = cache["dissim"]
|
|
78
|
+
|
|
79
|
+
if isinstance(texts, str):
|
|
80
|
+
texts = [texts]
|
|
81
|
+
|
|
82
|
+
total_inserted = 0
|
|
83
|
+
all_results: list = []
|
|
84
|
+
total_tokens = 0
|
|
85
|
+
for text in texts:
|
|
86
|
+
if not text or not text.strip():
|
|
87
|
+
continue
|
|
88
|
+
# 1. observe idiolect
|
|
89
|
+
idiolect.observe(user_id, text)
|
|
90
|
+
# 2. normalize
|
|
91
|
+
norm = idiolect.normalize(user_id, text)
|
|
92
|
+
# 3. split into clauses
|
|
93
|
+
clauses = [c.text for c in (dissim.simplify_text(norm) or [norm])]
|
|
94
|
+
# 4. ingest each clause through the standard writer pipeline
|
|
95
|
+
for clause in clauses:
|
|
96
|
+
if not clause or not clause.strip():
|
|
97
|
+
continue
|
|
98
|
+
out = mem.add(
|
|
99
|
+
[{"role": "user", "content": clause}],
|
|
100
|
+
user_id=user_id, agent_id=agent_id, run_id=run_id)
|
|
101
|
+
total_inserted += out.get("stats", {}).get("facts_inserted", 0)
|
|
102
|
+
total_tokens += out.get("stats", {}).get("tokens", 0)
|
|
103
|
+
all_results.extend(out.get("results", []))
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
"event": "CHAOS_INGEST",
|
|
107
|
+
"results": all_results,
|
|
108
|
+
"commit": None, # multiple commits per call
|
|
109
|
+
"stats": {
|
|
110
|
+
"messages": len(texts),
|
|
111
|
+
"tokens": total_tokens,
|
|
112
|
+
"facts_inserted": total_inserted,
|
|
113
|
+
"llm_calls": 0,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
__all__ = ["chaos_ingest"]
|