superlocalmemory 4.1.3 → 4.1.5
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +64 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +2 -2
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +2 -2
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +2 -2
- package/plugin-src/skills/slm-profile/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +2 -2
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +8 -2
- package/src/superlocalmemory/cli/daemon.py +69 -6
- package/src/superlocalmemory/cli/diagnostics_cmd.py +74 -1
- package/src/superlocalmemory/cli/main.py +12 -0
- package/src/superlocalmemory/core/remember_runtime.py +12 -1
- package/src/superlocalmemory/reliability/__init__.py +45 -0
- package/src/superlocalmemory/reliability/join_liveness.py +301 -0
- package/src/superlocalmemory/reliability/prior_distance.py +243 -0
- package/src/superlocalmemory/server/routes/backup.py +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Has a Bayesian learner's posterior actually moved off its prior?
|
|
6
|
+
|
|
7
|
+
A Thompson-sampling selector over Beta posteriors records a play, applies a
|
|
8
|
+
reward, and reports both. None of that tells you whether it learned anything.
|
|
9
|
+
Its own counters cannot: a reward channel that emits one constant value
|
|
10
|
+
increments the posterior on every play, so the play count rises, the timestamps
|
|
11
|
+
advance, and the dashboard looks alive while the distribution never moves.
|
|
12
|
+
|
|
13
|
+
This is not a hypothetical. A Beta posterior updated as
|
|
14
|
+
``alpha += r; beta += (1 - r)`` is stationary in mean for exactly one reward
|
|
15
|
+
value: ``r = 0.5``. And that value is the usual neutral fallback when a reward
|
|
16
|
+
cannot be attributed to a play. So the failure that produces *no learning at
|
|
17
|
+
all* is also the failure that produces *the most normal-looking counters*.
|
|
18
|
+
|
|
19
|
+
The signature is exact, not statistical. With a ``Beta(a0, b0)`` prior and *n*
|
|
20
|
+
observations all equal to 0.5::
|
|
21
|
+
|
|
22
|
+
alpha - a0 == beta - b0 == n * 0.5
|
|
23
|
+
|
|
24
|
+
0.5 is exactly representable in binary floating point, so ``n * 0.5`` is exact
|
|
25
|
+
for any plausible *n*. A learner matching that identity on every unit has
|
|
26
|
+
provably received the neutral value every single time — there is no sampling
|
|
27
|
+
noise to argue about, and one run is enough to establish it.
|
|
28
|
+
|
|
29
|
+
What this check does NOT claim: that a moving posterior is a *good* one. Motion
|
|
30
|
+
off the prior is necessary for learning, not sufficient. This distinguishes
|
|
31
|
+
"receiving signal" from "receiving nothing", which is the distinction the
|
|
32
|
+
learner's own metrics cannot make.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
import sqlite3
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger("superlocalmemory.reliability.prior_distance")
|
|
43
|
+
|
|
44
|
+
#: Below this many observations in total, a posterior sitting at its prior is
|
|
45
|
+
#: expected rather than suspicious, so no verdict is issued.
|
|
46
|
+
DEFAULT_MIN_OBSERVATIONS = 20
|
|
47
|
+
|
|
48
|
+
#: And below this many observations *per unit*. An aggregate floor alone lets a
|
|
49
|
+
#: store with many units and few observations report STALLED on units that were
|
|
50
|
+
#: never played at all.
|
|
51
|
+
_MIN_OBSERVATIONS_PER_UNIT = 2.0
|
|
52
|
+
|
|
53
|
+
#: Distance from the prior mean below which a unit counts as unmoved. Kept well
|
|
54
|
+
#: above float noise so that a genuinely tiny update is not reported as none.
|
|
55
|
+
_MEAN_EPSILON = 1e-9
|
|
56
|
+
|
|
57
|
+
#: Beta learners in this store, as (table, unit column, observation column).
|
|
58
|
+
#: Each is a Beta(1, 1) posterior over a named unit.
|
|
59
|
+
_BETA_LEARNERS: tuple[tuple[str, str, str | None], ...] = (
|
|
60
|
+
("bandit_arms", "arm_id", "plays"),
|
|
61
|
+
("source_quality", "source_id", None),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_PRIOR_ALPHA = 1.0
|
|
65
|
+
_PRIOR_BETA = 1.0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class LearnerVerdict:
|
|
70
|
+
"""One Beta learner, and whether its posterior has moved."""
|
|
71
|
+
|
|
72
|
+
table: str
|
|
73
|
+
units: int
|
|
74
|
+
units_at_prior_mean: int
|
|
75
|
+
units_matching_neutral_identity: int
|
|
76
|
+
observations: int
|
|
77
|
+
verdict: str
|
|
78
|
+
detail: str
|
|
79
|
+
sample: tuple[tuple[str, float, float], ...] = field(default=())
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def is_stalled(self) -> bool:
|
|
83
|
+
return self.verdict == "STALLED"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _table_exists(conn: sqlite3.Connection, table: str) -> bool:
|
|
87
|
+
row = conn.execute(
|
|
88
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1",
|
|
89
|
+
(table,),
|
|
90
|
+
).fetchone()
|
|
91
|
+
return row is not None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
|
|
95
|
+
return {r[1] for r in conn.execute(f'PRAGMA table_info("{table}")')}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _inspect_learner(
|
|
99
|
+
conn: sqlite3.Connection,
|
|
100
|
+
table: str,
|
|
101
|
+
unit_column: str,
|
|
102
|
+
observation_column: str | None,
|
|
103
|
+
*,
|
|
104
|
+
min_observations: int,
|
|
105
|
+
) -> LearnerVerdict | None:
|
|
106
|
+
"""Inspect one Beta learner. Returns None when the table is absent."""
|
|
107
|
+
if not _table_exists(conn, table):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
available = _columns(conn, table)
|
|
111
|
+
if not {"alpha", "beta"}.issubset(available):
|
|
112
|
+
return None
|
|
113
|
+
unit = unit_column if unit_column in available else "rowid"
|
|
114
|
+
obs_col = observation_column if (observation_column or "") in available else None
|
|
115
|
+
|
|
116
|
+
select = f'SELECT "{unit}", alpha, beta'
|
|
117
|
+
select += f', "{obs_col}"' if obs_col else ", NULL"
|
|
118
|
+
rows = conn.execute(f'{select} FROM "{table}"').fetchall()
|
|
119
|
+
if not rows:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
units = len(rows)
|
|
123
|
+
at_prior_mean = 0
|
|
124
|
+
neutral_identity = 0
|
|
125
|
+
observations = 0
|
|
126
|
+
for _unit, alpha, beta, obs in rows:
|
|
127
|
+
alpha = float(alpha or 0.0)
|
|
128
|
+
beta = float(beta or 0.0)
|
|
129
|
+
total = alpha + beta
|
|
130
|
+
if total > 0 and abs(alpha / total - 0.5) < _MEAN_EPSILON:
|
|
131
|
+
at_prior_mean += 1
|
|
132
|
+
n = int(obs) if obs is not None else None
|
|
133
|
+
if n is None:
|
|
134
|
+
# No per-unit observation count. Infer it from the identity itself:
|
|
135
|
+
# n = (alpha - a0) / 0.5 only holds if every reward was neutral, so
|
|
136
|
+
# this is checked, never assumed.
|
|
137
|
+
candidate = (alpha - _PRIOR_ALPHA) * 2.0
|
|
138
|
+
n = int(round(candidate)) if candidate >= 0 else 0
|
|
139
|
+
observations += n
|
|
140
|
+
if n > 0 and (
|
|
141
|
+
abs((alpha - _PRIOR_ALPHA) - n * 0.5) < _MEAN_EPSILON
|
|
142
|
+
and abs((beta - _PRIOR_BETA) - n * 0.5) < _MEAN_EPSILON
|
|
143
|
+
):
|
|
144
|
+
neutral_identity += 1
|
|
145
|
+
|
|
146
|
+
sample = tuple(
|
|
147
|
+
(str(r[0]), float(r[1] or 0.0), float(r[2] or 0.0)) for r in rows[:3]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# The floor has to bind per unit, not in aggregate. 165 arms sharing 20
|
|
151
|
+
# observations leaves most of them untouched at exactly the prior, which
|
|
152
|
+
# satisfies the unmoved test for a reason that carries no information. A
|
|
153
|
+
# verdict of STALLED must mean "measured inert", never "too sparse to tell".
|
|
154
|
+
per_unit = observations / units if units else 0.0
|
|
155
|
+
if observations < min_observations or per_unit < _MIN_OBSERVATIONS_PER_UNIT:
|
|
156
|
+
verdict = "INSUFFICIENT_DATA"
|
|
157
|
+
detail = (
|
|
158
|
+
f"{observations} observations across {units} units "
|
|
159
|
+
f"({per_unit:.2f} per unit) is too sparse for an unmoved posterior to "
|
|
160
|
+
f"mean anything; the floors are {min_observations} in total and "
|
|
161
|
+
f"{_MIN_OBSERVATIONS_PER_UNIT:g} per unit."
|
|
162
|
+
)
|
|
163
|
+
elif neutral_identity == units:
|
|
164
|
+
verdict = "STALLED"
|
|
165
|
+
detail = (
|
|
166
|
+
f"All {units} units satisfy (alpha-{_PRIOR_ALPHA:g}) == "
|
|
167
|
+
f"(beta-{_PRIOR_BETA:g}) == n/2 exactly across {observations} "
|
|
168
|
+
f"observations, so the rewards sum to exactly n/2 on every unit and "
|
|
169
|
+
f"each posterior mean is still {0.5}. No unit has acquired any "
|
|
170
|
+
f"preference. Note the identity constrains the SUM: it is also "
|
|
171
|
+
f"satisfied by a symmetric non-neutral stream, so confirm against a "
|
|
172
|
+
f"per-observation record before concluding the reward was constant."
|
|
173
|
+
)
|
|
174
|
+
elif at_prior_mean == units:
|
|
175
|
+
verdict = "STALLED"
|
|
176
|
+
detail = (
|
|
177
|
+
f"All {units} units sit at posterior mean 0.5 after {observations} "
|
|
178
|
+
f"observations, without matching the exact neutral identity. The "
|
|
179
|
+
f"updates are symmetric but not uniformly 0.5 — inspect the reward "
|
|
180
|
+
f"source."
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
moved = units - at_prior_mean
|
|
184
|
+
verdict = "MOVING"
|
|
185
|
+
detail = (
|
|
186
|
+
f"{moved} of {units} units have moved off the prior mean across "
|
|
187
|
+
f"{observations} observations."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return LearnerVerdict(
|
|
191
|
+
table=table,
|
|
192
|
+
units=units,
|
|
193
|
+
units_at_prior_mean=at_prior_mean,
|
|
194
|
+
units_matching_neutral_identity=neutral_identity,
|
|
195
|
+
observations=observations,
|
|
196
|
+
verdict=verdict,
|
|
197
|
+
detail=detail,
|
|
198
|
+
sample=sample,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def check_beta_learners(
|
|
203
|
+
learning_db: Any,
|
|
204
|
+
*,
|
|
205
|
+
min_observations: int = DEFAULT_MIN_OBSERVATIONS,
|
|
206
|
+
) -> list[LearnerVerdict]:
|
|
207
|
+
"""Report, per Beta learner in ``learning_db``, whether it has learned.
|
|
208
|
+
|
|
209
|
+
``learning_db`` may be a path or an open :class:`sqlite3.Connection`. The
|
|
210
|
+
database is only read. Fail-soft: any error yields an empty list and a log
|
|
211
|
+
line, because a diagnostic must never be the reason something breaks.
|
|
212
|
+
"""
|
|
213
|
+
owns_connection = not isinstance(learning_db, sqlite3.Connection)
|
|
214
|
+
conn: sqlite3.Connection | None = None
|
|
215
|
+
try:
|
|
216
|
+
if owns_connection:
|
|
217
|
+
conn = sqlite3.connect(f"file:{learning_db}?mode=ro", uri=True)
|
|
218
|
+
else:
|
|
219
|
+
conn = learning_db
|
|
220
|
+
out: list[LearnerVerdict] = []
|
|
221
|
+
for table, unit_column, observation_column in _BETA_LEARNERS:
|
|
222
|
+
verdict = _inspect_learner(
|
|
223
|
+
conn,
|
|
224
|
+
table,
|
|
225
|
+
unit_column,
|
|
226
|
+
observation_column,
|
|
227
|
+
min_observations=min_observations,
|
|
228
|
+
)
|
|
229
|
+
if verdict is not None:
|
|
230
|
+
out.append(verdict)
|
|
231
|
+
return out
|
|
232
|
+
except Exception:
|
|
233
|
+
logger.debug("prior-distance check skipped", exc_info=True)
|
|
234
|
+
return []
|
|
235
|
+
finally:
|
|
236
|
+
if owns_connection and conn is not None:
|
|
237
|
+
try:
|
|
238
|
+
conn.close()
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
__all__ = ["DEFAULT_MIN_OBSERVATIONS", "LearnerVerdict", "check_beta_learners"]
|
|
@@ -737,7 +737,7 @@ a { color: #00D4AA; }
|
|
|
737
737
|
<p class="sub">Back up your memories to a private GitHub repository.</p>
|
|
738
738
|
|
|
739
739
|
<label>Personal Access Token</label>
|
|
740
|
-
<input type="password" id="pat" placeholder="
|
|
740
|
+
<input type="password" id="pat" placeholder="GitHub personal access token">
|
|
741
741
|
|
|
742
742
|
<label>Repository Name</label>
|
|
743
743
|
<input type="text" id="repo" value="slm-backup" placeholder="slm-backup">
|