superlocalmemory 3.3.24 → 3.3.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "3.3.24",
3
+ "version": "3.3.26",
4
4
  "description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
5
5
  "keywords": [
6
6
  "ai-memory",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "3.3.24"
3
+ version = "3.3.26"
4
4
  description = "Information-geometric agent memory with mathematical guarantees"
5
5
  readme = "README.md"
6
6
  license = {text = "Elastic-2.0"}
@@ -259,6 +259,8 @@ class ForgettingConfig:
259
259
  learning_rate: float = 1.0 # eta in spaced repetition update
260
260
  # Coupling
261
261
  forgetting_drift_scale: float = 0.5 # How strongly forgetting affects Langevin drift
262
+ # Trust-weighted forgetting (Paper 3, Section 5.5)
263
+ trust_kappa: float = 2.0 # Sensitivity: lambda_eff = lambda * (1 + trust_kappa * (1 - tau))
262
264
  # Scheduler
263
265
  scheduler_interval_minutes: int = 30 # How often to recompute retentions
264
266
  # Immunity
@@ -202,31 +202,69 @@ class ForgettingScheduler:
202
202
  - confirmation_count mapped from atomic_facts.evidence_count
203
203
  - emotional_salience from atomic_facts.emotional_valence
204
204
  """
205
- rows = self._db.execute(
206
- "SELECT f.fact_id, "
207
- " COALESCE(al.access_count, 0) as access_count, "
208
- " COALESCE(fi.pagerank_score, 0.0) as importance, "
209
- " COALESCE(f.evidence_count, 0) as confirmation_count, "
210
- " f.created_at, "
211
- " COALESCE(r.last_accessed_at, f.created_at) as last_accessed_at, "
212
- " COALESCE(f.emotional_valence, 0.0) as emotional_salience "
213
- "FROM atomic_facts f "
214
- "LEFT JOIN ("
215
- " SELECT fact_id, COUNT(*) as access_count "
216
- " FROM fact_access_log WHERE profile_id = ? GROUP BY fact_id"
217
- ") al ON f.fact_id = al.fact_id "
218
- "LEFT JOIN fact_importance fi "
219
- " ON f.fact_id = fi.fact_id AND fi.profile_id = ? "
220
- "LEFT JOIN fact_retention r "
221
- " ON f.fact_id = r.fact_id AND r.profile_id = ? "
222
- "WHERE f.profile_id = ? "
223
- "AND f.fact_id NOT IN ("
224
- " SELECT json_each.value "
225
- " FROM core_memory_blocks, json_each(core_memory_blocks.source_fact_ids) "
226
- " WHERE core_memory_blocks.profile_id = ?"
227
- ")",
228
- (profile_id, profile_id, profile_id, profile_id, profile_id),
229
- )
205
+ # V3.3.26: Trust-weighted forgetting — look up trust score for
206
+ # the agent that created each fact. Falls back to 1.0 if trust_scores
207
+ # table or created_by column is unavailable.
208
+ trust_available = self._has_trust_tables()
209
+ if trust_available:
210
+ sql = (
211
+ "SELECT f.fact_id, "
212
+ " COALESCE(al.access_count, 0) as access_count, "
213
+ " COALESCE(fi.pagerank_score, 0.0) as importance, "
214
+ " COALESCE(f.evidence_count, 0) as confirmation_count, "
215
+ " f.created_at, "
216
+ " COALESCE(r.last_accessed_at, f.created_at) as last_accessed_at, "
217
+ " COALESCE(f.emotional_valence, 0.0) as emotional_salience, "
218
+ " COALESCE(ts.trust_score, 1.0) as trust_score "
219
+ "FROM atomic_facts f "
220
+ "LEFT JOIN ("
221
+ " SELECT fact_id, COUNT(*) as access_count "
222
+ " FROM fact_access_log WHERE profile_id = ? GROUP BY fact_id"
223
+ ") al ON f.fact_id = al.fact_id "
224
+ "LEFT JOIN fact_importance fi "
225
+ " ON f.fact_id = fi.fact_id AND fi.profile_id = ? "
226
+ "LEFT JOIN fact_retention r "
227
+ " ON f.fact_id = r.fact_id AND r.profile_id = ? "
228
+ "LEFT JOIN trust_scores ts "
229
+ " ON ts.target_id = f.created_by "
230
+ " AND ts.target_type = 'agent' "
231
+ " AND ts.profile_id = ? "
232
+ "WHERE f.profile_id = ? "
233
+ "AND f.fact_id NOT IN ("
234
+ " SELECT json_each.value "
235
+ " FROM core_memory_blocks, json_each(core_memory_blocks.source_fact_ids) "
236
+ " WHERE core_memory_blocks.profile_id = ?"
237
+ ")"
238
+ )
239
+ params = (profile_id,) * 6
240
+ else:
241
+ sql = (
242
+ "SELECT f.fact_id, "
243
+ " COALESCE(al.access_count, 0) as access_count, "
244
+ " COALESCE(fi.pagerank_score, 0.0) as importance, "
245
+ " COALESCE(f.evidence_count, 0) as confirmation_count, "
246
+ " f.created_at, "
247
+ " COALESCE(r.last_accessed_at, f.created_at) as last_accessed_at, "
248
+ " COALESCE(f.emotional_valence, 0.0) as emotional_salience "
249
+ "FROM atomic_facts f "
250
+ "LEFT JOIN ("
251
+ " SELECT fact_id, COUNT(*) as access_count "
252
+ " FROM fact_access_log WHERE profile_id = ? GROUP BY fact_id"
253
+ ") al ON f.fact_id = al.fact_id "
254
+ "LEFT JOIN fact_importance fi "
255
+ " ON f.fact_id = fi.fact_id AND fi.profile_id = ? "
256
+ "LEFT JOIN fact_retention r "
257
+ " ON f.fact_id = r.fact_id AND r.profile_id = ? "
258
+ "WHERE f.profile_id = ? "
259
+ "AND f.fact_id NOT IN ("
260
+ " SELECT json_each.value "
261
+ " FROM core_memory_blocks, json_each(core_memory_blocks.source_fact_ids) "
262
+ " WHERE core_memory_blocks.profile_id = ?"
263
+ ")"
264
+ )
265
+ params = (profile_id,) * 5
266
+
267
+ rows = self._db.execute(sql, params)
230
268
 
231
269
  facts: list[dict] = []
232
270
  for row in rows:
@@ -238,6 +276,7 @@ class ForgettingScheduler:
238
276
  "confirmation_count": int(d["confirmation_count"]),
239
277
  "emotional_salience": float(d["emotional_salience"]),
240
278
  "last_accessed_at": str(d["last_accessed_at"]),
279
+ "trust_score": float(d.get("trust_score", 1.0)),
241
280
  })
242
281
  return facts
243
282
 
@@ -251,6 +290,19 @@ class ForgettingScheduler:
251
290
  retention_rows = self._db.batch_get_retention(fact_ids, profile_id)
252
291
  return {r["fact_id"]: r["lifecycle_zone"] for r in retention_rows}
253
292
 
293
+ def _has_trust_tables(self) -> bool:
294
+ """Check if trust_scores table and created_by column exist."""
295
+ try:
296
+ self._db.execute(
297
+ "SELECT 1 FROM trust_scores LIMIT 0", (),
298
+ )
299
+ self._db.execute(
300
+ "SELECT created_by FROM atomic_facts LIMIT 0", (),
301
+ )
302
+ return True
303
+ except Exception:
304
+ return False
305
+
254
306
  def _soft_delete_with_audit(self, fact_id: str, profile_id: str) -> None:
255
307
  """Soft-delete a forgotten fact with compliance audit trail.
256
308
 
@@ -78,6 +78,7 @@ class FactRetentionInput(TypedDict):
78
78
  confirmation_count: int # Mapped from atomic_facts.evidence_count
79
79
  emotional_salience: float # Mapped from atomic_facts.emotional_valence
80
80
  last_accessed_at: str # ISO 8601 datetime string
81
+ trust_score: float # Source trust in [0, 1]. Default 1.0.
81
82
 
82
83
 
83
84
  # ---------------------------------------------------------------------------
@@ -142,6 +143,47 @@ class EbbinghausCurve:
142
143
  # HR-02: Clamp to [0.0, 1.0]
143
144
  return max(0.0, min(1.0, r))
144
145
 
146
+ def trust_modulated_retention(
147
+ self,
148
+ hours_since_access: float,
149
+ strength: float,
150
+ trust_score: float = 1.0,
151
+ ) -> float:
152
+ """Compute trust-weighted Ebbinghaus retention.
153
+
154
+ lambda_eff = lambda * (1 + kappa * (1 - trust))
155
+
156
+ Low-trust memories decay faster. When trust=1.0, identical to
157
+ standard retention. When trust=0.0, decay rate is (1+kappa)x faster.
158
+
159
+ Paper 3, Section 5.5: Trust-Weighted Forgetting.
160
+
161
+ Args:
162
+ hours_since_access: Hours since last access.
163
+ strength: Memory strength S.
164
+ trust_score: Source trust in [0, 1]. Default 1.0 (fully trusted).
165
+
166
+ Returns:
167
+ Retention score in [0.0, 1.0].
168
+ """
169
+ if hours_since_access < 0:
170
+ return 1.0
171
+
172
+ s = max(self._config.min_strength, strength)
173
+ tau = max(0.0, min(1.0, trust_score))
174
+ kappa = self._config.trust_kappa
175
+
176
+ # Trust-modulated decay rate
177
+ lambda_base = 1.0 / s
178
+ lambda_eff = lambda_base * (1.0 + kappa * (1.0 - tau))
179
+
180
+ r = math.exp(-lambda_eff * hours_since_access)
181
+
182
+ if math.isnan(r) or math.isinf(r):
183
+ return 0.0
184
+
185
+ return max(0.0, min(1.0, r))
186
+
145
187
  def memory_strength(
146
188
  self,
147
189
  access_count: int,
@@ -294,7 +336,8 @@ class EbbinghausCurve:
294
336
  strength = self.memory_strength(
295
337
  access_count, importance, confirmation_count, emotional_salience,
296
338
  )
297
- ret = self.retention(hours_since, strength)
339
+ trust = fact.get("trust_score", 1.0)
340
+ ret = self.trust_modulated_retention(hours_since, strength, trust)
298
341
  zone = self.lifecycle_zone(ret)
299
342
 
300
343
  results.append({
@@ -145,14 +145,14 @@ class FRQADMetric:
145
145
  if bit_width >= 32:
146
146
  return np.array(base_variance, dtype=np.float64)
147
147
 
148
- # V3.3.12: Paper-correct ADDITIVE variance combination (was multiplicative).
149
- # sigma²_total = sigma²_obs + sigma²_quant
150
- # sigma²_quant = Delta²/12 where Delta = 2/2^b (uniform quantization step)
151
- delta = 2.0 / (2 ** bit_width) # Quantization step size
152
- sigma_q_sq = (delta ** 2) / 12.0 # Uniform quantization noise variance
153
- sigma_total = np.asarray(base_variance, dtype=np.float64) + sigma_q_sq
154
-
155
- return np.clip(sigma_total, self._config.variance_floor, self._config.variance_ceiling)
148
+ # V3.3.26: MULTIPLICATIVE variance inflation (Paper 3, Equation 2).
149
+ # sigma²_eff = sigma²_obs * (32 / bit_width) ^ kappa
150
+ # When bw=32: scale=1.0 (no change). When bw=4: scale=2.83x (kappa=0.5).
151
+ # This is MORE novel and MORE aggressive than additive Delta²/12.
152
+ scale = (32.0 / bit_width) ** self._config.kappa
153
+ sigma_inflated = np.asarray(base_variance, dtype=np.float64) * scale
154
+
155
+ return np.clip(sigma_inflated, self._config.variance_floor, self._config.variance_ceiling)
156
156
 
157
157
  # ------------------------------------------------------------------
158
158
  # Core distance (THE novel contribution)
@@ -92,6 +92,8 @@ class SemanticChannel:
92
92
  self._fisher_mode = fisher_mode if fisher_mode in ("simplified", "full") else "simplified"
93
93
  # Lazily instantiated full metric (avoids import cost when not needed)
94
94
  self._full_metric: object | None = None
95
+ # V3.3.26: Lazily instantiated FRQAD metric for mixed-precision scoring
96
+ self._frqad_metric: object | None = None
95
97
  self._vector_store = vector_store
96
98
  # V3.3.19: TurboQuant 3-tier search (stateless, optional)
97
99
  self._qas = quantization_aware_search
@@ -276,21 +278,68 @@ class SemanticChannel:
276
278
  q_mean: np.ndarray | None,
277
279
  q_var: np.ndarray | None,
278
280
  ) -> float:
279
- """Compute Fisher-Rao similarity using simplified or full metric.
281
+ """Compute Fisher-Rao similarity using simplified, full, or FRQAD metric.
280
282
 
281
283
  Simplified (default): Mahalanobis-like distance using only fact variance.
282
- Full: Atkinson-Mitchell geodesic via FisherRaoMetric.similarity(),
283
- requires both query and fact (mean, variance) pairs.
284
+ Full: Atkinson-Mitchell geodesic via FisherRaoMetric.similarity().
285
+ FRQAD: V3.3.26 quantization-aware distance via FRQADMetric when
286
+ the fact has a non-32-bit embedding (mixed precision).
284
287
 
285
- Falls back to simplified if full metric cannot be applied (e.g.
286
- missing fisher_mean on the fact, or missing query variance).
288
+ Falls back to simplified if full/FRQAD cannot be applied.
287
289
  """
290
+ # V3.3.26: FRQAD for mixed-precision facts
291
+ fact_bw = getattr(fact, "bit_width", 32) or 32
292
+ if fact_bw < 32 and q_mean is not None and q_var is not None:
293
+ return self._compute_frqad_sim(
294
+ q_mean, q_var, 32, f_vec, var_vec, fact_bw, fact,
295
+ )
296
+
288
297
  if self._fisher_mode == "full":
289
298
  return self._compute_full_fisher_sim(
290
299
  q_vec, f_vec, var_vec, fact, q_mean, q_var,
291
300
  )
292
301
  return _fisher_rao_similarity(q_vec, f_vec, var_vec, self._temperature)
293
302
 
303
+ def _compute_frqad_sim(
304
+ self,
305
+ q_mean: np.ndarray,
306
+ q_var: np.ndarray,
307
+ q_bw: int,
308
+ f_mean: np.ndarray,
309
+ f_var: np.ndarray,
310
+ f_bw: int,
311
+ fact: AtomicFact,
312
+ ) -> float:
313
+ """FRQAD: quantization-aware Fisher-Rao similarity (Paper 3, C1).
314
+
315
+ Uses variance inflation: sigma_eff = sigma * (32/bw)^kappa
316
+ to penalize lower-precision embeddings on the statistical manifold.
317
+ """
318
+ frqad = self._get_frqad_metric()
319
+ if frqad is None:
320
+ return _fisher_rao_similarity(q_mean, f_mean, f_var, self._temperature)
321
+ try:
322
+ return frqad.similarity(
323
+ q_mean, q_var, q_bw,
324
+ f_mean, f_var, f_bw,
325
+ )
326
+ except (ValueError, FloatingPointError):
327
+ logger.debug("FRQAD raised; falling back to simplified Fisher-Rao")
328
+ return _fisher_rao_similarity(q_mean, f_mean, f_var, self._temperature)
329
+
330
+ def _get_frqad_metric(self) -> object | None:
331
+ """Lazy-load FRQADMetric to avoid import-time cost."""
332
+ if self._frqad_metric is None:
333
+ try:
334
+ from superlocalmemory.math.fisher import FisherRaoMetric
335
+ from superlocalmemory.math.fisher_quantized import FRQADConfig, FRQADMetric
336
+ base = FisherRaoMetric(temperature=self._temperature)
337
+ self._frqad_metric = FRQADMetric(base, FRQADConfig())
338
+ except Exception:
339
+ logger.debug("FRQAD metric unavailable; mixed-precision scoring disabled")
340
+ return None
341
+ return self._frqad_metric
342
+
294
343
  def _compute_full_fisher_sim(
295
344
  self,
296
345
  q_vec: np.ndarray,
@@ -1873,7 +1873,7 @@
1873
1873
 
1874
1874
  <footer>
1875
1875
  <p>SuperLocalMemory V3 by <a href="https://github.com/varun369">Varun Pratap Bhardwaj</a></p>
1876
- <p><a href="https://github.com/varun369/SuperLocalMemoryV2">GitHub Repository</a> | MIT License</p>
1876
+ <p><a href="https://github.com/qualixar/superlocalmemory">GitHub Repository</a> | Elastic License 2.0</p>
1877
1877
  </footer>
1878
1878
  </body>
1879
1879
  </html>
package/ui/index.html CHANGED
@@ -1873,7 +1873,7 @@
1873
1873
 
1874
1874
  <footer>
1875
1875
  <p>SuperLocalMemory V3 by <a href="https://github.com/varun369">Varun Pratap Bhardwaj</a></p>
1876
- <p><a href="https://github.com/varun369/SuperLocalMemoryV2">GitHub Repository</a> | MIT License</p>
1876
+ <p><a href="https://github.com/qualixar/superlocalmemory">GitHub Repository</a> | Elastic License 2.0</p>
1877
1877
  </footer>
1878
1878
  </body>
1879
1879
  </html>