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.
- cortex_c2/__init__.py +161 -0
- cortex_c2/consolidation.py +146 -0
- cortex_c2/router_ablation.py +254 -0
- cortex_c2/router_v2.py +109 -0
- cortex_c2b/__init__.py +724 -0
- cortex_c2b/companion_demo.py +203 -0
- cortex_c2b/crypto.py +102 -0
- cortex_c2b/external_arm.py +591 -0
- cortex_c2b/hm_lm.py +983 -0
- cortex_c2b/hm_protocol.py +308 -0
- cortex_c2b/lifecycle.py +454 -0
- cortex_c2b/lm_bridge.py +338 -0
- cortex_c2b/organ_use.py +254 -0
- cortex_c2b/read_path.py +218 -0
- cortex_c2b/reconstruction_probe.py +471 -0
- cortex_c2b/write_path.py +176 -0
- cortex_data/__init__.py +439 -0
- cortex_data/relay.py +174 -0
- cortex_eval/__init__.py +20 -0
- cortex_eval/ablation_mqar.py +187 -0
- cortex_eval/confirmation_run.py +133 -0
- cortex_eval/domain_map.py +127 -0
- cortex_eval/external_baseline.py +148 -0
- cortex_eval/ingest_confirmation.py +194 -0
- cortex_eval/mqar.py +122 -0
- cortex_eval/multiseed.py +148 -0
- cortex_eval/recurrent_base.py +197 -0
- cortex_eval/recurrent_ladder.py +159 -0
- cortex_eval/resumable_ladder.py +231 -0
- cortex_eval/resumable_rb.py +207 -0
- cortex_eval/run_mqar.py +127 -0
- cortex_eval/serial_position.py +116 -0
- quantum_cortex-1.0.0.dist-info/METADATA +209 -0
- quantum_cortex-1.0.0.dist-info/RECORD +41 -0
- quantum_cortex-1.0.0.dist-info/WHEEL +5 -0
- quantum_cortex-1.0.0.dist-info/licenses/AUTHORS.md +30 -0
- quantum_cortex-1.0.0.dist-info/licenses/LICENSE +73 -0
- quantum_cortex-1.0.0.dist-info/licenses/LICENSE-DOCS.md +34 -0
- quantum_cortex-1.0.0.dist-info/licenses/NOTICE +50 -0
- quantum_cortex-1.0.0.dist-info/top_level.txt +5 -0
- train.py +885 -0
cortex_c2b/lifecycle.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""cortex_c2b.lifecycle -- Slice E: consolidate, demote, evict on a rhythm (ADR-007 D4 and D6).
|
|
2
|
+
|
|
3
|
+
The journal was a store (A), a gated write path (B), a router path (C) and a
|
|
4
|
+
measured organ (D). Slice E makes it ALIVE: on a rhythm (RES-9, the sleep of the
|
|
5
|
+
model) live entries are replayed into the persistent associative memory,
|
|
6
|
+
consolidated entries are demoted into content-addressed summaries (RES-16),
|
|
7
|
+
and forgetting is scheduled (D4.8: noise is what never contributes). Budgets are
|
|
8
|
+
homeostatic setpoints, not limits. It is a RES-17 state machine, the same law
|
|
9
|
+
the router uses (`cortex_c2.consolidation`), at the memory scale.
|
|
10
|
+
|
|
11
|
+
Eight invariants, validated by the founder on 2026-09-12 before any code, each
|
|
12
|
+
one exercised by `tests/test_c2b_lifecycle.py`:
|
|
13
|
+
|
|
14
|
+
1. No shortcut around the state machine. Every transition, every salience
|
|
15
|
+
change (credit, contract, scheduled decay) and every demotion link is an
|
|
16
|
+
event in the append only log; replaying the log rebuilds the lifecycle
|
|
17
|
+
state exactly (states, saliences, links, references).
|
|
18
|
+
2. Forward only, no resurrection. States move live -> consolidated ->
|
|
19
|
+
demoted -> evicted; an evicted entry never scores, never reads, never
|
|
20
|
+
returns. Payloads are released at the LAST pointer, by identity (the
|
|
21
|
+
content hash) and never by similarity: an irreversible operation rests
|
|
22
|
+
only on an exact relation.
|
|
23
|
+
3. Consolidation is a replay, not a tick. An entry becomes `consolidated`
|
|
24
|
+
only after it has been replayed into the persistent associative memory
|
|
25
|
+
and its reconstruction verified under a declared error. Replay priority:
|
|
26
|
+
salience, then primacy (RES-9).
|
|
27
|
+
4. Demotion keeps access. K SIMILAR consolidated entries (same index bucket,
|
|
28
|
+
pairwise cosine above a declared radius) become one summary: a content
|
|
29
|
+
addressed payload holding the K pointers and a digest; the sources move
|
|
30
|
+
to `demoted` and the summary is reachable from every source cue. A summary
|
|
31
|
+
can be demoted in turn (the hierarchy of RES-16); no cycle is possible.
|
|
32
|
+
5. Eviction is a verdict, never a size cut. Live entries are evicted only by
|
|
33
|
+
the noise tribunal (Slice B rule, never modulated by pressure);
|
|
34
|
+
consolidated entries only below a salience floor and old enough; demoted
|
|
35
|
+
sources only with their summary (cascade). A live entry above the floor
|
|
36
|
+
survives any pressure.
|
|
37
|
+
6. Budgets are setpoints. Per scope, bytes (non-evicted entries + payloads
|
|
38
|
+
alive) and read latency (entries touched per read, p95) are regulated
|
|
39
|
+
variables; pressure in [0, 1] rises with the distance to the setpoint and
|
|
40
|
+
acts on rhythms only (consolidation batch, eviction floor), never on the
|
|
41
|
+
rules above.
|
|
42
|
+
7. An explicit, deterministic rhythm. Phases fire on the injected logical
|
|
43
|
+
clock every P admitted writes, from a hashed configuration and a seed;
|
|
44
|
+
two runs with the same configuration and event sequence write identical
|
|
45
|
+
logs. Every phase appends one telemetry snapshot.
|
|
46
|
+
8. The H.M. check precedes the commit. A sentinel set of contracted facts
|
|
47
|
+
(the frozen H.M. generator) lives in the journal; every phase is planned
|
|
48
|
+
first, the sentinel probe is replayed on the plan, and the phase applies
|
|
49
|
+
only if contracted recall holds and the negative control does not rise.
|
|
50
|
+
Otherwise the phase is refused and recorded, pressure stays. No rollback
|
|
51
|
+
exists (invariant 2), so the check comes first -- as the breaker re routes
|
|
52
|
+
instead of killing. The protocol's thresholds are untouched.
|
|
53
|
+
|
|
54
|
+
9. The journal survives a full process restart (ADR-007 Decision 8, founder,
|
|
55
|
+
2026-09-12). A scheduler opened on a journal continues it: the persistent
|
|
56
|
+
memory is rebuilt from the consolidation order, the sentinels are found
|
|
57
|
+
again through their events, the phase and write counters resume. The test
|
|
58
|
+
that counts runs session B in a new process started from the disk alone,
|
|
59
|
+
sealed and clear (`test_the_journal_survives_a_full_process_restart`).
|
|
60
|
+
|
|
61
|
+
Design choices taken by the founder (2026-09-12): reference counting on
|
|
62
|
+
identity; the summary indexed under its source cues; refuse before commit; K,
|
|
63
|
+
P and the similarity radius in the hashed configuration; persistence declared
|
|
64
|
+
per component, sealed at rest per scope with the hub's QJE1 framing.
|
|
65
|
+
|
|
66
|
+
Pure Python over numpy; no torch. The persistent memory here is a stand-in
|
|
67
|
+
(`PersistentMemory`, a modern Hopfield store over cues); the C2 layer of
|
|
68
|
+
ADR-006 replaces it behind the same two calls when the journal is wired into
|
|
69
|
+
`train.py`. Written from scratch (our filon).
|
|
70
|
+
"""
|
|
71
|
+
from __future__ import annotations
|
|
72
|
+
|
|
73
|
+
import json
|
|
74
|
+
import math
|
|
75
|
+
from dataclasses import dataclass, asdict, field
|
|
76
|
+
|
|
77
|
+
import numpy as np
|
|
78
|
+
|
|
79
|
+
from cortex_data import _hash_obj
|
|
80
|
+
from cortex_c2b import (Journal, Entry, STATE_LIVE, STATE_CONSOLIDATED, STATE_DEMOTED,
|
|
81
|
+
STATE_EVICTED, SCHEMA_SUMMARY, CUE_DIM, JournalReadOnly)
|
|
82
|
+
from cortex_c2b.write_path import WritePath, WriteReport, noise_candidates
|
|
83
|
+
from cortex_c2b.read_path import JournalPath
|
|
84
|
+
from cortex_c2b.hm_protocol import generate_facts, Fact
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------- #
|
|
88
|
+
# Configuration -- hashed: whatever changes a result is in the hash (ADR-001/005) #
|
|
89
|
+
# ---------------------------------------------------------------------------- #
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class LifecycleConfig:
|
|
92
|
+
period_writes: int = 16 # P: one phase every P admitted writes
|
|
93
|
+
consolidate_batch: int = 8 # entries replayed per phase at pressure 0 (x2 at pressure 1)
|
|
94
|
+
replay_error_max: float = 0.05 # cosine distance reconstruction <-> cue that verifies a replay
|
|
95
|
+
demote_k: int = 4 # K: sources per summary
|
|
96
|
+
demote_min_cosine: float = 0.90 # similarity radius inside a group (pairwise cosine >= this)
|
|
97
|
+
evict_floor: float = 0.20 # salience floor for consolidated entries at pressure 0
|
|
98
|
+
evict_floor_max: float = 0.95 # the same floor at pressure 1
|
|
99
|
+
evict_min_age: float = 0.0 # logical age (now - t_written) before a consolidated entry is a candidate
|
|
100
|
+
tribunal_floor: float = 0.20 # Slice B rule for LIVE entries -- never modulated
|
|
101
|
+
tribunal_min_age: float = 0.0
|
|
102
|
+
decay: float = 0.90 # salience factor per phase on consolidated/demoted, contracted exempt
|
|
103
|
+
bytes_setpoint: int | None = None # regulated: non-evicted entries + payloads alive
|
|
104
|
+
latency_setpoint: float | None = None # regulated: p95 entries touched per read
|
|
105
|
+
band: float = 0.25 # pressure 0 at setpoint*(1-band), 1 at the setpoint
|
|
106
|
+
sentinel_facts: int = 20 # contracted facts planted at start (0 disables the check)
|
|
107
|
+
sentinel_negctrl: int = 10 # never planted facts probed at every phase
|
|
108
|
+
sentinel_seed: int = 0
|
|
109
|
+
seed: int = 0
|
|
110
|
+
|
|
111
|
+
def config_hash(self) -> str:
|
|
112
|
+
return _hash_obj(asdict(self))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------- #
|
|
116
|
+
# The persistent associative memory -- the "weights" side, as a stand-in #
|
|
117
|
+
# ---------------------------------------------------------------------------- #
|
|
118
|
+
class PersistentMemory:
|
|
119
|
+
"""Modern-Hopfield store over cues: replay adds a pattern, reconstruct returns
|
|
120
|
+
the softmax weighted combination of stored patterns for a query. A replay
|
|
121
|
+
is verified by reading back the cue itself. Interference between near
|
|
122
|
+
duplicates makes a replay fail verification -- an honest capacity effect,
|
|
123
|
+
reported, never hidden. The C2 layer takes over behind these two calls."""
|
|
124
|
+
|
|
125
|
+
def __init__(self, dim: int = CUE_DIM, beta: float = 16.0):
|
|
126
|
+
self.dim, self.beta = dim, beta
|
|
127
|
+
self._K = np.zeros((0, dim), dtype=np.float32)
|
|
128
|
+
|
|
129
|
+
def __len__(self):
|
|
130
|
+
return int(self._K.shape[0])
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _unit(v) -> np.ndarray:
|
|
134
|
+
v = np.asarray(v, dtype=np.float32)
|
|
135
|
+
return v / (np.linalg.norm(v) + 1e-8)
|
|
136
|
+
|
|
137
|
+
def reconstruct(self, cue) -> np.ndarray | None:
|
|
138
|
+
if len(self) == 0:
|
|
139
|
+
return None
|
|
140
|
+
q = self._unit(cue)
|
|
141
|
+
logits = self.beta * (self._K @ q)
|
|
142
|
+
w = np.exp(logits - logits.max()); w /= w.sum()
|
|
143
|
+
return w @ self._K
|
|
144
|
+
|
|
145
|
+
def replay(self, cue) -> float:
|
|
146
|
+
"""Store the pattern and return the verification error (cosine distance
|
|
147
|
+
between the reconstruction and the cue). The caller decides on the
|
|
148
|
+
declared threshold; a failed replay is withdrawn from the store."""
|
|
149
|
+
q = self._unit(cue)
|
|
150
|
+
self._K = np.vstack([self._K, q[None, :]])
|
|
151
|
+
r = self.reconstruct(q)
|
|
152
|
+
err = float(np.clip(1.0 - float(r @ q / (np.linalg.norm(r) + 1e-8)), 0.0, 1.0))
|
|
153
|
+
return err
|
|
154
|
+
|
|
155
|
+
def withdraw_last(self) -> None:
|
|
156
|
+
self._K = self._K[:-1]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------- #
|
|
160
|
+
# A phase plan -- computed first, checked, then applied (or refused) #
|
|
161
|
+
# ---------------------------------------------------------------------------- #
|
|
162
|
+
@dataclass
|
|
163
|
+
class PhasePlan:
|
|
164
|
+
consolidate: list[str] = field(default_factory=list)
|
|
165
|
+
demote: list[list[str]] = field(default_factory=list)
|
|
166
|
+
evict: list[str] = field(default_factory=list) # verdicts (tribunal + salience), cascade included
|
|
167
|
+
pressure: float = 0.0
|
|
168
|
+
floor_eff: float = 0.0
|
|
169
|
+
batch_eff: int = 0
|
|
170
|
+
|
|
171
|
+
def evict_set(self) -> set[str]:
|
|
172
|
+
return set(self.evict)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class LifecycleScheduler:
|
|
176
|
+
"""The sleep of the journal. Wire it above a Journal, its WritePath and its
|
|
177
|
+
JournalPath; route admitted writes through `write()` (or call `on_write`
|
|
178
|
+
after your own). Phases fire every P admitted writes on the logical clock
|
|
179
|
+
you pass as `now`; call `phase(now)` yourself for an explicit sleep.
|
|
180
|
+
|
|
181
|
+
Nothing here touches `Entry.state` or `Entry.salience` directly: the store's
|
|
182
|
+
primitives do, and each one is an event (invariant 1)."""
|
|
183
|
+
|
|
184
|
+
def __init__(self, journal: Journal, write_path: WritePath, journal_path: JournalPath,
|
|
185
|
+
cfg: LifecycleConfig | None = None, memory: PersistentMemory | None = None,
|
|
186
|
+
now: float = 0.0):
|
|
187
|
+
self.j, self.wp, self.jp = journal, write_path, journal_path
|
|
188
|
+
self.cfg = cfg or LifecycleConfig()
|
|
189
|
+
self.memory = memory if memory is not None else PersistentMemory(dim=journal.cue_dim) # an empty memory is falsy
|
|
190
|
+
self.replay_failed = 0 # telemetry only; the log's phase snapshots carry the totals
|
|
191
|
+
self.last_phase: dict | None = None
|
|
192
|
+
# ---- Decision 8: a scheduler opened on a journal continues it, it does not restart it
|
|
193
|
+
for eid in journal.consolidated_order: # the persistent memory, in consolidation order
|
|
194
|
+
self.memory.replay(journal._entries[eid].cue) # verified when it was consolidated
|
|
195
|
+
self.phases = journal.phase_count
|
|
196
|
+
self.refused = journal.refused_count
|
|
197
|
+
# the sentinel set: contracted facts from the frozen H.M. generator (invariant 8)
|
|
198
|
+
self.sentinels: list[tuple[str, Fact]] = []
|
|
199
|
+
self.negctrl: list[Fact] = []
|
|
200
|
+
if self.cfg.sentinel_facts > 0:
|
|
201
|
+
if journal.sentinels_recorded:
|
|
202
|
+
facts, _ = generate_facts(self.cfg.sentinel_facts, seed=self.cfg.sentinel_seed)
|
|
203
|
+
self.negctrl, _ = generate_facts(self.cfg.sentinel_negctrl, seed=self.cfg.sentinel_seed + 10_000)
|
|
204
|
+
for i, eid in journal.sentinels_recorded:
|
|
205
|
+
if i >= len(facts) or eid not in journal._entries:
|
|
206
|
+
raise ValueError("sentinel events do not match this configuration's sentinel set")
|
|
207
|
+
self.sentinels.append((eid, facts[i]))
|
|
208
|
+
else:
|
|
209
|
+
self._plant_sentinels(now)
|
|
210
|
+
sentinel_ids = {eid for eid, _ in self.sentinels}
|
|
211
|
+
self.writes_since_phase = sum(1 for eid in journal.writes_since_last_phase
|
|
212
|
+
if eid not in sentinel_ids
|
|
213
|
+
and journal._entries[eid].schema_id != SCHEMA_SUMMARY)
|
|
214
|
+
|
|
215
|
+
# ------------------------------------------------------------------ writes --
|
|
216
|
+
def write(self, cue, payload: bytes, schema_id: str, now: float):
|
|
217
|
+
"""The one entry point in the lifecycle world: gated write, index, rhythm.
|
|
218
|
+
A journal in read only mode refuses the write and says so in the report
|
|
219
|
+
(Decision 9); a `stop` policy lets StorageExhausted propagate."""
|
|
220
|
+
try:
|
|
221
|
+
rep = self.wp.write(cue, payload, schema_id, now=now)
|
|
222
|
+
except JournalReadOnly as e:
|
|
223
|
+
return WriteReport(admitted=False, surprise=float("nan"), reason=f"refused, {e}")
|
|
224
|
+
if rep.admitted:
|
|
225
|
+
self.jp.on_write(rep.entry)
|
|
226
|
+
self.on_write(rep.entry, now)
|
|
227
|
+
return rep
|
|
228
|
+
|
|
229
|
+
def on_write(self, entry: Entry, now: float) -> dict | None:
|
|
230
|
+
"""Count an admitted write; fire a phase every P (invariant 7)."""
|
|
231
|
+
self.writes_since_phase += 1
|
|
232
|
+
if self.writes_since_phase >= self.cfg.period_writes:
|
|
233
|
+
self.writes_since_phase = 0
|
|
234
|
+
return self.phase(now)
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def contract(self, entry_id: str, now: float | None = None) -> None:
|
|
238
|
+
"""Credit an entry to full salience under a contract: decay never touches it."""
|
|
239
|
+
self.j.set_salience(entry_id, 1.0, why="contract", now=now)
|
|
240
|
+
|
|
241
|
+
# -------------------------------------------------------------- sentinels --
|
|
242
|
+
def _plant_sentinels(self, now: float) -> None:
|
|
243
|
+
facts, _ = generate_facts(self.cfg.sentinel_facts, seed=self.cfg.sentinel_seed)
|
|
244
|
+
self.negctrl, _ = generate_facts(self.cfg.sentinel_negctrl, seed=self.cfg.sentinel_seed + 10_000)
|
|
245
|
+
for i, f in enumerate(facts):
|
|
246
|
+
rep = self.wp.write(f.cue, f.statement.encode(), f.schema, now=now)
|
|
247
|
+
if rep.admitted:
|
|
248
|
+
self.jp.on_write(rep.entry)
|
|
249
|
+
self.contract(rep.entry.entry_id, now)
|
|
250
|
+
self.j.mark_sentinel(i, rep.entry.entry_id) # an event: a restart finds them again
|
|
251
|
+
self.sentinels.append((rep.entry.entry_id, f))
|
|
252
|
+
|
|
253
|
+
def _hit(self, fact: Fact) -> tuple[str | None, bytes | None]:
|
|
254
|
+
"""The effective entry a probe of this fact lands on, and its payload."""
|
|
255
|
+
hits = self.jp.retrieve(fact.cue, k=1)
|
|
256
|
+
if not hits:
|
|
257
|
+
return None, None
|
|
258
|
+
e, payload, _ = hits[0]
|
|
259
|
+
return e.entry_id, payload
|
|
260
|
+
|
|
261
|
+
def sentinel_probe(self, plan: PhasePlan | None = None) -> tuple[float, float]:
|
|
262
|
+
"""Contracted recall and negative-control rate through the real read path,
|
|
263
|
+
with a plan overlaid: a hit that the plan would evict counts as lost. A
|
|
264
|
+
plan can only remove entries, so it can never raise the negative
|
|
265
|
+
control; the guard is kept because the rule says so."""
|
|
266
|
+
gone = plan.evict_set() if plan else set()
|
|
267
|
+
if not self.sentinels:
|
|
268
|
+
return 1.0, 0.0
|
|
269
|
+
recalled = 0
|
|
270
|
+
for eid, f in self.sentinels:
|
|
271
|
+
hit, payload = self._hit(f)
|
|
272
|
+
if hit is not None and hit not in gone and f.attr in payload.decode("utf-8", errors="ignore"):
|
|
273
|
+
recalled += 1
|
|
274
|
+
halluc = 0
|
|
275
|
+
for f in self.negctrl:
|
|
276
|
+
hit, payload = self._hit(f)
|
|
277
|
+
if hit is not None and hit not in gone and f.attr in payload.decode("utf-8", errors="ignore"):
|
|
278
|
+
halluc += 1
|
|
279
|
+
return recalled / len(self.sentinels), (halluc / len(self.negctrl) if self.negctrl else 0.0)
|
|
280
|
+
|
|
281
|
+
# --------------------------------------------------------------- pressure --
|
|
282
|
+
@staticmethod
|
|
283
|
+
def _pressure_of(usage: float, setpoint: float | None, band: float) -> float:
|
|
284
|
+
if not setpoint or setpoint <= 0:
|
|
285
|
+
return 0.0
|
|
286
|
+
return float(np.clip((usage / setpoint - (1.0 - band)) / band, 0.0, 1.0))
|
|
287
|
+
|
|
288
|
+
def pressure(self) -> dict:
|
|
289
|
+
b = self.j.regulated_bytes()
|
|
290
|
+
lat = self.jp.index.p95_touched()
|
|
291
|
+
pb = self._pressure_of(b, self.cfg.bytes_setpoint, self.cfg.band)
|
|
292
|
+
pl = self._pressure_of(lat, self.cfg.latency_setpoint, self.cfg.band)
|
|
293
|
+
# Decision 9, invariant 2: free disk space is regulated too -- pressure rises as the
|
|
294
|
+
# free space approaches the declared floor (0 at floor*(1+band), 1 at the floor)
|
|
295
|
+
floor, free = self.j.disk_free_floor_bytes, self.j.disk_free()
|
|
296
|
+
pd = 0.0
|
|
297
|
+
if floor and free is not None:
|
|
298
|
+
pd = float(np.clip((floor * (1.0 + self.cfg.band) - free) / (floor * self.cfg.band), 0.0, 1.0))
|
|
299
|
+
return {"bytes": b, "latency_p95": lat, "disk_free": free, "pressure_bytes": pb,
|
|
300
|
+
"pressure_latency": pl, "pressure_disk": pd, "pressure": max(pb, pl, pd)}
|
|
301
|
+
|
|
302
|
+
# ------------------------------------------------------------------- plan --
|
|
303
|
+
def plan(self, now: float) -> PhasePlan:
|
|
304
|
+
cfg, J = self.cfg, self.j
|
|
305
|
+
pr = self.pressure()["pressure"]
|
|
306
|
+
p = PhasePlan(pressure=pr,
|
|
307
|
+
floor_eff=cfg.evict_floor + pr * (cfg.evict_floor_max - cfg.evict_floor),
|
|
308
|
+
batch_eff=int(math.ceil(cfg.consolidate_batch * (1.0 + pr))))
|
|
309
|
+
# 3. consolidate: salience first, then primacy (oldest first), deterministic
|
|
310
|
+
live = [(eid, e) for eid, e in J._entries.items() if e.state == STATE_LIVE]
|
|
311
|
+
live.sort(key=lambda t: (-t[1].salience, t[1].t_written, t[0]))
|
|
312
|
+
p.consolidate = [eid for eid, _ in live[: p.batch_eff]]
|
|
313
|
+
# 4. demote: K similar consolidated entries per group -- similarity means the
|
|
314
|
+
# index neighbourhood (a bucket in any table) AND an exact pairwise cosine
|
|
315
|
+
# above the declared radius; deterministic order; never a scan of the store
|
|
316
|
+
consolidated = [eid for eid, e in sorted(J._entries.items()) if e.state == STATE_CONSOLIDATED]
|
|
317
|
+
cset, grouped = set(consolidated), set()
|
|
318
|
+
for eid in consolidated:
|
|
319
|
+
if eid in grouped:
|
|
320
|
+
continue
|
|
321
|
+
cue = np.asarray(J._entries[eid].cue, dtype=np.float32)
|
|
322
|
+
scored = []
|
|
323
|
+
for c in self.jp.index.candidates(cue):
|
|
324
|
+
if c in cset and c not in grouped and c != eid:
|
|
325
|
+
v = np.asarray(J._entries[c].cue, dtype=np.float32)
|
|
326
|
+
cos = float(cue @ v / (np.linalg.norm(cue) * np.linalg.norm(v) + 1e-8))
|
|
327
|
+
if cos >= cfg.demote_min_cosine:
|
|
328
|
+
scored.append((-cos, c))
|
|
329
|
+
members = [eid] + [c for _, c in sorted(scored)[: cfg.demote_k - 1]]
|
|
330
|
+
if len(members) == cfg.demote_k and self._min_pairwise_cosine(members) >= cfg.demote_min_cosine:
|
|
331
|
+
p.demote.append(members); grouped.update(members)
|
|
332
|
+
# 5. evict: verdicts only
|
|
333
|
+
evict = list(noise_candidates(J, cfg.tribunal_floor, cfg.tribunal_min_age, now)) # live, the tribunal
|
|
334
|
+
for eid, e in sorted(J._entries.items()):
|
|
335
|
+
if (e.state == STATE_CONSOLIDATED and eid not in grouped and e.salience < p.floor_eff
|
|
336
|
+
and (now - e.t_written) >= cfg.evict_min_age):
|
|
337
|
+
evict.append(eid)
|
|
338
|
+
for eid in list(evict): # cascade, whatever the branch: a summary
|
|
339
|
+
for src in J.summary_sources.get(eid, []): # takes its sources with it (the store
|
|
340
|
+
if J._entries[src].state != STATE_EVICTED and src not in evict: # enforces the same rule)
|
|
341
|
+
evict.append(src)
|
|
342
|
+
for src, sid in sorted(J.summary_of.items()): # repair: a demoted source whose summary is
|
|
343
|
+
if (J._entries[sid].state == STATE_EVICTED and J._entries[src].state == STATE_DEMOTED
|
|
344
|
+
and src not in evict): # already gone (a cascade cut by a storage
|
|
345
|
+
evict.append(src) # failure, policy stop) is evicted now
|
|
346
|
+
p.evict = evict
|
|
347
|
+
return p
|
|
348
|
+
|
|
349
|
+
def _min_pairwise_cosine(self, ids: list[str]) -> float:
|
|
350
|
+
V = np.stack([np.asarray(self.j._entries[i].cue, dtype=np.float32) for i in ids])
|
|
351
|
+
V = V / (np.linalg.norm(V, axis=1, keepdims=True) + 1e-8)
|
|
352
|
+
S = V @ V.T
|
|
353
|
+
return float(S[np.triu_indices(len(ids), k=1)].min()) if len(ids) > 1 else 1.0
|
|
354
|
+
|
|
355
|
+
# ------------------------------------------------------------------ phase --
|
|
356
|
+
def phase(self, now: float) -> dict:
|
|
357
|
+
"""One sleep: plan, check the sentinels on the plan, then apply or refuse."""
|
|
358
|
+
cfg = self.cfg
|
|
359
|
+
p = self.plan(now)
|
|
360
|
+
recall_before, neg_before = self.sentinel_probe(None)
|
|
361
|
+
recall_after, neg_after = self.sentinel_probe(p)
|
|
362
|
+
ok = (recall_after >= 1.0) and (neg_after <= neg_before)
|
|
363
|
+
rec = {"phase": self.phases + 1, "now": now, "config_hash": cfg.config_hash(),
|
|
364
|
+
"pressure": round(p.pressure, 4), "floor_eff": round(p.floor_eff, 4),
|
|
365
|
+
"batch_eff": p.batch_eff, "planned": {"consolidate": len(p.consolidate),
|
|
366
|
+
"demote_groups": len(p.demote), "evict": len(p.evict)},
|
|
367
|
+
"sentinel_recall": round(recall_after, 4), "sentinel_negctrl": round(neg_after, 4),
|
|
368
|
+
"refused": not ok}
|
|
369
|
+
if not ok:
|
|
370
|
+
self.refused += 1
|
|
371
|
+
rec["reason"] = ("contracted recall would fall to %.3f" % recall_after if recall_after < 1.0
|
|
372
|
+
else "negative control would rise")
|
|
373
|
+
rec.update(self._log_telemetry())
|
|
374
|
+
self.j.phase_event(rec)
|
|
375
|
+
self.phases += 1
|
|
376
|
+
self.last_phase = rec
|
|
377
|
+
return rec
|
|
378
|
+
applied = self._apply(p, now)
|
|
379
|
+
self.j.decay(cfg.decay) # one event; contracted exempt
|
|
380
|
+
rec["applied"] = applied
|
|
381
|
+
rec.update(self._log_telemetry())
|
|
382
|
+
self.j.phase_event(rec)
|
|
383
|
+
self.phases += 1
|
|
384
|
+
self.last_phase = rec
|
|
385
|
+
return rec
|
|
386
|
+
|
|
387
|
+
def _apply(self, p: PhasePlan, now: float) -> dict:
|
|
388
|
+
J, cfg = self.j, self.cfg
|
|
389
|
+
consolidated, failed = 0, 0
|
|
390
|
+
for eid in p.consolidate: # 3. replay, verify, then transition
|
|
391
|
+
err = self.memory.replay(J._entries[eid].cue)
|
|
392
|
+
if err <= cfg.replay_error_max:
|
|
393
|
+
J.transition(eid, STATE_CONSOLIDATED); consolidated += 1
|
|
394
|
+
else:
|
|
395
|
+
self.memory.withdraw_last(); failed += 1
|
|
396
|
+
self.replay_failed += failed
|
|
397
|
+
demoted = 0
|
|
398
|
+
for group in p.demote: # 4. K similar -> one summary
|
|
399
|
+
self._demote(group, now); demoted += 1
|
|
400
|
+
evicted = 0
|
|
401
|
+
for eid in p.evict: # 5. verdicts (cascade already listed)
|
|
402
|
+
if J._entries[eid].state != STATE_EVICTED:
|
|
403
|
+
J.transition(eid, STATE_EVICTED); evicted += 1
|
|
404
|
+
return {"consolidated": consolidated, "replay_failed": failed,
|
|
405
|
+
"demoted_groups": demoted, "evicted": evicted}
|
|
406
|
+
|
|
407
|
+
def _demote(self, group: list[str], now: float) -> Entry:
|
|
408
|
+
J = self.j
|
|
409
|
+
cues = np.stack([np.asarray(J._entries[i].cue, dtype=np.float32) for i in group])
|
|
410
|
+
centroid = cues.mean(axis=0); centroid = centroid / (np.linalg.norm(centroid) + 1e-8)
|
|
411
|
+
level = 1 + max(J.summary_level.get(i, 0) for i in group)
|
|
412
|
+
payload = json.dumps({"kind": SCHEMA_SUMMARY, "level": level,
|
|
413
|
+
"pointers": [J._entries[i].pointer for i in group],
|
|
414
|
+
"sources": list(group),
|
|
415
|
+
"digest": [round(float(x), 3) for x in centroid[:8]]},
|
|
416
|
+
separators=(",", ":")).encode()
|
|
417
|
+
salience = max(J._entries[i].salience for i in group)
|
|
418
|
+
summary = J.write(centroid, payload, salience=salience, schema_id=SCHEMA_SUMMARY, now=now)
|
|
419
|
+
self.jp.on_write(summary)
|
|
420
|
+
J.demote(summary.entry_id, list(group), level=level) # the link (an event)
|
|
421
|
+
for src in group:
|
|
422
|
+
J.transition(src, STATE_DEMOTED) # each a transition event
|
|
423
|
+
self.jp.index.set_alias(src, summary.entry_id) # reachable from every source cue
|
|
424
|
+
if any(i in J.contracted for i in group):
|
|
425
|
+
self.contract(summary.entry_id, now) # a contract survives its demotion
|
|
426
|
+
return summary
|
|
427
|
+
|
|
428
|
+
# -------------------------------------------------------------- telemetry --
|
|
429
|
+
def _telemetry(self) -> dict:
|
|
430
|
+
snap = self.j.snapshot()
|
|
431
|
+
pr = self.pressure()
|
|
432
|
+
return {"bytes": pr["bytes"], "latency_p95": round(pr["latency_p95"], 2),
|
|
433
|
+
"disk_free": pr["disk_free"], "pressure_disk": round(pr["pressure_disk"], 4),
|
|
434
|
+
"storage_mode": self.j.mode, "unpersisted_events": len(self.j.spill),
|
|
435
|
+
"scope_hash": self.j.scope_config_hash(),
|
|
436
|
+
"pressure_bytes": round(pr["pressure_bytes"], 4),
|
|
437
|
+
"pressure_latency": round(pr["pressure_latency"], 4),
|
|
438
|
+
"live": snap[STATE_LIVE], "consolidated": snap[STATE_CONSOLIDATED],
|
|
439
|
+
"demoted": snap[STATE_DEMOTED], "evicted": snap[STATE_EVICTED],
|
|
440
|
+
"summaries": snap["summaries"], "contracted": snap["contracted"],
|
|
441
|
+
"memory_patterns": len(self.memory), "phases_refused": self.refused,
|
|
442
|
+
"replay_failed_total": self.replay_failed}
|
|
443
|
+
|
|
444
|
+
def _log_telemetry(self) -> dict:
|
|
445
|
+
"""What goes into the phase event: everything but the raw free disk bytes, an
|
|
446
|
+
environment reading that would break the byte identity of two identical
|
|
447
|
+
runs (invariant 7). The disk pressure stays: it is a function of the
|
|
448
|
+
declared floor, zero when none is declared."""
|
|
449
|
+
t = self._telemetry(); t.pop("disk_free", None)
|
|
450
|
+
return t
|
|
451
|
+
|
|
452
|
+
def snapshot(self) -> dict:
|
|
453
|
+
"""The RES-17 snapshot for C6 telemetry / the run record's standard_suite."""
|
|
454
|
+
return {"config_hash": self.cfg.config_hash(), "phases": self.phases, **self._telemetry()}
|