superlocalmemory 3.4.64 → 3.5.1

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 (31) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/package.json +1 -1
  3. package/pyproject.toml +5 -2
  4. package/src/superlocalmemory/cli/commands.py +80 -33
  5. package/src/superlocalmemory/cli/daemon.py +3 -1
  6. package/src/superlocalmemory/cli/main.py +1 -1
  7. package/src/superlocalmemory/core/backend_orchestrator.py +19 -13
  8. package/src/superlocalmemory/core/config.py +83 -0
  9. package/src/superlocalmemory/core/injection.py +351 -0
  10. package/src/superlocalmemory/core/recall_pipeline.py +29 -0
  11. package/src/superlocalmemory/core/store_pipeline.py +13 -0
  12. package/src/superlocalmemory/hooks/auto_recall_hook.py +50 -26
  13. package/src/superlocalmemory/hooks/before_web_hook.py +3 -2
  14. package/src/superlocalmemory/hooks/user_prompt_hook.py +5 -2
  15. package/src/superlocalmemory/mcp/tools_active.py +130 -9
  16. package/src/superlocalmemory/mcp/tools_context.py +18 -4
  17. package/src/superlocalmemory/retrieval/bm25_channel.py +50 -0
  18. package/src/superlocalmemory/retrieval/engine.py +43 -9
  19. package/src/superlocalmemory/retrieval/hopfield_channel.py +22 -9
  20. package/src/superlocalmemory/retrieval/temporal_channel.py +10 -1
  21. package/src/superlocalmemory/server/routes/memories.py +2 -2
  22. package/src/superlocalmemory/server/routes/v3_api.py +1 -1
  23. package/src/superlocalmemory/server/unified_daemon.py +83 -3
  24. package/src/superlocalmemory/storage/database.py +47 -0
  25. package/src/superlocalmemory/storage/migration_runner.py +4 -0
  26. package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +40 -0
  27. package/src/superlocalmemory/storage/migrations/__init__.py +1 -0
  28. package/src/superlocalmemory/storage/models.py +3 -0
  29. package/src/superlocalmemory.egg-info/PKG-INFO +4 -2
  30. package/src/superlocalmemory.egg-info/SOURCES.txt +3 -0
  31. package/src/superlocalmemory.egg-info/requires.txt +4 -1
@@ -239,9 +239,26 @@ class DatabaseManager:
239
239
  emotional_valence=d.get("emotional_valence", 0.0),
240
240
  emotional_arousal=d.get("emotional_arousal", 0.0),
241
241
  signal_type=SignalType(d["signal_type"]) if d.get("signal_type") else SignalType.FACTUAL,
242
+ pinned=bool(d.get("pinned", 0)),
242
243
  created_at=d["created_at"],
243
244
  )
244
245
 
246
+ def set_pinned(self, fact_id: str, pinned: bool) -> None:
247
+ """Set or clear the pinned flag on a fact (v3.4.65 core-memory)."""
248
+ self.execute(
249
+ "UPDATE atomic_facts SET pinned = ? WHERE fact_id = ?",
250
+ (1 if pinned else 0, fact_id),
251
+ )
252
+
253
+ def get_pinned(self, profile_id: str) -> list[AtomicFact]:
254
+ """Return all pinned facts for a profile, highest-importance first."""
255
+ rows = self.execute(
256
+ "SELECT * FROM atomic_facts WHERE profile_id = ? AND pinned = 1 "
257
+ "ORDER BY importance DESC",
258
+ (profile_id,),
259
+ )
260
+ return [self._row_to_fact(r) for r in rows]
261
+
245
262
  def get_all_facts(self, profile_id: str) -> list[AtomicFact]:
246
263
  """All facts for a profile, newest first."""
247
264
  rows = self.execute(
@@ -606,6 +623,36 @@ class DatabaseManager:
606
623
  )
607
624
  return [self._row_to_scene(r) for r in rows]
608
625
 
