quantum-cortex 1.0.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 (41) hide show
  1. cortex_c2/__init__.py +161 -0
  2. cortex_c2/consolidation.py +146 -0
  3. cortex_c2/router_ablation.py +254 -0
  4. cortex_c2/router_v2.py +109 -0
  5. cortex_c2b/__init__.py +724 -0
  6. cortex_c2b/companion_demo.py +203 -0
  7. cortex_c2b/crypto.py +102 -0
  8. cortex_c2b/external_arm.py +591 -0
  9. cortex_c2b/hm_lm.py +983 -0
  10. cortex_c2b/hm_protocol.py +308 -0
  11. cortex_c2b/lifecycle.py +454 -0
  12. cortex_c2b/lm_bridge.py +338 -0
  13. cortex_c2b/organ_use.py +254 -0
  14. cortex_c2b/read_path.py +218 -0
  15. cortex_c2b/reconstruction_probe.py +471 -0
  16. cortex_c2b/write_path.py +176 -0
  17. cortex_data/__init__.py +439 -0
  18. cortex_data/relay.py +174 -0
  19. cortex_eval/__init__.py +20 -0
  20. cortex_eval/ablation_mqar.py +187 -0
  21. cortex_eval/confirmation_run.py +133 -0
  22. cortex_eval/domain_map.py +127 -0
  23. cortex_eval/external_baseline.py +148 -0
  24. cortex_eval/ingest_confirmation.py +194 -0
  25. cortex_eval/mqar.py +122 -0
  26. cortex_eval/multiseed.py +148 -0
  27. cortex_eval/recurrent_base.py +197 -0
  28. cortex_eval/recurrent_ladder.py +159 -0
  29. cortex_eval/resumable_ladder.py +231 -0
  30. cortex_eval/resumable_rb.py +207 -0
  31. cortex_eval/run_mqar.py +127 -0
  32. cortex_eval/serial_position.py +116 -0
  33. quantum_cortex-1.0.0.dist-info/METADATA +209 -0
  34. quantum_cortex-1.0.0.dist-info/RECORD +41 -0
  35. quantum_cortex-1.0.0.dist-info/WHEEL +5 -0
  36. quantum_cortex-1.0.0.dist-info/licenses/AUTHORS.md +30 -0
  37. quantum_cortex-1.0.0.dist-info/licenses/LICENSE +73 -0
  38. quantum_cortex-1.0.0.dist-info/licenses/LICENSE-DOCS.md +34 -0
  39. quantum_cortex-1.0.0.dist-info/licenses/NOTICE +50 -0
  40. quantum_cortex-1.0.0.dist-info/top_level.txt +5 -0
  41. train.py +885 -0
