superlocalmemory 3.4.11 → 3.4.12
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.4.
|
|
3
|
+
"version": "3.4.12",
|
|
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
|
@@ -187,6 +187,37 @@ class RetrievalEngine:
|
|
|
187
187
|
except Exception as exc:
|
|
188
188
|
logger.warning("Scene expansion: %s", exc)
|
|
189
189
|
|
|
190
|
+
# V3.4.11: Entity graph signal enhancement (post-RRF boost)
|
|
191
|
+
# Instead of competing as independent channel, entity_graph SCORES
|
|
192
|
+
# the candidates from other channels by graph proximity to query entities.
|
|
193
|
+
# Research: Microsoft GraphRAG DRIFT, Pistis-RAG cascaded architecture.
|
|
194
|
+
if (self._entity is not None
|
|
195
|
+
and "entity_graph" not in set(self._config.disabled_channels)
|
|
196
|
+
and fused):
|
|
197
|
+
try:
|
|
198
|
+
candidate_ids = [fr.fact_id for fr in fused[:100]]
|
|
199
|
+
eg_scores = self._entity.score_candidates(
|
|
200
|
+
query, candidate_ids, profile_id,
|
|
201
|
+
)
|
|
202
|
+
if eg_scores:
|
|
203
|
+
boosted = []
|
|
204
|
+
for fr in fused:
|
|
205
|
+
eg_sc = eg_scores.get(fr.fact_id, 0.0)
|
|
206
|
+
if eg_sc > 0:
|
|
207
|
+
eg_weight = strat.weights.get("entity_graph", 1.0)
|
|
208
|
+
boost = 1.0 + eg_sc * eg_weight * 0.3
|
|
209
|
+
boosted.append(FusionResult(
|
|
210
|
+
fact_id=fr.fact_id,
|
|
211
|
+
fused_score=fr.fused_score * boost,
|
|
212
|
+
channel_ranks=fr.channel_ranks,
|
|
213
|
+
channel_scores={**fr.channel_scores, "entity_graph": eg_sc},
|
|
214
|
+
))
|
|
215
|
+
else:
|
|
216
|
+
boosted.append(fr)
|
|
217
|
+
fused = sorted(boosted, key=lambda r: r.fused_score, reverse=True)
|
|
218
|
+
except Exception as exc:
|
|
219
|
+
logger.warning("Entity graph signal enhancement: %s", exc)
|
|
220
|
+
|
|
190
221
|
# 4. Load facts for rerank pool
|
|
191
222
|
pool = min(len(fused), max(effective_limit * 3, 30))
|
|
192
223
|
top = fused[:pool]
|
|
@@ -448,13 +479,9 @@ class RetrievalEngine:
|
|
|
448
479
|
except Exception as exc:
|
|
449
480
|
logger.warning("BM25 channel: %s", exc)
|
|
450
481
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if r:
|
|
455
|
-
out["entity_graph"] = r
|
|
456
|
-
except Exception as exc:
|
|
457
|
-
logger.warning("Entity channel: %s", exc)
|
|
482
|
+
# V3.4.12: entity_graph is now a signal enhancer (post-RRF boost),
|
|
483
|
+
# not an independent channel. Removed from channel execution to avoid
|
|
484
|
+
# running spreading activation twice. See score_candidates() in engine.recall().
|
|
458
485
|
|
|
459
486
|
if self._temporal is not None and "temporal" not in disabled:
|
|
460
487
|
try:
|
|
@@ -370,6 +370,124 @@ class EntityGraphChannel:
|
|
|
370
370
|
results.sort(key=lambda x: x[1], reverse=True)
|
|
371
371
|
return results[:top_k]
|
|
372
372
|
|
|
373
|
+
def score_candidates(
|
|
374
|
+
self,
|
|
375
|
+
query: str,
|
|
376
|
+
candidate_fact_ids: list[str],
|
|
377
|
+
profile_id: str,
|
|
378
|
+
) -> dict[str, float]:
|
|
379
|
+
"""Score candidate facts by their entity-graph proximity to query entities.
|
|
380
|
+
|
|
381
|
+
V3.4.11 "Signal Enhancer" architecture: instead of returning its own
|
|
382
|
+
independent set of fact_ids (which get outranked by multi-channel facts
|
|
383
|
+
in RRF), this method scores EXISTING candidates from semantic/BM25
|
|
384
|
+
by their graph connectivity to query entities.
|
|
385
|
+
|
|
386
|
+
Research basis: Microsoft GraphRAG DRIFT Search, HippoRAG, Pistis-RAG
|
|
387
|
+
cascaded architecture. Graph signals act as post-retrieval boosters,
|
|
388
|
+
not independent retrievers. Avoids the "weakest link" phenomenon where
|
|
389
|
+
non-overlapping result sets cause rank collapse in RRF fusion.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
query: The user's query string.
|
|
393
|
+
candidate_fact_ids: Fact IDs from semantic/BM25/other channels.
|
|
394
|
+
profile_id: User profile.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Dict mapping fact_id → entity_graph score [0, 1].
|
|
398
|
+
Facts with no entity connection return 0.
|
|
399
|
+
Facts directly linked to query entities score ~1.0.
|
|
400
|
+
Facts 1-hop away score ~0.7 (decay factor).
|
|
401
|
+
"""
|
|
402
|
+
if not candidate_fact_ids:
|
|
403
|
+
return {}
|
|
404
|
+
|
|
405
|
+
raw_entities = extract_query_entities(query)
|
|
406
|
+
if not raw_entities:
|
|
407
|
+
return {}
|
|
408
|
+
|
|
409
|
+
canonical_ids = self._resolve_entities(raw_entities, profile_id)
|
|
410
|
+
if not canonical_ids:
|
|
411
|
+
return {}
|
|
412
|
+
|
|
413
|
+
self._ensure_adjacency(profile_id)
|
|
414
|
+
|
|
415
|
+
# Run full spreading activation (same as search())
|
|
416
|
+
activation: dict[str, float] = defaultdict(float)
|
|
417
|
+
visited_entities: set[str] = set(canonical_ids)
|
|
418
|
+
use_cache = bool(self._entity_to_facts)
|
|
419
|
+
|
|
420
|
+
for eid in canonical_ids:
|
|
421
|
+
if use_cache:
|
|
422
|
+
for fid in self._entity_to_facts.get(eid, ()):
|
|
423
|
+
activation[fid] = max(activation[fid], 1.0)
|
|
424
|
+
else:
|
|
425
|
+
for fact in self._db.get_facts_by_entity(eid, profile_id):
|
|
426
|
+
activation[fact.fact_id] = max(activation[fact.fact_id], 1.0)
|
|
427
|
+
|
|
428
|
+
frontier = set(activation.keys())
|
|
429
|
+
for hop in range(1, self._max_hops):
|
|
430
|
+
hop_decay = self._decay ** hop
|
|
431
|
+
if hop_decay < self._threshold:
|
|
432
|
+
break
|
|
433
|
+
next_frontier: set[str] = set()
|
|
434
|
+
for fid in frontier:
|
|
435
|
+
if use_cache:
|
|
436
|
+
for neighbor, edge_weight in self._adj.get(fid, ()):
|
|
437
|
+
if self._graph_metrics:
|
|
438
|
+
weighted = activation[fid] * self._decay * edge_weight
|
|
439
|
+
if neighbor in self._graph_metrics:
|
|
440
|
+
pr = self._graph_metrics[neighbor].get("pagerank_score", 0.0)
|
|
441
|
+
weighted *= min(1.0 + pr * 2.0, 2.0)
|
|
442
|
+
else:
|
|
443
|
+
weighted = activation[fid] * self._decay
|
|
444
|
+
if weighted >= self._threshold and weighted > activation.get(neighbor, 0.0):
|
|
445
|
+
activation[neighbor] = weighted
|
|
446
|
+
next_frontier.add(neighbor)
|
|
447
|
+
|
|
448
|
+
if use_cache:
|
|
449
|
+
for fid in frontier:
|
|
450
|
+
for eid in self._fact_to_entities.get(fid, ()):
|
|
451
|
+
if eid not in visited_entities:
|
|
452
|
+
visited_entities.add(eid)
|
|
453
|
+
for linked_fid in self._entity_to_facts.get(eid, ()):
|
|
454
|
+
if hop_decay > activation.get(linked_fid, 0.0):
|
|
455
|
+
activation[linked_fid] = hop_decay
|
|
456
|
+
next_frontier.add(linked_fid)
|
|
457
|
+
|
|
458
|
+
frontier = next_frontier
|
|
459
|
+
if not frontier:
|
|
460
|
+
break
|
|
461
|
+
|
|
462
|
+
# Community-aware boosting (same as search)
|
|
463
|
+
if self._graph_metrics and use_cache:
|
|
464
|
+
from collections import Counter as _Counter
|
|
465
|
+
seed_communities: _Counter = _Counter()
|
|
466
|
+
for eid in canonical_ids:
|
|
467
|
+
for fid in self._entity_to_facts.get(eid, ()):
|
|
468
|
+
m = self._graph_metrics.get(fid, {})
|
|
469
|
+
comm = m.get("community_id")
|
|
470
|
+
if comm is not None:
|
|
471
|
+
seed_communities[comm] += 1
|
|
472
|
+
if seed_communities:
|
|
473
|
+
total_seeds = sum(seed_communities.values())
|
|
474
|
+
for fid in list(activation.keys()):
|
|
475
|
+
m = self._graph_metrics.get(fid, {})
|
|
476
|
+
fact_comm = m.get("community_id")
|
|
477
|
+
if fact_comm is not None and fact_comm in seed_communities:
|
|
478
|
+
boost = min(1.0 + 0.15 * (seed_communities[fact_comm] / total_seeds), 1.3)
|
|
479
|
+
activation[fid] *= boost
|
|
480
|
+
|
|
481
|
+
# Extract scores ONLY for the candidate set, normalize to [0, 1]
|
|
482
|
+
candidate_set = set(candidate_fact_ids)
|
|
483
|
+
scored = {fid: activation.get(fid, 0.0) for fid in candidate_set}
|
|
484
|
+
|
|
485
|
+
max_score = max(scored.values()) if scored else 0
|
|
486
|
+
if max_score > 0:
|
|
487
|
+
scored = {fid: sc / max_score for fid, sc in scored.items()}
|
|
488
|
+
|
|
489
|
+
return scored
|
|
490
|
+
|
|
373
491
|
def _suppress_contradictions(
|
|
374
492
|
self, activation: dict[str, float], profile_id: str,
|
|
375
493
|
) -> None:
|
|
@@ -587,6 +587,9 @@ def _register_daemon_routes(application: FastAPI) -> None:
|
|
|
587
587
|
"score": round(r.score, 4),
|
|
588
588
|
"fact_type": getattr(r.fact.fact_type, 'value', str(r.fact.fact_type)),
|
|
589
589
|
"fact_id": r.fact.fact_id,
|
|
590
|
+
"channel_scores": {
|
|
591
|
+
k: round(v, 4) for k, v in r.channel_scores.items()
|
|
592
|
+
} if r.channel_scores else {},
|
|
590
593
|
}
|
|
591
594
|
for r in response.results
|
|
592
595
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: superlocalmemory
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.12
|
|
4
4
|
Summary: Information-geometric agent memory with mathematical guarantees
|
|
5
5
|
Author-email: Varun Pratap Bhardwaj <admin@superlocalmemory.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -83,7 +83,7 @@ Dynamic: license-file
|
|
|
83
83
|
|
|
84
84
|
<h1 align="center">SuperLocalMemory V3.4</h1>
|
|
85
85
|
<p align="center"><strong>Every other AI forgets. Yours won't.</strong><br/><em>Infinite memory for Claude Code, Cursor, Windsurf & 17+ AI tools.</em></p>
|
|
86
|
-
<p align="center"><code>v3.4.
|
|
86
|
+
<p align="center"><code>v3.4.11</code> — Install once. Every session remembers the last. Automatically.</p>
|
|
87
87
|
<p align="center"><strong>Backed by 3 peer-reviewed research papers</strong> · <a href="https://arxiv.org/abs/2603.02240">arXiv:2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">arXiv:2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">arXiv:2604.04514</a></p>
|
|
88
88
|
|
|
89
89
|
<p align="center">
|
|
@@ -457,15 +457,20 @@ Auto-capture hooks: `slm hooks install` + `slm observe` + `slm session-context`.
|
|
|
457
457
|
- Auto-learned soft prompts injected into agent context
|
|
458
458
|
- Behavioral pattern detection and outcome tracking
|
|
459
459
|
|
|
460
|
-
### Skill Evolution
|
|
461
|
-
- **Per-skill performance tracking** —
|
|
462
|
-
- **
|
|
463
|
-
- **
|
|
464
|
-
- **
|
|
465
|
-
- **
|
|
466
|
-
- **
|
|
467
|
-
- **ECC
|
|
468
|
-
|
|
460
|
+
### Skill Evolution
|
|
461
|
+
- **Per-skill performance tracking** — tracks which skills succeed and fail across sessions (zero-LLM, always on)
|
|
462
|
+
- **Evolution engine** — 3-trigger system with blind verification. Off by default — enable via `slm config set evolution.enabled true`
|
|
463
|
+
- **MCP tools** — `evolve_skill`, `skill_health`, `skill_lineage` for programmatic access
|
|
464
|
+
- **Lineage DAG** — visual evolution history in the dashboard
|
|
465
|
+
- **CLI config** — `slm config get/set` for all evolution settings
|
|
466
|
+
- **Post-session triggers** — automatic analysis on session end via Stop hook
|
|
467
|
+
- **[ECC](https://github.com/affaan-m/everything-claude-code) integration** — optional enhanced observations via `slm ingest --source ecc`
|
|
468
|
+
|
|
469
|
+
### Tiered Storage & Scaling
|
|
470
|
+
- **4-tier lifecycle** — active, warm, cold, archived with automatic promotion/demotion
|
|
471
|
+
- **Deep recall** — archived facts searchable at reduced weight
|
|
472
|
+
- **Graph pruning** — automatic cleanup of orphan edges, self-loops, duplicates
|
|
473
|
+
- **Fact consolidation** — clusters related facts into consolidated summaries
|
|
469
474
|
|
|
470
475
|
### Trust & Security
|
|
471
476
|
- Bayesian Beta-distribution trust scoring (per-agent, per-fact)
|
|
@@ -476,9 +481,9 @@ Auto-capture hooks: `slm hooks install` + `slm observe` + `slm session-context`.
|
|
|
476
481
|
### Infrastructure
|
|
477
482
|
- 23-tab web dashboard with real-time visualization
|
|
478
483
|
- 17+ IDE integrations (Claude, Cursor, Windsurf, VS Code, JetBrains, Zed, etc.)
|
|
479
|
-
-
|
|
484
|
+
- 38 MCP tools + 7 MCP resources
|
|
480
485
|
- Profile isolation (independent memory spaces)
|
|
481
|
-
-
|
|
486
|
+
- 2,900+ tests, AGPL v3, cross-platform (Mac/Linux/Windows)
|
|
482
487
|
- CPU-only — no GPU required
|
|
483
488
|
- Automatic orphaned process cleanup
|
|
484
489
|
|