superlocalmemory 3.2.1 → 3.2.2

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 (30) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +61 -1
  3. package/package.json +1 -1
  4. package/pyproject.toml +26 -1
  5. package/src/superlocalmemory/attribution/signer.py +6 -1
  6. package/src/superlocalmemory/core/config.py +114 -1
  7. package/src/superlocalmemory/core/consolidation_engine.py +595 -0
  8. package/src/superlocalmemory/core/embeddings.py +0 -1
  9. package/src/superlocalmemory/core/engine.py +164 -674
  10. package/src/superlocalmemory/core/engine_wiring.py +474 -0
  11. package/src/superlocalmemory/core/graph_analyzer.py +199 -0
  12. package/src/superlocalmemory/core/recall_pipeline.py +247 -0
  13. package/src/superlocalmemory/core/store_pipeline.py +483 -0
  14. package/src/superlocalmemory/core/worker_pool.py +35 -12
  15. package/src/superlocalmemory/encoding/auto_linker.py +308 -0
  16. package/src/superlocalmemory/encoding/context_generator.py +175 -0
  17. package/src/superlocalmemory/encoding/temporal_validator.py +513 -0
  18. package/src/superlocalmemory/hooks/auto_invoker.py +484 -0
  19. package/src/superlocalmemory/retrieval/channel_registry.py +154 -0
  20. package/src/superlocalmemory/retrieval/engine.py +12 -0
  21. package/src/superlocalmemory/retrieval/semantic_channel.py +87 -3
  22. package/src/superlocalmemory/retrieval/spreading_activation.py +311 -0
  23. package/src/superlocalmemory/retrieval/strategy.py +6 -6
  24. package/src/superlocalmemory/retrieval/vector_store.py +386 -0
  25. package/src/superlocalmemory/server/routes/v3_api.py +576 -0
  26. package/src/superlocalmemory/storage/access_log.py +169 -0
  27. package/src/superlocalmemory/storage/database.py +288 -0
  28. package/src/superlocalmemory/storage/schema.py +10 -0
  29. package/src/superlocalmemory/storage/schema_v32.py +252 -0
  30. package/src/superlocalmemory/storage/v2_migrator.py +24 -2