@@ -0,0 +1,308 @@
1
+ """cortex_c2b.hm_protocol — the H.M. dissociation protocol (ADR-007 Slice D, the milestone).
2
+
3
+ The refutable claim of the whole memory story: skills and semantic knowledge live
4
+ in the WEIGHTS; episodes live in the JOURNAL (C2b). Cut the journal at inference
5
+ and episodic recall must collapse while skills hold. If the dissociation does not
6
+ hold, the memory story is wrong — and the loss is published.
7
+
8
+ Implements `docs/benchmarks/hm-protocol.md` EXACTLY. Frozen thresholds (never
9
+ adjusted after seeing numbers; a change needs a dated amendment BEFORE a run):
10
+ δ = 0.50 hm_recall_on − hm_recall_off ≥ δ (the gap is the claim)
11
+ ε_S = 0.01 hm_skill_delta ≤ ε_S (1% relative) (skills intact)
12
+ floor = chance + 0.05 (recall_off at the floor)
13
+ negative control: hallucinated recall ≥ 0.10 INVALIDATES the run
14
+
15
+ Session A plants 200 synthetic facts (5 schemas, balanced) through the normal
16
+ C2b write path (surprise gate active — novel facts pass). Session B, in a fresh
17
+ context, probes each fact (direct query + one paraphrase) with the journal ON
18
+ and OFF. A negative-control set of 50 never-planted facts must yield ~0 recall
19
+ in both modes.
20
+
21
+ Slice D binds the protocol to the C2b organ built in A–C: the journal (store),
22
+ the write path (gate), the read path (cue index). The "model" side — skills
23
+ and the recall reader — is abstracted behind `SkillProbe` and `Reader` so the
24
+ protocol can run today against the journal alone (recall_off ≡ no journal ⇒
25
+ parametric floor by construction) and later against the full model whose
26
+ weights hold skills. Both arms, both numbers, PASS or FAIL, published.
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import hashlib
31
+ import json
32
+ from dataclasses import dataclass, field
33
+ from datetime import datetime, timezone
34
+
35
+ import numpy as np
36
+
37
+ from cortex_c2b import Journal, CUE_DIM, MODE_DURABLE, POLICY_STOP, lifecycle_declaration
38
+ from cortex_c2b.write_path import WritePath
39
+ from cortex_c2b.read_path import JournalPath
40
+
41
+ # ---- frozen thresholds (hm-protocol.md) ------------------------------------
42
+ DELTA = 0.50
43
+ EPSILON_S = 0.01
44
+ FLOOR_MARGIN = 0.05
45
+ NEGCTRL_INVALID = 0.10
46
+ N_FACTS = 200
47
+ N_NEGCTRL = 50
48
+ SCHEMAS = ("person", "place", "event", "object", "decision")
49
+
50
+ _CONS = "bdfgklmnprstvz"
51
+ _VOW = "aeiou"
52
+
53
+
54
+ # ---------------------------------------------------------------------------- #
55
+ # The generator: open, seeded, config-hashed, leakage-proof #
56
+ # ---------------------------------------------------------------------------- #
57
+ def _entity_name(rng) -> str:
58
+ """`{consonant-vowel trigrams}-{4 hex}` — never a dictionary word, never a real name."""
59
+ syl = "".join(rng.choice(list(_CONS)) + rng.choice(list(_VOW)) for _ in range(2))
60
+ tri = syl[:1].upper() + syl[1:]
61
+ hexs = "".join(rng.choice(list("0123456789abcdef")) for _ in range(4))
62
+ return f"{tri}-{hexs}"
63
+
64
+
65
+ _TEMPLATES = {
66
+ "person": ("{e} works as a {a}", "what is {e}'s job?", "{e} — occupation?"),
67
+ "place": ("{e} is a town in {a}", "where is {e}?", "{e} lies in which region?"),
68
+ "event": ("{e} took place in {a}", "when did {e} happen?", "{e} — the year?"),
69
+ "object": ("{e} is made of {a}", "what is {e} made of?", "{e} — its material?"),
70
+ "decision": ("{e} was decided by {a}", "who decided {e}?", "{e} — decided by whom?"),
71
+ }
72
+ _ATTRS = {
73
+ "person": ["glazier", "cartographer", "cooper", "luthier", "falconer", "archivist", "tanner", "milliner"],
74
+ "place": ["Kalvaria", "Osterhold", "Brenmoor", "Vilkas", "Sandrelle", "Tornquay", "Ebbelund", "Marrowick"],
75
+ "event": ["1873", "1904", "1931", "1958", "1967", "1982", "1996", "2003"],
76
+ "object": ["pewter", "boxwood", "vellum", "basalt", "linen", "amber", "cobalt", "horn"],
77
+ "decision": ["the guild council", "the harbour master", "the abbess", "the river warden",
78
+ "the quarry board", "the toll keeper", "the choir master", "the ferry syndic"],
79
+ }
80
+
81
+
82
+ @dataclass
83
+ class Fact:
84
+ entity: str
85
+ schema: str
86
+ attr: str
87
+ statement: str
88
+ query: str
89
+ paraphrase: str
90
+ cue: np.ndarray = field(repr=False)
91
+
92
+
93
+ def _cue_for(text: str, dim: int = CUE_DIM) -> np.ndarray:
94
+ """Deterministic pseudo-embedding of a text (hash-seeded unit vector). Stands in
95
+ for the model's cue encoder so the protocol runs against the journal alone;
96
+ the same function embeds statement, query and paraphrase, so recall is a
97
+ retrieval test, not a language test — exactly the journal's job."""
98
+ h = int(hashlib.sha256(text.encode()).hexdigest()[:16], 16)
99
+ v = np.random.default_rng(h).standard_normal(dim).astype(np.float32)
100
+ return v / (np.linalg.norm(v) + 1e-8)
101
+
102
+
103
+ address_cue = _cue_for
104
+ """The organ level cue convention, under its public name (whitepaper 3.6, added
105
+ 2026-09-30): the entity is the address. Any unit vector of CUE_DIM floats is a
106
+ valid cue; this one stands in for a learned encoder and addresses no paraphrase."""
107
+
108
+
109
+ def value_of_statement(statement: str) -> tuple[str, str, str] | None:
110
+ """The organ's own reading of a line: (schema, entity, attribute) recovered from a
111
+ planted statement by the generator's templates, or None when no template matches.
112
+ Used by the `+value` decode policies (ADR-008 amendment 2026-09-18): the value the
113
+ model answers is then the cited line's, read by the organ, never generated."""
114
+ import re
115
+ for schema, (st, _, _) in _TEMPLATES.items():
116
+ pat = "^" + re.escape(st).replace(r"\{e\}", "(?P<e>.+?)").replace(r"\{a\}", "(?P<a>.+)") + "$"
117
+ m = re.match(pat, statement)
118
+ if m:
119
+ return schema, m.group("e"), m.group("a")
120
+ return None
121
+
122
+
123
+ def generate_facts(n: int = N_FACTS, seed: int = 0) -> tuple[list[Fact], str]:
124
+ """Balanced across 5 schemas; returns (facts, config_hash of the generator)."""
125
+ rng = np.random.default_rng(seed)
126
+ facts = []
127
+ per = n // len(SCHEMAS)
128
+ for schema in SCHEMAS:
129
+ st, q, p = _TEMPLATES[schema]
130
+ for _ in range(per):
131
+ e = _entity_name(rng)
132
+ a = rng.choice(_ATTRS[schema])
133
+ statement = st.format(e=e, a=a)
134
+ facts.append(Fact(entity=e, schema=schema, attr=a, statement=statement,
135
+ query=q.format(e=e), paraphrase=p.format(e=e),
136
+ cue=_cue_for(f"{schema}|{e}"))) # the entity is the address
137
+ cfg = hashlib.sha256(json.dumps({"n": n, "seed": seed, "schemas": SCHEMAS,
138
+ "templates": _TEMPLATES}, sort_keys=True).encode()).hexdigest()[:16]
139
+ return facts, cfg
140
+
141
+
142
+ # ---------------------------------------------------------------------------- #
143
+ # The reader: recall = retrieve the planted attribute for a probed entity #
144
+ # ---------------------------------------------------------------------------- #
145
+ class Reader:
146
+ """Journal ON: query the cue index for the entity's address, read the payload,
147
+ exact-match the attribute. Journal OFF: no journal ⇒ the parametric floor
148
+ (a weights-only model that never saw the episode can only guess: chance)."""
149
+
150
+ def __init__(self, journal_path: JournalPath | None, chance: float):
151
+ self.jp = journal_path
152
+ self.chance = chance
153
+
154
+ def recall(self, fact: Fact, rng) -> bool:
155
+ if self.jp is None: # journal OFF
156
+ # parametric floor: a weights-only model that never saw the episode can
157
+ # only guess ONCE per fact (its guess is the same for the direct query and
158
+ # the paraphrase — both address the same entity); seeded per fact.
159
+ g = np.random.default_rng(int(hashlib.sha256(fact.entity.encode()).hexdigest()[:8], 16))
160
+ return bool(g.random() < self.chance)
161
+ hits = self.jp.retrieve(fact.cue, k=1)
162
+ if not hits:
163
+ return False
164
+ entry, payload, score = hits[0]
165
+ return fact.attr in payload.decode("utf-8", errors="ignore")
166
+
167
+
168
+ class SkillProbe:
169
+ """Skills live in the weights. Against the journal alone the skill score is
170
+ journal-independent by construction (the journal is not consulted): we
171
+ return a fixed reference so `hm_skill_delta` is exactly 0 — the protocol's
172
+ contract, to be replaced by the model's real suites when the LM is wired."""
173
+
174
+ def __init__(self, reference: float = 0.7):
175
+ self.reference = reference
176
+
177
+ def score(self, journal_on: bool) -> float:
178
+ return self.reference
179
+
180
+
181
+ # ---------------------------------------------------------------------------- #
182
+ # The protocol #
183
+ # ---------------------------------------------------------------------------- #
184
+ def run_hm_protocol(n_facts: int = N_FACTS, n_negctrl: int = N_NEGCTRL, seed: int = 0,
185
+ admission_threshold: float = 0.15, journal_path=None, key: bytes | None = None,
186
+ plaintext: bool = False, policy: str = POLICY_STOP) -> dict:
187
+ """Spec amendment 2026-09-12 (ADR-007 D8/D9, dated before any run that uses it):
188
+ the protocol reads the journal's storage state and reports `persistent` and
189
+ `claimable`. Without `journal_path` the journal lives in memory: session B
190
+ shares the process with session A, and the pass is reported with
191
+ `persistent: false` -- an organ level measurement, not a claim of survival
192
+ across a restart. With `journal_path`, session B REOPENS the journal from the
193
+ disk alone (a new object, the index recomputed); `persistent` is true only if
194
+ the journal stayed durable through both sessions. A measurement run uses the
195
+ `stop` policy: a storage failure aborts the run instead of continuing in
196
+ memory unnoticed. Thresholds are untouched."""
197
+ facts, cfg = generate_facts(n_facts, seed)
198
+ neg, _ = generate_facts(n_negctrl, seed + 10_000) # never planted
199
+ rng = np.random.default_rng(seed + 1)
200
+
201
+ # chance level: pick an attribute at random among the schema's vocabulary
202
+ chance = float(np.mean([1.0 / len(_ATTRS[s]) for s in SCHEMAS]))
203
+ floor = chance + FLOOR_MARGIN
204
+
205
+ # --- Session A: plant every fact through the NORMAL gated write path -----
206
+ def open_journal():
207
+ if journal_path is None:
208
+ return Journal()
209
+ return Journal(journal_path, key=key, plaintext=plaintext, policy=policy)
210
+ journal = open_journal()
211
+ wp = WritePath(journal, admission_threshold=admission_threshold, seed=seed)
212
+ jp = JournalPath(journal)
213
+ admitted = 0
214
+ for f in facts:
215
+ rep = wp.write(f.cue, f.statement.encode(), f.schema, now=float(admitted))
216
+ if rep.admitted:
217
+ jp.on_write(rep.entry); admitted += 1
218
+ durable_a = journal.mode == MODE_DURABLE
219
+
220
+ # --- Session B: fresh context (new Reader objects, nothing from A but the journal) --
221
+ if journal_path is not None:
222
+ del wp, jp # nothing of session A but the disk
223
+ journal = open_journal() # reopened: structure, bytes, index recomputed
224
+ jp = JournalPath(journal)
225
+ storage = lifecycle_declaration(journal)["storage"]
226
+ persistent = bool(journal_path is not None and durable_a and journal.mode == MODE_DURABLE)
227
+ reader_on = Reader(jp, chance)
228
+ reader_off = Reader(None, chance) # journal cut
229
+ def recall_rate(reader, fs):
230
+ hits = 0
231
+ for f in fs:
232
+ # direct query + one paraphrase — both address the same entity cue
233
+ hits += int(reader.recall(f, rng) or reader.recall(f, rng))
234
+ return hits / len(fs)
235
+ recall_on = recall_rate(reader_on, facts)
236
+ recall_off = recall_rate(reader_off, facts)
237
+ negctrl_on = recall_rate(reader_on, neg) # never planted → the JOURNAL must not hallucinate
238
+ negctrl_off = recall_rate(reader_off, neg) # a guessing floor "recalls" ~chance by construction
239
+ # Amendment 2026-09-08 (dated BEFORE this run's verdict is used; see hm-protocol.md §5):
240
+ # the negative control is read ABOVE CHANCE — guessing is not hallucinating.
241
+ negctrl = max(negctrl_on, max(0.0, negctrl_off - chance))
242
+
243
+ # --- Skills arm ---------------------------------------------------------
244
+ sp = SkillProbe()
245
+ s_on, s_off = sp.score(True), sp.score(False)
246
+ skill_delta = abs(s_on - s_off) / max(s_on, 1e-9)
247
+
248
+ # --- Verdict (frozen thresholds) ----------------------------------------
249
+ valid = negctrl < NEGCTRL_INVALID
250
+ gap = recall_on - recall_off
251
+ dissociation_pass = bool(valid and skill_delta <= EPSILON_S and gap >= DELTA and recall_off <= floor)
252
+
253
+ return {
254
+ "benchmark": "hm_protocol", "spec": "docs/benchmarks/hm-protocol.md (frozen thresholds)",
255
+ "timestamp_utc": datetime.now(timezone.utc).isoformat(),
256
+ "generator": {"n_facts": n_facts, "n_negctrl": n_negctrl, "seed": seed, "config_hash": cfg,
257
+ "schemas": SCHEMAS, "admitted": admitted},
258
+ "thresholds": {"delta": DELTA, "epsilon_s": EPSILON_S, "floor": round(floor, 4),
259
+ "chance": round(chance, 4), "negctrl_invalid": NEGCTRL_INVALID},
260
+ "hm_recall_on": round(recall_on, 4),
261
+ "hm_recall_off": round(recall_off, 4),
262
+ "hm_gap": round(gap, 4),
263
+ "hm_negctrl_rate": round(negctrl, 4),
264
+ "hm_negctrl_on": round(negctrl_on, 4),
265
+ "hm_negctrl_off_raw": round(negctrl_off, 4),
266
+ "hm_negctrl_off_above_chance": round(max(0.0, negctrl_off - chance), 4),
267
+ "hm_skill_delta": round(skill_delta, 4),
268
+ "run_valid": valid,
269
+ "hm_dissociation_pass": int(dissociation_pass),
270
+ "persistent": persistent,
271
+ "claimable": int(dissociation_pass and persistent),
272
+ "storage": {"on_disk": journal_path is not None, "mode": storage["mode"], "policy": storage["policy"],
273
+ "sealed": key is not None, "plaintext_scope": storage["plaintext_scope"]},
274
+ "verdict": (("PASS \u2014 the dissociation holds: episodes live in the journal, skills are journal-independent"
275
+ if dissociation_pass else
276
+ ("INVALID \u2014 hallucinated recall on the negative control" if not valid else
277
+ "FAIL \u2014 the dissociation does not hold (recorded; triggers a dated revision of the memory story)"))
278
+ + ("" if persistent else
279
+ " \u2014 persistent: false (journal in memory or in memory mode; sessions A and B within one process): not claimable")),
280
+ "reserve": "Slice D runs the protocol against the C2b organ with a hash-seeded cue encoder and a "
281
+ "reference skill probe; the language-model arm (real skills suites, learned cue encoder) "
282
+ "lands when the LM is wired to the journal. Recall here is a retrieval test — the journal's job.",
283
+ }
284
+
285
+
286
+ if __name__ == "__main__":
287
+ import argparse
288
+ from pathlib import Path
289
+ ap = argparse.ArgumentParser(description="the H.M. dissociation protocol against the C2b organ")
290
+ ap.add_argument("--persistent", action="store_true",
291
+ help="sealed journal on disk, session B reopened from the disk alone (claimable)")
292
+ ap.add_argument("--seed", type=int, default=0)
293
+ ap.add_argument("--out", default=None,
294
+ help="retain the full result as a file (e.g. metrics/mqar/hm-protocol-persistent-<date>.json); "
295
+ "a number read from a console is not a source")
296
+ args = ap.parse_args()
297
+ if args.persistent:
298
+ import tempfile
299
+ from cortex_c2b.crypto import generate_key
300
+ with tempfile.TemporaryDirectory() as d: # sealed on disk, session B reopened from the disk alone
301
+ r = run_hm_protocol(seed=args.seed, journal_path=f"{d}/hm-journal.jsonl", key=generate_key())
302
+ else:
303
+ r = run_hm_protocol(seed=args.seed)
304
+ if args.out:
305
+ out = Path(args.out); out.parent.mkdir(parents=True, exist_ok=True)
306
+ out.write_text(json.dumps(r, indent=2) + "\n", encoding="utf-8")
307
+ print(f"retained: {out}")
308
+ print(json.dumps({k: v for k, v in r.items() if k != "generator"}, indent=2))