626
+ def get_scenes_for_facts_batch(
627
+ self, fact_ids: list[str], profile_id: str,
628
+ ) -> dict[str, list[MemoryScene]]:
629
+ """v3.5.0: batch scene lookup — one query replaces N individual LIKE scans.
630
+
631
+ The 20 individual ``get_scenes_for_fact`` calls in the retrieval engine's
632
+ scene expansion path were the single largest recall latency source (~5.7s).
633
+ This replaces them with a single multi-LIKE OR query. Returns
634
+ ``{fact_id: [scenes]}`` for facts that belong to at least one scene.
635
+ """
636
+ if not fact_ids:
637
+ return {}
638
+ clauses = " OR ".join(
639
+ '(profile_id = ? AND fact_ids_json LIKE ?)' for _ in fact_ids
640
+ )
641
+ params: list[str] = []
642
+ for fid in fact_ids:
643
+ params.extend((profile_id, f'%"{fid}"%'))
644
+ rows = self.execute(
645
+ f"SELECT * FROM memory_scenes WHERE {clauses} ORDER BY last_updated DESC",
646
+ tuple(params),
647
+ )
648
+ out: dict[str, list[MemoryScene]] = {}
649
+ for r in rows:
650
+ scene = self._row_to_scene(r)
651
+ for fid in fact_ids:
652
+ if fid in (scene.fact_ids or []):
653
+ out.setdefault(fid, []).append(scene)
654
+ return out
655
+
609
656
  def increment_entity_fact_count(self, entity_id: str) -> None:
610
657
  """Atomically increment fact_count for a canonical entity."""