@@ -0,0 +1,252 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3
4
+
5
+ """SuperLocalMemory V3.2 -- Schema Extensions (Associative Memory).
6
+
7
+ Phase 0.5 creates this file with EMPTY V32_DDL.
8
+ Each implementing phase OWNS its DDL and appends here:
9
+ - Phase 1 adds: fact_access_log, fact_embeddings (vec0), embedding_metadata
10
+ - Phase 2 adds: fact_context
11
+ - Phase 3 adds: association_edges, activation_cache, fact_importance
12
+ - Phase 4 adds: fact_temporal_validity
13
+ - Phase 5 adds: core_memory_blocks
14
+
15
+ Design rules:
16
+ - profile_id + FK CASCADE on every table (Rule 01)
17
+ - CREATE IF NOT EXISTS for idempotency (Rule 02)
18
+ - Rollback SQL in V32_ROLLBACK (Rule 20)
19
+ - Never ALTER existing tables (Rule 02)
20
+
21
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Final
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Table names (for drop_all parity -- Rule 20)
30
+ # All 9 names listed upfront. DDL added by each phase.
31
+ # ---------------------------------------------------------------------------
32
+
33
+ V32_TABLES: Final[tuple[str, ...]] = (
34
+ "fact_access_log",
35
+ "fact_embeddings",
36
+ "embedding_metadata",
37
+ "fact_context",
38
+ "association_edges",
39
+ "activation_cache",
40
+ "fact_importance",
41
+ "fact_temporal_validity",
42
+ "core_memory_blocks",
43
+ )
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # DDL Statements -- EMPTY at Phase 0.5. Each phase appends its DDL.
47
+ # Phase LLDs are AUTHORITATIVE for column names and constraints.
48
+ # ---------------------------------------------------------------------------
49
+
50
+ V32_DDL: list[str] = [
51
+ # --- Phase 1: Vector Foundation ---
52
+ """
53
+ CREATE TABLE IF NOT EXISTS fact_access_log (
54
+ log_id TEXT PRIMARY KEY,
55
+ fact_id TEXT NOT NULL,
56
+ profile_id TEXT NOT NULL DEFAULT 'default',
57
+ accessed_at TEXT NOT NULL DEFAULT (datetime('now')),
58
+ access_type TEXT NOT NULL DEFAULT 'recall'
59
+ CHECK (access_type IN ('recall', 'auto_invoke', 'search', 'consolidation')),
60
+ session_id TEXT NOT NULL DEFAULT '',
61
+
62
+ FOREIGN KEY (fact_id) REFERENCES atomic_facts (fact_id)
63
+ ON DELETE CASCADE,
64
+ FOREIGN KEY (profile_id) REFERENCES profiles (profile_id)
65
+ ON DELETE CASCADE
66
+ );
67
+ CREATE INDEX IF NOT EXISTS idx_access_log_fact
68
+ ON fact_access_log (fact_id, accessed_at DESC);
69
+ CREATE INDEX IF NOT EXISTS idx_access_log_profile
70
+ ON fact_access_log (profile_id, accessed_at DESC);
71
+ CREATE INDEX IF NOT EXISTS idx_access_log_profile_fact
72
+ ON fact_access_log (profile_id, fact_id);
73
+ """,
74
+ """
75
+ CREATE TABLE IF NOT EXISTS embedding_metadata (
76
+ vec_rowid INTEGER PRIMARY KEY,
77
+ fact_id TEXT NOT NULL UNIQUE,
78
+ profile_id TEXT NOT NULL DEFAULT 'default',
79
+ model_name TEXT NOT NULL DEFAULT '',
80
+ dimension INTEGER NOT NULL DEFAULT 768,
81
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
82
+
83
+ FOREIGN KEY (fact_id) REFERENCES atomic_facts (fact_id)
84
+ ON DELETE CASCADE,
85
+ FOREIGN KEY (profile_id) REFERENCES profiles (profile_id)
86
+ ON DELETE CASCADE
87
+ );
88
+ CREATE INDEX IF NOT EXISTS idx_embmeta_fact
89
+ ON embedding_metadata (fact_id);
90
+ CREATE INDEX IF NOT EXISTS idx_embmeta_profile
91
+ ON embedding_metadata (profile_id);
92
+ """,
93
+ # --- Phase 2: Auto-Invoke Engine ---
94
+ """
95
+ CREATE TABLE IF NOT EXISTS fact_context (
96
+ fact_id TEXT PRIMARY KEY,
97
+ profile_id TEXT NOT NULL,
98
+ contextual_description TEXT NOT NULL,
99
+ keywords TEXT,
100
+ generated_by TEXT NOT NULL DEFAULT 'rules',
101
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
102
+
103
+ FOREIGN KEY (fact_id) REFERENCES atomic_facts (fact_id)
104
+ ON DELETE CASCADE,
105
+ FOREIGN KEY (profile_id) REFERENCES profiles (profile_id)
106
+ ON DELETE CASCADE
107
+ );
108
+ CREATE INDEX IF NOT EXISTS idx_fact_context_profile
109
+ ON fact_context (profile_id);
110
+ """,
111
+ # Phase 4 will add: fact_temporal_validity
112
+ # Phase 5 will add: core_memory_blocks
113
+
114
+ # --- Phase 3: Association Graph ---
115
+ """
116
+ CREATE TABLE IF NOT EXISTS association_edges (
117
+ edge_id TEXT PRIMARY KEY,
118
+ profile_id TEXT NOT NULL,
119
+ source_fact_id TEXT NOT NULL,
120
+ target_fact_id TEXT NOT NULL,
121
+ association_type TEXT NOT NULL CHECK(association_type IN (
122
+ 'auto_link', 'hebbian', 'consolidation', 'user_defined'
123
+ )),
124
+ weight REAL NOT NULL DEFAULT 0.5,
125
+ co_access_count INTEGER NOT NULL DEFAULT 0,
126
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
127
+ last_strengthened TEXT,
128
+ FOREIGN KEY (source_fact_id) REFERENCES atomic_facts(fact_id) ON DELETE CASCADE,
129
+ FOREIGN KEY (target_fact_id) REFERENCES atomic_facts(fact_id) ON DELETE CASCADE,
130
+ FOREIGN KEY (profile_id) REFERENCES profiles(profile_id) ON DELETE CASCADE
131
+ );
132
+ CREATE INDEX IF NOT EXISTS idx_assoc_source
133
+ ON association_edges(source_fact_id, profile_id);
134
+ CREATE INDEX IF NOT EXISTS idx_assoc_target
135
+ ON association_edges(target_fact_id, profile_id);
136
+ CREATE INDEX IF NOT EXISTS idx_assoc_profile
137
+ ON association_edges(profile_id);
138
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_assoc_unique_pair
139
+ ON association_edges(profile_id, source_fact_id, target_fact_id, association_type);
140
+ """,
141
+ """
142
+ CREATE TABLE IF NOT EXISTS activation_cache (
143
+ cache_id TEXT PRIMARY KEY,
144
+ profile_id TEXT NOT NULL,
145
+ query_hash TEXT NOT NULL,
146
+ node_id TEXT NOT NULL,
147
+ activation_value REAL NOT NULL,
148
+ iteration INTEGER NOT NULL,
149
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
150
+ expires_at TEXT NOT NULL DEFAULT (datetime('now', '+1 hour')),
151
+ FOREIGN KEY (profile_id) REFERENCES profiles(profile_id) ON DELETE CASCADE
152
+ );
153
+ CREATE INDEX IF NOT EXISTS idx_actcache_profile_query
154
+ ON activation_cache(profile_id, query_hash);
155
+ CREATE INDEX IF NOT EXISTS idx_actcache_expires
156
+ ON activation_cache(expires_at);
157
+ """,
158
+ """
159
+ CREATE TABLE IF NOT EXISTS fact_importance (
160
+ fact_id TEXT PRIMARY KEY,
161
+ profile_id TEXT NOT NULL,
162
+ pagerank_score REAL NOT NULL DEFAULT 0.0,
163
+ community_id INTEGER,
164
+ degree_centrality REAL DEFAULT 0.0,
165
+ computed_at TEXT NOT NULL DEFAULT (datetime('now')),
166
+ FOREIGN KEY (fact_id) REFERENCES atomic_facts(fact_id) ON DELETE CASCADE,
167
+ FOREIGN KEY (profile_id) REFERENCES profiles(profile_id) ON DELETE CASCADE
168
+ );
169
+ CREATE INDEX IF NOT EXISTS idx_fact_importance_profile
170
+ ON fact_importance(profile_id);
171
+ CREATE INDEX IF NOT EXISTS idx_fact_importance_pagerank
172
+ ON fact_importance(profile_id, pagerank_score DESC);
173
+ CREATE INDEX IF NOT EXISTS idx_fact_importance_community
174
+ ON fact_importance(profile_id, community_id);
175
+ """,
176
+ # --- Phase 5: Core Memory Blocks (Sleep-Time Consolidation) ---
177
+ """
178
+ CREATE TABLE IF NOT EXISTS core_memory_blocks (
179
+ block_id TEXT PRIMARY KEY,
180
+ profile_id TEXT NOT NULL,
181
+ block_type TEXT NOT NULL CHECK(block_type IN (
182
+ 'user_profile', 'project_context', 'behavioral_patterns',
183
+ 'active_decisions', 'learned_preferences', 'custom'
184
+ )),
185
+ content TEXT NOT NULL,
186
+ source_fact_ids TEXT NOT NULL DEFAULT '[]',
187
+ char_count INTEGER NOT NULL DEFAULT 0,
188
+ version INTEGER NOT NULL DEFAULT 1,
189
+ compiled_by TEXT NOT NULL DEFAULT 'rules',
190
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
191
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
192
+ FOREIGN KEY (profile_id) REFERENCES profiles(profile_id) ON DELETE CASCADE
193
+ );
194
+ CREATE INDEX IF NOT EXISTS idx_core_blocks_profile
195
+ ON core_memory_blocks(profile_id);
196
+ CREATE INDEX IF NOT EXISTS idx_core_blocks_type
197
+ ON core_memory_blocks(profile_id, block_type);
198
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_core_blocks_unique
199
+ ON core_memory_blocks(profile_id, block_type);
200
+ """,
201
+ # --- Phase 4: Temporal Intelligence ---
202
+ """
203
+ CREATE TABLE IF NOT EXISTS fact_temporal_validity (
204
+ fact_id TEXT PRIMARY KEY,
205
+ profile_id TEXT NOT NULL,
206
+
207
+ -- Event Time (when was this fact true in the real world?)
208
+ valid_from TEXT,
209
+ valid_until TEXT,
210
+
211
+ -- Transaction Time (when did the system learn/invalidate?)
212
+ system_created_at TEXT NOT NULL DEFAULT (datetime('now')),
213
+ system_expired_at TEXT,
214
+
215
+ -- Invalidation metadata
216
+ invalidated_by TEXT,
217
+ invalidation_reason TEXT,
218
+
219
+ FOREIGN KEY (fact_id) REFERENCES atomic_facts(fact_id) ON DELETE CASCADE,
220
+ FOREIGN KEY (profile_id) REFERENCES profiles(profile_id) ON DELETE CASCADE
221
+ );
222
+ CREATE INDEX IF NOT EXISTS idx_temporal_valid
223
+ ON fact_temporal_validity(profile_id, valid_until);
224
+ CREATE INDEX IF NOT EXISTS idx_temporal_system_expired
225
+ ON fact_temporal_validity(profile_id, system_expired_at);
226
+ CREATE INDEX IF NOT EXISTS idx_temporal_invalidated_by
227
+ ON fact_temporal_validity(invalidated_by);
228
+ """,
229
+ ]
230
+
231
+ # vec0 virtual table DDL — executed by VectorStore ONLY (requires extension loaded first).
232
+ # NOT in V32_DDL because executescript cannot load extensions mid-script.
233
+ V32_VEC0_DDL: Final[str] = """
234
+ CREATE VIRTUAL TABLE IF NOT EXISTS fact_embeddings USING vec0(
235
+ profile_id TEXT PARTITION KEY,
236
+ embedding float[768] distance_metric=cosine
237
+ );
238
+ """
239
+
240
+ # ---------------------------------------------------------------------------
241
+ # Rollback DDL (reverse FK order -- Rule 20)
242
+ # ---------------------------------------------------------------------------
243
+
244
+ V32_ROLLBACK: Final[tuple[str, ...]] = tuple(
245
+ f"DROP TABLE IF EXISTS {table}" for table in reversed(V32_TABLES)
246
+ )
247
+
248
+
249
+ def rollback_v32(conn) -> None:
250
+ """Drop all V32 tables in reverse FK order. For testing/rollback only."""
251
+ for sql in V32_ROLLBACK:
252
+ conn.execute(sql)
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
  import logging
14
14
  import shutil
15
15
  import sqlite3
16
+ import sys
16
17
  from datetime import datetime, UTC
17
18
  from pathlib import Path
18
19
 
@@ -390,8 +391,29 @@ class V2Migrator:
390
391
  original_backup = self._home / ".claude-memory-v2-original"
391
392
  if not original_backup.exists():
392
393
  self._v2_base.rename(original_backup)
393
- self._v2_base.symlink_to(self._v3_base)
394
- stats["steps"].append("Created symlink: .claude-memory -> .superlocalmemory")
394
+ try:
395
+ if sys.platform == "win32":
396
+ # On Windows, symlinks require admin privileges.
397
+ # Use a directory junction instead (works without elevation).
398
+ import subprocess
399
+ subprocess.run(
400
+ ["cmd", "/c", "mklink", "/J",
401
+ str(self._v2_base), str(self._v3_base)],
402
+ check=True, capture_output=True,
403
+ )
404
+ else:
405
+ self._v2_base.symlink_to(self._v3_base)
406
+ stats["steps"].append(
407
+ "Created symlink: .claude-memory -> .superlocalmemory"
408
+ )
409
+ except (OSError, subprocess.CalledProcessError) as exc:
410
+ logger.warning(
411
+ "Could not create symlink/junction: %s. "
412
+ "V2 backward compatibility link skipped.", exc,
413
+ )
414
+ stats["steps"].append(
415
+ f"Symlink skipped (OS error: {exc})"
416
+ )
395
417
  else:
396
418
  stats["steps"].append("Symlink skipped (backup dir already exists)")
397
419
  else: