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.
Files changed (120) hide show
  1. context_m.py +17 -0
  2. cortexm/__init__.py +45 -0
  3. cortexm/accel.py +403 -0
  4. cortexm/api/__init__.py +0 -0
  5. cortexm/api/chaos.py +118 -0
  6. cortexm/api/memory.py +635 -0
  7. cortexm/bench/__init__.py +0 -0
  8. cortexm/bench/abilities.py +311 -0
  9. cortexm/bench/baselines.py +89 -0
  10. cortexm/bench/beam_loader.py +317 -0
  11. cortexm/bench/generator.py +376 -0
  12. cortexm/bench/harness.py +211 -0
  13. cortexm/bench/messy.py +218 -0
  14. cortexm/bench/micro.py +251 -0
  15. cortexm/bench/ood.py +443 -0
  16. cortexm/bench/run.py +137 -0
  17. cortexm/bridge/__init__.py +0 -0
  18. cortexm/bridge/dates.py +178 -0
  19. cortexm/bridge/decoders.py +204 -0
  20. cortexm/bridge/enrich.py +255 -0
  21. cortexm/bridge/extractor.py +316 -0
  22. cortexm/bridge/fallback.py +332 -0
  23. cortexm/bridge/onnx_runtime.py +158 -0
  24. cortexm/bridge/patterns.py +760 -0
  25. cortexm/bridge/ppr.py +104 -0
  26. cortexm/bridge/prefilter.py +188 -0
  27. cortexm/bridge/query_extract.py +420 -0
  28. cortexm/bridge/reader.py +1174 -0
  29. cortexm/bridge/rerank.py +204 -0
  30. cortexm/bridge/writer.py +492 -0
  31. cortexm/cli.py +295 -0
  32. cortexm/cognition/__init__.py +53 -0
  33. cortexm/cognition/abstraction.py +192 -0
  34. cortexm/cognition/analogy.py +159 -0
  35. cortexm/cognition/engine.py +204 -0
  36. cortexm/cognition/gaps.py +365 -0
  37. cortexm/cognition/scanner.py +204 -0
  38. cortexm/config.py +375 -0
  39. cortexm/cortexm.py +8 -0
  40. cortexm/enterprise/__init__.py +0 -0
  41. cortexm/enterprise/audit.py +178 -0
  42. cortexm/enterprise/governance.py +239 -0
  43. cortexm/errors.py +35 -0
  44. cortexm/features/__init__.py +0 -0
  45. cortexm/features/git.py +204 -0
  46. cortexm/features/prefetch.py +88 -0
  47. cortexm/features/zk.py +105 -0
  48. cortexm/federation/__init__.py +39 -0
  49. cortexm/federation/crdt.py +275 -0
  50. cortexm/federation/fabric.py +109 -0
  51. cortexm/federation/hlc.py +80 -0
  52. cortexm/federation/node.py +145 -0
  53. cortexm/federation/schema_report.py +73 -0
  54. cortexm/federation/transport.py +164 -0
  55. cortexm/index/__init__.py +19 -0
  56. cortexm/index/nsg.py +386 -0
  57. cortexm/mcp/__init__.py +0 -0
  58. cortexm/mcp/server.py +985 -0
  59. cortexm/metrics.py +62 -0
  60. cortexm/migrate/__init__.py +0 -0
  61. cortexm/migrate/importers.py +192 -0
  62. cortexm/provenance/__init__.py +78 -0
  63. cortexm/provenance/agent.py +214 -0
  64. cortexm/provenance/cose.py +201 -0
  65. cortexm/provenance/scitt.py +258 -0
  66. cortexm/provenance/vc.py +250 -0
  67. cortexm/security/__init__.py +0 -0
  68. cortexm/security/crypto.py +162 -0
  69. cortexm/security/hashes.py +140 -0
  70. cortexm/security/injection.py +149 -0
  71. cortexm/security/mind.py +154 -0
  72. cortexm/security/pii.py +265 -0
  73. cortexm/security/rbac.py +169 -0
  74. cortexm/security/sandbox.py +131 -0
  75. cortexm/security/zk_hamming.py +142 -0
  76. cortexm/security/zk_sql.py +485 -0
  77. cortexm/server/__init__.py +0 -0
  78. cortexm/server/metrics.py +88 -0
  79. cortexm/server/rest.py +936 -0
  80. cortexm/server/sparql.py +984 -0
  81. cortexm/text/__init__.py +0 -0
  82. cortexm/text/dissim.py +252 -0
  83. cortexm/text/embedder.py +155 -0
  84. cortexm/text/fuzzy.py +218 -0
  85. cortexm/text/idiolect.py +253 -0
  86. cortexm/text/labse.py +374 -0
  87. cortexm/text/tokenizer.py +79 -0
  88. cortexm/trace/__init__.py +0 -0
  89. cortexm/trace/blob_arena.py +277 -0
  90. cortexm/trace/consolidate.py +337 -0
  91. cortexm/trace/contradictions.py +69 -0
  92. cortexm/trace/dedup.py +114 -0
  93. cortexm/trace/edges.py +214 -0
  94. cortexm/trace/fact.py +121 -0
  95. cortexm/trace/fade.py +245 -0
  96. cortexm/trace/lifecycle.py +112 -0
  97. cortexm/trace/rebuild.py +173 -0
  98. cortexm/trace/rules.py +171 -0
  99. cortexm/trace/store.py +680 -0
  100. cortexm/trace/structural.py +183 -0
  101. cortexm/trace/tmt.py +335 -0
  102. cortexm/util.py +148 -0
  103. cortexm/vsa/__init__.py +0 -0
  104. cortexm/vsa/attribution.py +149 -0
  105. cortexm/vsa/cleanup.py +161 -0
  106. cortexm/vsa/codecs.py +397 -0
  107. cortexm/vsa/hologram_overlay.py +139 -0
  108. cortexm/vsa/index.py +163 -0
  109. cortexm/vsa/ops.py +149 -0
  110. cortexm/vsa/palace.py +446 -0
  111. cortexm/vsa/role_vectors.py +236 -0
  112. cortexm/vsa/slb.py +78 -0
  113. cortexm/vsa/tlsh_trie.py +137 -0
  114. cortexm/vsa/working_memory.py +249 -0
  115. cortexm-0.3.0.dist-info/METADATA +482 -0
  116. cortexm-0.3.0.dist-info/RECORD +120 -0
  117. cortexm-0.3.0.dist-info/WHEEL +5 -0
  118. cortexm-0.3.0.dist-info/entry_points.txt +2 -0
  119. cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
  120. cortexm-0.3.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,211 @@
1
+ """BEAM-style harness runner.
2
+
3
+ Protocol (mirrors the plan's Phase 2 "Proof of God" benchmark):
4
+ 1. Generate a seeded synthetic conversation at bucket scale
5
+ (128K / 500K / 1M / 10M estimated tokens).
6
+ 2. Ingest under the μ=0 protocol — zero LLM calls, asserted.
7
+ 3. Probe with questions across the 10 BEAM abilities.
8
+ 4. Score with the deterministic nugget judge (offline, reproducible).
9
+ 5. Compare against BM25-RAG and vector-only baselines.
10
+ 6. Report per-ability accuracy, ingest throughput, storage, latency,
11
+ and trust metrics (provenance completeness, audit latency, cost).
12
+
13
+ A pluggable LLM reader/judge slot (canonical protocol: gpt-5 reader +
14
+ judge) is exposed via ``llm_judge=None``; the deterministic judge keeps
15
+ the whole harness honest and free.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import time
22
+ from dataclasses import dataclass, field
23
+ from datetime import datetime, timezone
24
+
25
+ from cortexm import metrics
26
+ from cortexm.api.memory import Memory
27
+ from cortexm.bench.abilities import (ABILITY_NAMES, ABILITIES, build_probes,
28
+ judge)
29
+ from cortexm.bench.baselines import BM25Index, bm25_context, vector_only_context
30
+ from cortexm.bench.generator import generate
31
+
32
+
33
+ @dataclass
34
+ class BucketResult:
35
+ bucket: str
36
+ n_questions: int = 0
37
+ per_ability: dict = field(default_factory=dict)
38
+ per_system: dict = field(default_factory=dict)
39
+ ingest: dict = field(default_factory=dict)
40
+ corpus: dict = field(default_factory=dict)
41
+ trust: dict = field(default_factory=dict)
42
+
43
+ def to_dict(self) -> dict:
44
+ return {
45
+ "bucket": self.bucket, "n_questions": self.n_questions,
46
+ "per_ability": self.per_ability, "per_system": self.per_system,
47
+ "ingest": self.ingest, "corpus": self.corpus, "trust": self.trust,
48
+ }
49
+
50
+
51
+ def run_bucket(bucket: str, seed: int = 42, systems=("context_m", "vector_only", "bm25"),
52
+ db_path: str = ":memory:", max_probes: int | None = None,
53
+ llm_judge=None) -> BucketResult:
54
+ res = BucketResult(bucket=bucket)
55
+ t0 = time.time()
56
+ corpus = generate(bucket, seed=seed)
57
+ res.corpus = {
58
+ "sessions": len(corpus.sessions),
59
+ "personas": len(corpus.personas),
60
+ "estimated_tokens": corpus.total_tokens,
61
+ "generation_seconds": round(corpus.generation_seconds, 2),
62
+ }
63
+
64
+ from cortexm.config import Config as _Cfg
65
+ cfg = _Cfg(db_path=db_path) if db_path != ":memory:" else _Cfg()
66
+ cfg.apply_rules_each_add = False # bulk mode: Datalog after ingest
67
+ memory = Memory(cfg)
68
+ metrics.reset_counters()
69
+
70
+ # ---- ingest (μ=0) ----------------------------------------------------
71
+ t_ingest = time.time()
72
+ n_msgs = 0
73
+ for user_id, date, msgs in corpus.sessions:
74
+ payload = [{"role": role, "content": text, "timestamp": date}
75
+ for role, text in msgs]
76
+ n_msgs += len(msgs)
77
+ memory.add(payload, user_id=user_id, timestamp=date)
78
+ n_derived = memory.apply_rules()
79
+ ingest_s = time.time() - t_ingest
80
+ stats = memory.stats()
81
+ res.ingest = {
82
+ "wall_seconds": round(ingest_s, 2),
83
+ "messages": n_msgs,
84
+ "tokens_per_second": int(corpus.total_tokens / max(ingest_s, 1e-9)),
85
+ "messages_per_second": round(n_msgs / max(ingest_s, 1e-9), 1),
86
+ "llm_calls": metrics.counters()["llm_calls"],
87
+ "u0_protocol": stats["u0_protocol"],
88
+ "facts": stats["facts"],
89
+ "active_facts": stats["active_facts"],
90
+ "chunks": stats["chunks"],
91
+ "commits": stats["commits"],
92
+ "derived_facts": stats["derived"],
93
+ "deferred_rule_pass": True,
94
+ }
95
+
96
+ # ---- probes -----------------------------------------------------------
97
+ probes = build_probes(corpus.personas, __import__("random").Random(seed))
98
+ by_ability: dict[str, list] = {a: [] for a in ABILITIES}
99
+ for p in probes:
100
+ by_ability[p.ability].append(p)
101
+ if max_probes:
102
+ for a in ABILITIES:
103
+ by_ability[a] = by_ability[a][:max_probes]
104
+ res.n_questions = sum(len(v) for v in by_ability.values())
105
+
106
+ # ---- baseline indexes ---------------------------------------------------
107
+ chunks_by_user: dict[str, list[dict]] = {}
108
+ for user_id, date, msgs in corpus.sessions:
109
+ docs = chunks_by_user.setdefault(user_id, [])
110
+ for i, (role, text) in enumerate(msgs):
111
+ if len(text) > 12: # skip tiny interjections
112
+ docs.append({"id": f"{user_id}:{len(docs)}", "text": text})
113
+ bm25 = {uid: BM25Index(docs) for uid, docs in chunks_by_user.items()}
114
+
115
+ # ---- evaluate ---------------------------------------------------------
116
+ per_system: dict[str, dict[str, float]] = {s: {a: 0.0 for a in ABILITIES}
117
+ for s in systems}
118
+ counts = {a: len(by_ability[a]) for a in ABILITIES}
119
+ details: list[dict] = []
120
+ latencies = {s: [] for s in systems}
121
+ provenance_ok = 0
122
+ provenance_checks = 0
123
+
124
+ for ability in ABILITIES:
125
+ for probe in by_ability[ability]:
126
+ for system in systems:
127
+ t_q = time.time()
128
+ if system == "context_m":
129
+ out = memory.search(probe.question, user_id=probe.user_id, k=12)
130
+ context = out["context_block"]
131
+ provenance_checks += 1
132
+ if out["provenance"]["verification"]:
133
+ provenance_ok += 1
134
+ elif system == "vector_only":
135
+ context = vector_only_context(memory, probe.question,
136
+ probe.user_id)
137
+ elif system == "bm25":
138
+ context = bm25_context(bm25[probe.user_id], probe.question)
139
+ latencies[system].append(time.time() - t_q)
140
+ score_fn = llm_judge if llm_judge else judge
141
+ score, detail = score_fn(probe, context)
142
+ per_system[system][ability] += score
143
+ if system == "context_m":
144
+ details.append({"ability": ability,
145
+ "question": probe.question,
146
+ "score": score, "detail": detail,
147
+ "context": context[:400]})
148
+
149
+ for system in systems:
150
+ res.per_system[system] = {
151
+ "overall": round(sum(per_system[system][a] for a in ABILITIES)
152
+ / max(res.n_questions, 1), 4),
153
+ "per_ability": {
154
+ a: round(per_system[system][a] / max(counts[a], 1), 4)
155
+ for a in ABILITIES if counts[a]
156
+ },
157
+ "mean_latency_ms": round(
158
+ sum(latencies[system]) / max(len(latencies[system]), 1) * 1e3, 2),
159
+ }
160
+ res.per_ability = res.per_system.get("context_m", {}).get("per_ability", {})
161
+ res.trust = {
162
+ "provenance_completeness": round(
163
+ provenance_ok / max(provenance_checks, 1), 4),
164
+ "audit_latency_ms": round(
165
+ sum(latencies.get("context_m", [])) /
166
+ max(len(latencies.get("context_m", [])), 1) * 1e3, 2),
167
+ "u0_ingest_llm_calls": metrics.counters()["llm_calls"],
168
+ "storage": memory.storage_stats(),
169
+ "hash_provider": stats["hash_provider"],
170
+ "codec": stats["codec"],
171
+ "vsa_mode": stats["vsa_mode"],
172
+ "wall_seconds_total": round(time.time() - t0, 2),
173
+ }
174
+ memory.close()
175
+ res.details = details # type: ignore[attr-defined]
176
+ return res
177
+
178
+
179
+ def format_report(results: list[BucketResult]) -> str:
180
+ lines = ["# Context-M — BEAM-Style Benchmark Results", ""]
181
+ for r in results:
182
+ lines.append(f"## Bucket: {r.bucket.upper()} "
183
+ f"({r.corpus['estimated_tokens']:,} est. tokens, "
184
+ f"{r.n_questions} questions)")
185
+ lines.append("")
186
+ sys_names = list(r.per_system.keys())
187
+ header = "| System | Overall | " + " | ".join(ABILITIES) + " |"
188
+ sep = "|---" * (2 + len(ABILITIES)) + "|"
189
+ lines.append(header)
190
+ lines.append(sep)
191
+ for s in sys_names:
192
+ d = r.per_system[s]
193
+ row = [f"**{d['overall']:.1%}**" if s == "context_m" else f"{d['overall']:.1%}"]
194
+ for a in ABILITIES:
195
+ v = d["per_ability"].get(a)
196
+ row.append(f"{v:.0%}" if v is not None else "—")
197
+ lines.append(f"| {s} | " + " | ".join(row) + " |")
198
+ lines.append("")
199
+ lines.append(f"- Ingest: {r.ingest['wall_seconds']}s for "
200
+ f"{r.ingest['tokens_per_second']:,} tokens/s "
201
+ f"(μ=0: {r.ingest['u0_protocol']}, "
202
+ f"{r.ingest['llm_calls']} LLM calls)")
203
+ lines.append(f"- Memory: {r.ingest['facts']:,} facts / "
204
+ f"{r.ingest['chunks']:,} chunks / "
205
+ f"{r.ingest['commits']:,} commits "
206
+ f"({r.ingest['derived_facts']} derived by Datalog)")
207
+ lines.append(f"- Provenance completeness: "
208
+ f"{r.trust['provenance_completeness']:.1%} | "
209
+ f"retrieval latency p50≈{r.trust['audit_latency_ms']}ms")
210
+ lines.append("")
211
+ return "\n".join(lines)
cortexm/bench/messy.py ADDED
@@ -0,0 +1,218 @@
1
+ """Messy persona generator — slang / compound sentences / misspellings.
2
+
3
+ The default ``cortexm.bench.generator`` produces clean, grammatical
4
+ persona messages ("My name is Alice. I work at Google as a software
5
+ engineer."). On that corpus, the μ=0 pattern extractor scores ~1.0
6
+ recall because every fact sits in a clean SVO clause. Unmess + dissim
7
+ have nothing to fix.
8
+
9
+ This module applies a *messifier* to the same persona timeline:
10
+ - run-on compound sentences stitched with "and" / "so" / "ngl" / "tbh"
11
+ - slang tokens: bruh, ngl, tbh, fr fr, no cap, smh, rn, nvm, wyd, ykwis
12
+ - common misspellings: defo, prolly, kinda, kinda-sorta, tmr, b4, 2
13
+ - text-speak: u / ur / 2 / 4 / k / lol / omg / lmao / rn
14
+ - code-switching: inject "yo", "tbh idk", "ngl that's wild" mid-sentence
15
+ - contraction chains: "I'ma", "I'd've", "shouldn't've"
16
+ - capitalization chaos: drop sentence-initial caps, ALL-CAPS bursts
17
+ - emoji-free but punctuation-sparse: drop periods, use commas instead
18
+
19
+ The output is intentionally HARD for clean pattern matchers — the
20
+ unmess / dissim / fuzzy / idiolect stack has actual work to do. This
21
+ is what the user asked for ("current synthetic corpus is too clean
22
+ to show the win") so the BEAM numbers actually move when the arxiv
23
+ improvements are toggled on.
24
+
25
+ Deterministic (seeded). Same persona → same messified text.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import random
30
+ from dataclasses import dataclass
31
+
32
+ from cortexm.bench.generator import (
33
+ Persona, make_persona, _month, SMALLTALK,
34
+ )
35
+
36
+
37
+ # slang / filler / discourse markers — injected at clause boundaries
38
+ SLANG_FILLERS = [
39
+ "ngl", "tbh", "fr fr", "no cap", "lowkey", "highkey", "smh",
40
+ "rn", "nvm", "wyd", "ykwis", "lowkey", "istg", "bruh", "bro",
41
+ "ye", "yeah", "ya", "ok so", "anyway", "like", "tbh idk",
42
+ "ngl that's wild", "ya feel me", "y'know", "like honestly",
43
+ ]
44
+
45
+ # text-speak substitutions (applied via simple regex / str.replace)
46
+ TEXTSPEAK = {
47
+ "you": "u", "your": "ur", "you're": "ur", "you are": "u r",
48
+ "to": "2", "too": "2", "for": "4", "before": "b4",
49
+ "tomorrow": "tmr", "definitely": "defo", "probably": "prolly",
50
+ "kind of": "kinda", "sort of": "sorta", "give me": "gimme",
51
+ "let me": "lemme", "want to": "wanna", "going to": "gonna",
52
+ "got to": "gotta", "out of": "outta", "because": "bc",
53
+ "don't know": "dk", "i don't know": "idk", "right now": "rn",
54
+ "with": "w/", "without": "w/o", "people": "ppl", "thanks": "thx",
55
+ "okay": "k", "ok": "k", "really": "rly", "though": "tho",
56
+ }
57
+
58
+ # common misspellings applied with low probability per word
59
+ MISSPELLINGS = {
60
+ "the": "teh", "and": "an", "is": "iz", "my": "mah",
61
+ "like": "liek", "work": "wrk", "at": "@", "name": "naem",
62
+ "live": "liv", "sister": "sis", "brother": "bro",
63
+ "manager": "mgr", "team": "tm", "started": "strt'd",
64
+ }
65
+
66
+ # discourse markers for compound sentence stitching
67
+ STITCHERS = [
68
+ "and tbh", "so like", "and ykwis", "ngl", "and then",
69
+ "and like", "so anyway", "and ya", "but like", "and ok",
70
+ "so basically", "and honestly", "but fr", "so ngl",
71
+ ]
72
+
73
+ # Casual smalltalk that's even less grammatical
74
+ MESSY_SMALLTALK = [
75
+ "yo wassup", "ayy hows it goin", "lol ok", "smh not again",
76
+ "ngl thats wild", "bruh fr??", "ok ok i got u", "ye i feel u",
77
+ "lol same", "rip", "yooo thats crazy", "ok wait what", "tbh idk man",
78
+ "ya im here", "eh could be worse", "ok cool cool cool", "ha nice",
79
+ "ngl kinda tired tbh", "wait gimme a sec", "ya ya sry", "eh whatever",
80
+ ]
81
+
82
+
83
+ @dataclass
84
+ class MessyCorpus:
85
+ """Result of messifying — keeps the underlying persona for ground
86
+ truth comparison. Same shape as the clean persona dict so the
87
+ existing BEAM benchmark can consume it unchanged."""
88
+ user_id: str
89
+ text: str
90
+ facts: list
91
+
92
+
93
+ def _messify_text(text: str, rng: random.Random,
94
+ textspeak_p: float = 0.45,
95
+ misspell_p: float = 0.10,
96
+ filler_p: float = 0.55) -> str:
97
+ """Apply slang / textspeak / misspellings / fillers to a clean text.
98
+
99
+ Probabilities are tuned so the result is recognizable but messy:
100
+ - textspeak_p: chance per word to apply a text-speak substitution
101
+ - misspell_p: chance per word to apply a common misspelling
102
+ - filler_p: chance at each clause boundary to inject a slang filler
103
+ """
104
+ if not text:
105
+ return text
106
+
107
+ # 1) text-speak substitutions (word-boundary preserving)
108
+ out = text
109
+ for src, dst in TEXTSPEAK.items():
110
+ if rng.random() < textspeak_p:
111
+ # case-insensitive replace, preserve first-letter case
112
+ import re
113
+ def _sub(m, dst=dst):
114
+ w = m.group(0)
115
+ return dst if w.islower() else dst.capitalize()
116
+ out = re.sub(rf"\b{src}\b", _sub, out, flags=re.IGNORECASE)
117
+
118
+ # 2) word-level misspellings
119
+ words = out.split()
120
+ out_words = []
121
+ for w in words:
122
+ # strip trailing punctuation for lookup, keep it
123
+ import re as _re
124
+ m = _re.match(r"^(\W*)(\w+)(\W*)$", w)
125
+ if not m:
126
+ out_words.append(w)
127
+ continue
128
+ pre, core, post = m.groups()
129
+ lower = core.lower()
130
+ if lower in MISSPELLINGS and rng.random() < misspell_p:
131
+ new_core = MISSPELLINGS[lower]
132
+ # preserve capitalization of first letter
133
+ if core[0].isupper():
134
+ new_core = new_core[:1].upper() + new_core[1:]
135
+ out_words.append(pre + new_core + post)
136
+ else:
137
+ out_words.append(w)
138
+ out = " ".join(out_words)
139
+
140
+ # 3) drop sentence-final periods with some probability (text-style)
141
+ if rng.random() < 0.6:
142
+ out = out.replace(".", "")
143
+ out = out.replace("!", "")
144
+ out = out.replace("?", "")
145
+
146
+ # 4) lowercase sentence-initial letters with some probability
147
+ if out and rng.random() < 0.5:
148
+ out = out[0].lower() + out[1:]
149
+
150
+ # 5) inject slang fillers at clause boundaries (commas / conjunctions)
151
+ import re
152
+ # split keeping delimiters
153
+ parts = re.split(r"(\s+(?:and|but|so|because|although|while|when|if)\s+)", out)
154
+ out_parts = []
155
+ for i, p in enumerate(parts):
156
+ out_parts.append(p)
157
+ # if this is a conjunction delimiter and next part is non-empty,
158
+ # maybe inject a filler after the conjunction
159
+ if i % 2 == 1 and rng.random() < filler_p:
160
+ filler = rng.choice(SLANG_FILLERS)
161
+ out_parts.append(f" {filler} ")
162
+ out = "".join(out_parts)
163
+
164
+ # 6) occasional ALL-CAPS burst on short sentences (≤4 words) for emphasis
165
+ words_now = out.split()
166
+ if len(words_now) <= 4 and rng.random() < 0.15:
167
+ out = out.upper()
168
+
169
+ return out.strip()
170
+
171
+
172
+ def messify_messages(persona: Persona, rng: random.Random,
173
+ session_date, part: int) -> list[tuple[str, str]]:
174
+ """Run the clean persona_messages generator, then messify each line.
175
+
176
+ Returns the same list-of-(role, text) shape, but each text is
177
+ slang-ified. Ground truth is still derivable from the persona.
178
+ """
179
+ from cortexm.bench.generator import persona_messages
180
+ clean = persona_messages(persona, rng, session_date, part)
181
+ messy = []
182
+ for role, text in clean:
183
+ if role == "user":
184
+ mtext = _messify_text(text, rng)
185
+ messy.append((role, mtext))
186
+ else:
187
+ messy.append((role, text))
188
+ # splice in extra messy smalltalk
189
+ for _ in range(rng.randrange(1, 3)):
190
+ messy.append(("user", rng.choice(MESSY_SMALLTALK)))
191
+ return messy
192
+
193
+
194
+ def make_messy_persona(rng: random.Random, idx: int, t0) -> Persona:
195
+ """Same persona generator as the clean one — personas themselves
196
+ don't need to be messy, only the surface text does."""
197
+ return make_persona(rng, idx, t0)
198
+
199
+
200
+ # Tiny end-to-end demo used by the BEAM bench harness when `--messy` is
201
+ # passed — converts a clean persona dict into a messy one.
202
+ def messify_persona_dict(p: dict, rng: random.Random) -> dict:
203
+ """Take a {user_id, text, facts} dict from the clean persona
204
+ generator and produce a messified copy. Facts list is preserved
205
+ (ground truth unchanged)."""
206
+ return {
207
+ "user_id": p["user_id"],
208
+ "text": _messify_text(p["text"], rng),
209
+ "facts": p["facts"],
210
+ }
211
+
212
+
213
+ __all__ = [
214
+ "MessyCorpus", "make_messy_persona", "messify_messages",
215
+ "messify_persona_dict", "_messify_text",
216
+ "SLANG_FILLERS", "TEXTSPEAK", "MISSPELLINGS", "STITCHERS",
217
+ "MESSY_SMALLTALK",
218
+ ]
cortexm/bench/micro.py ADDED
@@ -0,0 +1,251 @@
1
+ """Micro-benchmarks — the engineering claims behind the fabric.
2
+
3
+ 1. Retrieval latency & tree-index recall at 10K/50K/100K vectors
4
+ (plan milestone: <1ms retrieval on 100K memories)
5
+ 2. Codec ablation: recall@10 vs FP32 brute force + bytes/vector
6
+ (the cortexm-compress tier table)
7
+ 3. Self-healing memory: recall under bit-flip corruption, with and
8
+ without TMR, before/after healing (the "Proof of God" demo)
9
+ 4. SLB hit rate & latency under conversational locality replay
10
+ 5. Ingest throughput (tokens/s, facts/s) and μ=0 assertion
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import time
16
+
17
+ import numpy as np
18
+
19
+ from cortexm import metrics
20
+ from cortexm.config import Config
21
+ from cortexm.vsa.codecs import make_codec
22
+ from cortexm.vsa.ops import VSA
23
+ from cortexm.text.embedder import HashingEmbedder
24
+
25
+
26
+ def _synthetic_corpus(n: int, dims: int = 768, seed: int = 7):
27
+ """n fact holograms with genuine kNN structure: facts about the same
28
+ subject share lexical content, so true neighbors = same-subject facts."""
29
+ rng = np.random.default_rng(seed)
30
+ emb = HashingEmbedder(dims, seed)
31
+ vsa = VSA(dims, "perm", seed)
32
+ n_subj = max(50, n // 40)
33
+ subjects = [f"Person{i}" for i in range(n_subj)]
34
+ relations = ["works_at", "lives_in", "prefers", "has_skill", "event"]
35
+ facts, vecs = [], []
36
+ for i in range(n):
37
+ s = subjects[i % n_subj]
38
+ r = relations[i % len(relations)]
39
+ v = f"{s}-thing{(i // n_subj) % 40}"
40
+ facts.append((s, r, v))
41
+ vecs.append(vsa.encode_fact(emb.embed(s), emb.embed(r), emb.embed(v)))
42
+ return facts, np.stack(vecs), emb
43
+
44
+
45
+ def latency_and_recall():
46
+ from cortexm.vsa.index import TreeIndex
47
+
48
+ out = {}
49
+ for n in (10_000, 50_000, 100_000):
50
+ facts, vecs, emb = _synthetic_corpus(n)
51
+ ids = [f"fact{i}" for i in range(n)]
52
+ codec = make_codec("int8", 768)
53
+ packed = np.stack([codec.encode_packed(v) for v in vecs])
54
+ aux = np.array([codec.encode_scale(v) for v in vecs])
55
+
56
+ def getter(rows):
57
+ return packed[rows], aux[rows]
58
+
59
+ # queries: text of a random fact (its own hologram is the target)
60
+ rng = np.random.default_rng(11)
61
+ q_idx = rng.choice(n, 100, replace=False)
62
+ queries = [emb.embed(" ".join(facts[i])) for i in q_idx]
63
+
64
+ # brute force ground truth
65
+ t0 = time.perf_counter()
66
+ gt, gt_scores = [], []
67
+ for q in queries:
68
+ sc = codec.scores(packed, q, aux)
69
+ top = np.argsort(-sc)[:10]
70
+ gt.append(set(top.tolist()))
71
+ gt_scores.append(float(np.sort(sc)[::-1][:10].mean()))
72
+ flat_ms = (time.perf_counter() - t0) / len(queries) * 1e3
73
+
74
+ # tree index
75
+ t0 = time.perf_counter()
76
+ idx = TreeIndex(codec, getter, n, branch=8, leaf=512, seed=3)
77
+ idx.build()
78
+ build_s = time.perf_counter() - t0
79
+
80
+ t0 = time.perf_counter()
81
+ hits = 0
82
+ overlap = 0.0
83
+ quality = 0.0
84
+ lat = []
85
+ for q, want, want_score in zip(queries, gt, gt_scores):
86
+ t1 = time.perf_counter()
87
+ rows, scores = idx.search(q, 10, beam=8)
88
+ lat.append(time.perf_counter() - t1)
89
+ got = set(rows.tolist())
90
+ if got & want:
91
+ hits += 1
92
+ overlap += len(got & want) / 10
93
+ if len(scores):
94
+ quality += float(np.sort(scores)[::-1][:10].mean()) / max(want_score, 1e-9)
95
+ lat = np.array(lat) * 1e3
96
+ out[f"n={n}"] = {
97
+ "flat_ms": round(flat_ms, 2),
98
+ "tree_p50_ms": round(float(np.percentile(lat, 50)), 3),
99
+ "tree_p99_ms": round(float(np.percentile(lat, 99)), 3),
100
+ "any_hit@10": round(hits / len(queries), 4),
101
+ "overlap@10": round(overlap / len(queries), 4),
102
+ "quality_ratio": round(quality / len(queries), 4),
103
+ "index_build_s": round(build_s, 2),
104
+ "rows_scanned_avg": round(idx.leaf_rows_scanned / len(queries), 0),
105
+ }
106
+ return out
107
+
108
+
109
+ def codec_ablation(n: int = 20_000, k: int = 10):
110
+ emb = HashingEmbedder(768, 7)
111
+ vsa = VSA(768, "perm", 7)
112
+ n_subj = max(50, n // 40)
113
+ subjects = [f"Person{i}" for i in range(n_subj)]
114
+ relations = ["works_at", "lives_in", "prefers", "has_skill", "event"]
115
+ vecs, queries = [], []
116
+ for i in range(n):
117
+ s = subjects[i % n_subj]
118
+ r = relations[i % len(relations)]
119
+ v = f"{s}-thing{(i // n_subj) % 40}"
120
+ vecs.append(vsa.encode_fact(emb.embed(s), emb.embed(r), emb.embed(v)))
121
+ if i % (n // 200) == 0:
122
+ queries.append((vecs[-1], i))
123
+ vecs = np.stack(vecs)
124
+
125
+ gt = []
126
+ for q, i in queries:
127
+ sc = vecs @ q
128
+ top = np.argsort(-sc)[:k].tolist()
129
+ gt.append((i, set(top)))
130
+
131
+ out = {}
132
+ for name in ("int8", "binary", "rabitq", "pq"):
133
+ t0 = time.perf_counter()
134
+ codec = make_codec(name, 768, seed=7)
135
+ if name == "pq":
136
+ codec.train(vecs[: min(n, 4096)])
137
+ packed = np.stack([codec.encode_packed(v) for v in vecs])
138
+ enc_s = time.perf_counter() - t0
139
+ # hologram probes: self-hit + neighborhood preservation vs fp32
140
+ hits = 0
141
+ overlap = 0.0
142
+ shortlist = 0.0
143
+ t0 = time.perf_counter()
144
+ if name == "int8":
145
+ aux = np.array([codec.encode_scale(v) for v in vecs])
146
+ for (q, i), (self_idx, want) in zip(queries, gt):
147
+ sc = (codec.scores(packed, q, aux) if name == "int8"
148
+ else codec.scores(packed, q))
149
+ order = np.argsort(-sc)
150
+ got = order[:k].tolist()
151
+ if i in got:
152
+ hits += 1
153
+ overlap += len(set(got) & want) / k
154
+ # shortlist usage: fp32 top-10 within codec top-50
155
+ shortlist += len(set(order[:50].tolist()) & want) / k
156
+ out[name] = {
157
+ "bytes_per_vector": codec.bytes_per_vector,
158
+ "mb_per_million": round(codec.bytes_per_vector, 1),
159
+ "self_hit@10": round(hits / len(queries), 4),
160
+ "overlap@10_vs_fp32": round(overlap / len(queries), 4),
161
+ "recall@10_in_top50": round(shortlist / len(queries), 4),
162
+ "encode_ms_per_1k": round(enc_s / n * 1000 * 1e3, 2),
163
+ "query_ms_flat_20k": round((time.perf_counter() - t0) / len(queries) * 1e3, 3),
164
+ }
165
+ out["fp32_reference"] = {"bytes_per_vector": 3072, "mb_per_million": 3072.0,
166
+ "recall@10": 1.0}
167
+ return out
168
+
169
+
170
+ def self_healing(n: int = 5000):
171
+ """Recall under corruption — binary HDC tolerance + TMR majority vote.
172
+
173
+ Self-identification test: a corrupted hypervector must still rank
174
+ ITSELF as the nearest neighbor among n stored vectors (HDC's
175
+ error-correction radius), with and without TMR.
176
+ """
177
+ from cortexm.vsa.codecs import BinaryCodec
178
+
179
+ emb = HashingEmbedder(768, 7)
180
+ vsa = VSA(768, "perm", 7)
181
+ n_subj = max(50, n // 40)
182
+ subjects = [f"Person{i}" for i in range(n_subj)]
183
+ vecs = []
184
+ for i in range(n):
185
+ s = subjects[i % n_subj]
186
+ v = f"{s}-thing{(i // n_subj) % 40}"
187
+ vecs.append(vsa.encode_fact(emb.embed(s), emb.embed("works_at"),
188
+ emb.embed(v)))
189
+ vecs = np.stack(vecs)
190
+ q_idx = list(range(0, n, 50)) # 100 probe vectors
191
+
192
+ out = {}
193
+ for tmr in (False, True):
194
+ codec = BinaryCodec(768, tmr=tmr)
195
+ packed = np.stack([codec.encode_packed(v) for v in vecs])
196
+ crng = np.random.default_rng(99)
197
+ for rate in (0.0, 0.01, 0.05, 0.10, 0.20):
198
+ corrupted = packed.copy()
199
+ if rate > 0:
200
+ for row in q_idx: # corrupt the probe vectors
201
+ corrupted[row] = codec.corrupt(corrupted[row], rate, crng)
202
+ hits = 0
203
+ for i in q_idx:
204
+ sc = codec.scores(corrupted, vecs[i])
205
+ if int(np.argmax(sc)) == i:
206
+ hits += 1
207
+ out[f"{'tmr' if tmr else 'plain'}@{int(rate*100)}%"] = {
208
+ "self_identification": round(hits / len(q_idx), 3)}
209
+ return out
210
+
211
+
212
+ def slb_replay(n_queries: int = 400):
213
+ from cortexm.vsa.slb import SemanticLookasideBuffer
214
+
215
+ emb = HashingEmbedder(768, 7)
216
+ slb = SemanticLookasideBuffer(64, 0.97, 768)
217
+ rng = np.random.default_rng(3)
218
+ topics = [f"project {i} status update" for i in range(40)]
219
+ hit_lat, miss_lat = [], []
220
+ for i in range(n_queries):
221
+ # conversational locality: follow-ups repeat the previous topic
222
+ base = topics[rng.integers(len(topics))]
223
+ q = emb.embed(base + (" again" if i % 3 else ""))
224
+ t0 = time.perf_counter()
225
+ got = slb.lookup(q)
226
+ if got is not None:
227
+ hit_lat.append(time.perf_counter() - t0)
228
+ else:
229
+ miss_lat.append(time.perf_counter() - t0)
230
+ slb.store(q, [("f1", 0.5), ("f2", 0.4)])
231
+ return {
232
+ "hit_rate": round(slb.hits / n_queries, 3),
233
+ "avg_hit_latency_us": round(sum(hit_lat) / max(len(hit_lat), 1) * 1e6, 1),
234
+ "avg_miss_latency_us": round(sum(miss_lat) / max(len(miss_lat), 1) * 1e6, 1),
235
+ }
236
+
237
+
238
+ def run_micro() -> dict:
239
+ metrics.reset_counters()
240
+ out = {
241
+ "latency_recall": latency_and_recall(),
242
+ "codec_ablation": codec_ablation(),
243
+ "self_healing": self_healing(),
244
+ "slb_replay": slb_replay(),
245
+ "u0_llm_calls": metrics.llm_calls(),
246
+ }
247
+ return out
248
+
249
+
250
+ if __name__ == "__main__":
251
+ print(run_micro())