611
658
  self.execute(
@@ -50,6 +50,7 @@ from superlocalmemory.storage.migrations import (
50
50
  M012_shadow_observations as _M012,
51
51
  M013_bi_temporal_columns as _M013,
52
52
  M014_v345_scale_ready as _M014,
53
+ M015_add_pinned_column as _M015,
53
54
  )
54
55
 
55
56
  # Map migration name → module (used for the optional ``verify(conn)`` hook
@@ -69,6 +70,7 @@ _MODULES = {
69
70
  _M012.NAME: _M012,
70
71
  _M013.NAME: _M013,
71
72
  _M014.NAME: _M014,
73
+ _M015.NAME: _M015,
72
74
  }
73
75
 
74
76
  logger = logging.getLogger(__name__)
@@ -130,6 +132,8 @@ DEFERRED_MIGRATIONS: list[Migration] = [
130
132
  # as M011.
131
133
  Migration(name=_M013.NAME, db_target="memory", ddl=_M013.DDL),
132
134
  Migration(name=_M014.NAME, db_target="memory", ddl=_M014.DDL),
135
+ # M015 adds pinned column to atomic_facts (v3.4.65 core-memory pins).
136
+ Migration(name=_M015.NAME, db_target="memory", ddl=_M015.DDL),
133
137
  ]
134
138
 
135
139
 
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4.65 — Context Injection v2
4
+
5
+ """M015 — add `pinned` column to atomic_facts (v3.4.65, core-memory explicit pins).
6
+
7
+ Additive only — ALTER TABLE ADD COLUMN, default 0. No data loss, no type
8
+ changes. Idempotent via verify() + migration_log. Mirrors M001 pattern.
9
+
10
+ Backward-compat: old code that doesn't know about the column simply gets
11
+ DEFAULT 0 on SELECT *, which is the safe "not-pinned" state.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sqlite3
17
+
18
+ NAME = "M015_add_pinned_column"
19
+ DB_TARGET = "memory"
20
+
21
+ _REQUIRED_COLS = frozenset({"pinned"})
22
+
23
+
24
+ def verify(conn: sqlite3.Connection) -> bool:
25
+ try:
26
+ cols = {r[1] for r in conn.execute(
27
+ "PRAGMA table_info(atomic_facts)"
28
+ ).fetchall()}
29
+ except sqlite3.Error:
30
+ return False
31
+ return _REQUIRED_COLS <= cols
32
+
33
+
34
+ DDL = """
35
+ BEGIN IMMEDIATE;
36
+ ALTER TABLE atomic_facts ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;
37
+ CREATE INDEX IF NOT EXISTS idx_facts_pinned
38
+ ON atomic_facts(profile_id, pinned);
39
+ COMMIT;
40
+ """
@@ -23,6 +23,7 @@ from . import (
23
23
  M003_migration_log,
24
24
  M004_cross_platform_sync_log,
25
25
  M005_bandit_tables,
26
+ M015_add_pinned_column,
26
27
  )
27
28
 
28
29
  # ---------------------------------------------------------------------------
@@ -178,6 +178,9 @@ class AtomicFact:
178
178
  # Signal type (V2 compatible)
179
179
  signal_type: SignalType = SignalType.FACTUAL
180
180
 
181
+ # v3.4.65: Core Memory Block explicit pin (M015)
182
+ pinned: bool = False
183
+
181
184
  created_at: str = field(default_factory=_now)
182
185
 
183
186
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superlocalmemory
3
- Version: 3.4.64
3
+ Version: 3.5.1
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
@@ -69,6 +69,8 @@ Provides-Extra: ui
69
69
  Requires-Dist: fastapi[all]>=0.135.1; extra == "ui"
70
70
  Requires-Dist: uvicorn>=0.42.0; extra == "ui"
71
71
  Requires-Dist: python-multipart<1.0.0,>=0.0.6; extra == "ui"
72
+ Provides-Extra: injection
73
+ Requires-Dist: tiktoken>=0.8.0; extra == "injection"
72
74
  Provides-Extra: learning
73
75
  Requires-Dist: lightgbm>=4.0.0; extra == "learning"
74
76
  Provides-Extra: performance
@@ -79,7 +81,7 @@ Requires-Dist: google-auth-oauthlib>=1.2.0; extra == "ingestion"
79
81
  Requires-Dist: google-api-python-client>=2.100.0; extra == "ingestion"
80
82
  Requires-Dist: icalendar>=6.0.0; extra == "ingestion"
81
83
  Provides-Extra: full
82
- Requires-Dist: superlocalmemory[ingestion,learning,performance,search,ui]; extra == "full"
84
+ Requires-Dist: superlocalmemory[ingestion,injection,learning,performance,search,ui]; extra == "full"
83
85
  Provides-Extra: dev
84
86
  Requires-Dist: pytest>=8.0; extra == "dev"
85
87
  Requires-Dist: pytest-cov>=4.1; extra == "dev"
@@ -86,6 +86,7 @@ src/superlocalmemory/core/graph_analyzer.py
86
86
  src/superlocalmemory/core/graph_pruner.py
87
87
  src/superlocalmemory/core/health_monitor.py
88
88
  src/superlocalmemory/core/hooks.py
89
+ src/superlocalmemory/core/injection.py
89
90
  src/superlocalmemory/core/loop_watchdog.py
90
91
  src/superlocalmemory/core/maintenance.py
91
92
  src/superlocalmemory/core/maintenance_scheduler.py
@@ -377,6 +378,7 @@ src/superlocalmemory/storage/migrations/M011_archive_and_merge.py
377
378
  src/superlocalmemory/storage/migrations/M012_shadow_observations.py
378
379
  src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py
379
380
  src/superlocalmemory/storage/migrations/M014_v345_scale_ready.py
381
+ src/superlocalmemory/storage/migrations/M015_add_pinned_column.py
380
382
  src/superlocalmemory/storage/migrations/__init__.py
381
383
  src/superlocalmemory/trust/__init__.py
382
384
  src/superlocalmemory/trust/gate.py
@@ -452,6 +454,7 @@ tests/test_final_locomo_mini.py
452
454
  tests/test_hook_handlers.py
453
455
  tests/test_ide_connector.py
454
456
  tests/test_infra.py
457
+ tests/test_injection.py
455
458
  tests/test_learning_advanced.py
456
459
  tests/test_learning_collectors.py
457
460
  tests/test_llm_provider.py
@@ -36,7 +36,7 @@ pytest-asyncio>=0.21
36
36
  sqlite-vec>=0.1.6
37
37
 
38
38
  [full]
39
- superlocalmemory[ingestion,learning,performance,search,ui]
39
+ superlocalmemory[ingestion,injection,learning,performance,search,ui]
40
40
 
41
41
  [ingestion]
42
42
  keyring>=25.0.0
@@ -44,6 +44,9 @@ google-auth-oauthlib>=1.2.0
44
44
  google-api-python-client>=2.100.0
45
45
  icalendar>=6.0.0
46
46
 
47
+ [injection]
48
+ tiktoken>=0.8.0
49
+
47
50
  [learning]
48
51
  lightgbm>=4.0.0
49
52