superlocalmemory 3.6.22 → 3.7.0

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 (303) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +275 -72
  3. package/bin/slm-npm +43 -89
  4. package/docs/pi-dev-integration.md +43 -0
  5. package/ide/configs/antigravity-mcp.json +2 -2
  6. package/ide/configs/chatgpt-desktop-mcp.json +1 -1
  7. package/ide/configs/claude-desktop-mcp.json +2 -2
  8. package/ide/configs/windsurf-mcp.json +2 -2
  9. package/ide/hooks/context-hook.js +6 -2
  10. package/ide/hooks/post-recall-hook.js +7 -3
  11. package/ide/hooks/tool-event-hook.sh +2 -1
  12. package/package.json +19 -10
  13. package/plugin/.claude-plugin/plugin.json +1 -1
  14. package/plugin/_GENERATED.md +1 -1
  15. package/plugin/agents/slm-memory-advisor.md +1 -1
  16. package/plugin/requirements.txt +1 -1
  17. package/plugin/skills/slm-session/SKILL.md +1 -1
  18. package/plugin-src/rules/AGENTS.md +1 -1
  19. package/pyproject.toml +40 -8
  20. package/scripts/postinstall-interactive.js +17 -94
  21. package/scripts/postinstall.js +185 -258
  22. package/scripts/preuninstall.js +9 -50
  23. package/src/superlocalmemory/__init__.py +2 -2
  24. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  25. package/src/superlocalmemory/attribution/signer.py +34 -19
  26. package/src/superlocalmemory/attribution/watermark.py +1 -1
  27. package/src/superlocalmemory/cli/_lazy_init.py +3 -5
  28. package/src/superlocalmemory/cli/commands.py +490 -195
  29. package/src/superlocalmemory/cli/context_commands.py +5 -4
  30. package/src/superlocalmemory/cli/daemon.py +282 -187
  31. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  32. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  33. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  34. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  35. package/src/superlocalmemory/cli/main.py +128 -31
  36. package/src/superlocalmemory/cli/pending_store.py +54 -38
  37. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  38. package/src/superlocalmemory/cli/service_installer.py +57 -52
  39. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  40. package/src/superlocalmemory/cli/version_banner.py +2 -1
  41. package/src/superlocalmemory/code_graph/config.py +3 -1
  42. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  43. package/src/superlocalmemory/core/config.py +65 -20
  44. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  45. package/src/superlocalmemory/core/context_cache.py +56 -8
  46. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  47. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  48. package/src/superlocalmemory/core/embeddings.py +54 -18
  49. package/src/superlocalmemory/core/engine.py +150 -104
  50. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  51. package/src/superlocalmemory/core/engine_wiring.py +2 -0
  52. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  53. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  54. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  55. package/src/superlocalmemory/core/health_monitor.py +4 -2
  56. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  57. package/src/superlocalmemory/core/injection.py +69 -18
  58. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  59. package/src/superlocalmemory/core/maintenance.py +23 -22
  60. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  61. package/src/superlocalmemory/core/mutations.py +143 -0
  62. package/src/superlocalmemory/core/platform_utils.py +7 -4
  63. package/src/superlocalmemory/core/ram_lock.py +16 -5
  64. package/src/superlocalmemory/core/rate_limit.py +1 -1
  65. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  66. package/src/superlocalmemory/core/recall_worker.py +76 -59
  67. package/src/superlocalmemory/core/registry.py +1 -1
  68. package/src/superlocalmemory/core/scale_engine.py +293 -0
  69. package/src/superlocalmemory/core/score_contract.py +62 -0
  70. package/src/superlocalmemory/core/security_primitives.py +3 -1
  71. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  72. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  73. package/src/superlocalmemory/core/tier_manager.py +32 -20
  74. package/src/superlocalmemory/core/worker_pool.py +13 -4
  75. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  76. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  77. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  78. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  79. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  80. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  81. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  82. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  83. package/src/superlocalmemory/encoding/foresight.py +2 -0
  84. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  85. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  86. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  87. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  88. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  89. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  90. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  91. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  92. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  93. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  94. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  95. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  96. package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
  97. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  98. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  99. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  100. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  101. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  102. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  103. package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
  104. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  105. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  106. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  107. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  108. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  109. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  110. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  111. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  112. package/src/superlocalmemory/infra/backup.py +7 -5
  113. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  114. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  115. package/src/superlocalmemory/infra/data_root.py +199 -0
  116. package/src/superlocalmemory/infra/event_bus.py +3 -1
  117. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  118. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  119. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  120. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  121. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  122. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  123. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  124. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  125. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  126. package/src/superlocalmemory/learning/ensemble.py +11 -0
  127. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  128. package/src/superlocalmemory/learning/feedback.py +1 -1
  129. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  130. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  131. package/src/superlocalmemory/learning/ranker.py +4 -1
  132. package/src/superlocalmemory/learning/source_quality.py +1 -1
  133. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  134. package/src/superlocalmemory/llm/backbone.py +13 -8
  135. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  136. package/src/superlocalmemory/math/fisher.py +1 -1
  137. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  138. package/src/superlocalmemory/math/hopfield.py +1 -1
  139. package/src/superlocalmemory/math/langevin.py +1 -1
  140. package/src/superlocalmemory/math/polar_quant.py +3 -4
  141. package/src/superlocalmemory/math/qjl.py +1 -1
  142. package/src/superlocalmemory/math/sheaf.py +1 -1
  143. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  144. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  145. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  146. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  147. package/src/superlocalmemory/mcp/server.py +39 -13
  148. package/src/superlocalmemory/mcp/shared.py +69 -3
  149. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  150. package/src/superlocalmemory/mcp/tools_core.py +128 -29
  151. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  152. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  153. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  154. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  155. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  156. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  157. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  158. package/src/superlocalmemory/mesh/broker.py +2 -0
  159. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  160. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  161. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  162. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  163. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  164. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  165. package/src/superlocalmemory/optimize/config/store.py +6 -4
  166. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  167. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  168. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  169. package/src/superlocalmemory/optimize/storage/db.py +14 -13
  170. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  171. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  172. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  173. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  174. package/src/superlocalmemory/retrieval/engine.py +169 -79
  175. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  176. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  177. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  178. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  179. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  180. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  181. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  182. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  183. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  184. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  185. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  186. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  187. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  188. package/src/superlocalmemory/server/api.py +10 -7
  189. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  190. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  191. package/src/superlocalmemory/server/route_mutations.py +84 -0
  192. package/src/superlocalmemory/server/routes/agents.py +8 -6
  193. package/src/superlocalmemory/server/routes/brain.py +14 -12
  194. package/src/superlocalmemory/server/routes/chat.py +29 -12
  195. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  196. package/src/superlocalmemory/server/routes/helpers.py +29 -4
  197. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  198. package/src/superlocalmemory/server/routes/memories.py +104 -43
  199. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  200. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  201. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  202. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  203. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  204. package/src/superlocalmemory/server/security_middleware.py +1 -1
  205. package/src/superlocalmemory/server/ui.py +6 -3
  206. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  207. package/src/superlocalmemory/server/write_identity.py +147 -0
  208. package/src/superlocalmemory/storage/access_log.py +4 -3
  209. package/src/superlocalmemory/storage/database.py +118 -25
  210. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  211. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  212. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  213. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  214. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  215. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  216. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  217. package/src/superlocalmemory/storage/models.py +16 -0
  218. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  219. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  220. package/src/superlocalmemory/ui/favicon.svg +5 -0
  221. package/src/superlocalmemory/ui/index.html +1 -0
  222. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  223. package/src/superlocalmemory/ui/js/core.js +49 -8
  224. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  225. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  226. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  227. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  228. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  229. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  230. package/src/superlocalmemory/ui/js/settings.js +4 -2
  231. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  232. package/bin/slm +0 -59
  233. package/bin/slm.bat +0 -77
  234. package/bin/slm.cmd +0 -5
  235. package/ide/integrations/langchain/README.md +0 -106
  236. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  237. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  238. package/ide/integrations/langchain/pyproject.toml +0 -38
  239. package/ide/integrations/langchain/tests/__init__.py +0 -3
  240. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  241. package/ide/integrations/langchain/tests/test_security.py +0 -117
  242. package/ide/integrations/llamaindex/README.md +0 -81
  243. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  244. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  245. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  246. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  247. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  248. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  249. package/plugin-src/.mcp.json +0 -12
  250. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  251. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  252. package/plugin-src/hooks/.gitkeep +0 -0
  253. package/plugin-src/hooks/hooks.json +0 -23
  254. package/plugin-src/manifest.json +0 -25
  255. package/plugin-src/requirements.txt +0 -1
  256. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  257. package/plugin-src/scripts/ensure-venv.bat +0 -122
  258. package/plugin-src/scripts/ensure-venv.sh +0 -105
  259. package/plugin-src/scripts/slm-launch +0 -15
  260. package/plugin-src/scripts/slm-launch.bat +0 -17
  261. package/plugin-src/settings.json +0 -16
  262. package/plugin-src/skills/slm-cache/SKILL.md +0 -140
  263. package/plugin-src/skills/slm-compress/SKILL.md +0 -143
  264. package/plugin-src/skills/slm-graph/SKILL.md +0 -300
  265. package/plugin-src/skills/slm-recall/SKILL.md +0 -204
  266. package/plugin-src/skills/slm-remember/SKILL.md +0 -194
  267. package/plugin-src/skills/slm-session/SKILL.md +0 -207
  268. package/plugin-src/skills/slm-status/SKILL.md +0 -149
  269. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  270. package/scripts/_savings_math.py +0 -270
  271. package/scripts/build-dmg.sh +0 -417
  272. package/scripts/build-plugin.js +0 -742
  273. package/scripts/build-slm-hook.ps1 +0 -40
  274. package/scripts/build-slm-hook.sh +0 -45
  275. package/scripts/build_entry.py +0 -452
  276. package/scripts/ci/stage5b_gate.sh +0 -50
  277. package/scripts/dogfood_savings.py +0 -490
  278. package/scripts/generate-thumbnails.py +0 -218
  279. package/scripts/install-skills.ps1 +0 -4
  280. package/scripts/install-skills.sh +0 -5
  281. package/scripts/install.ps1 +0 -701
  282. package/scripts/install.sh +0 -1015
  283. package/scripts/postinstall_binary.js +0 -287
  284. package/scripts/prepack.js +0 -33
  285. package/scripts/release_manifest.py +0 -273
  286. package/scripts/slm-hook.spec +0 -56
  287. package/scripts/start-dashboard.ps1 +0 -52
  288. package/scripts/start-dashboard.sh +0 -41
  289. package/scripts/sync-wiki.ps1 +0 -127
  290. package/scripts/sync-wiki.sh +0 -82
  291. package/scripts/test-dmg.sh +0 -161
  292. package/scripts/test-npm-package.ps1 +0 -252
  293. package/scripts/test-npm-package.sh +0 -207
  294. package/scripts/verify-install.ps1 +0 -294
  295. package/scripts/verify-install.sh +0 -266
  296. package/scripts/verify-v27.ps1 +0 -301
  297. package/scripts/verify-v27.sh +0 -233
  298. package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
  299. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  300. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  301. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  302. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  303. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -79,7 +79,16 @@ class LanceDBVectorBackend:
79
79
  return self._db.create_table("embeddings", schema=schema)
80
80
 
81
81
  def close(self) -> None:
82
- """LanceDB is file-based no explicit close needed."""
82
+ """Release this backend's native table and connection references."""
83
+ for resource in (self._table, self._db):
84
+ close = getattr(resource, "close", None)
85
+ if callable(close):
86
+ try:
87
+ close()
88
+ except Exception:
89
+ logger.debug("LanceDB resource close failed", exc_info=True)
90
+ self._table = None
91
+ self._db = None
83
92
 
84
93
  # ------------------------------------------------------------------
85
94
  # Write Path
@@ -92,16 +101,33 @@ class LanceDBVectorBackend:
92
101
  tiers: list[str],
93
102
  profile_id: str = "default",
94
103
  ) -> int:
95
- """Batch insert vectors."""
104
+ """Idempotently insert or replace vectors by canonical fact ID."""
96
105
  if not fact_ids:
97
106
  return 0
98
107
  data = [
99
108
  {"fact_id": fid, "vector": emb, "tier": tier, "profile_id": profile_id}
100
109
  for fid, emb, tier in zip(fact_ids, embeddings, tiers)
101
110
  ]
102
- self._table.add(data)
111
+ # `add()` permits duplicate fact IDs on retries. Store/retry paths are
112
+ # at-least-once by design, so use LanceDB's merge operation to keep the
113
+ # projection one-row-per-fact and safe to replay after a restart.
114
+ (
115
+ self._table.merge_insert("fact_id")
116
+ .when_matched_update_all()
117
+ .when_not_matched_insert_all()
118
+ .execute(data)
119
+ )
103
120
  return len(data)
104
121
 
122
+ @staticmethod
123
+ def _fact_predicate(fact_id: str) -> str:
124
+ """Build a Lance SQL literal without allowing predicate injection."""
125
+ return "fact_id = '" + fact_id.replace("'", "''") + "'"
126
+
127
+ def remove_vector(self, fact_id: str) -> None:
128
+ """Delete one derived vector after its canonical fact is deleted."""
129
+ self._table.delete(self._fact_predicate(fact_id))
130
+
105
131
  # ------------------------------------------------------------------
106
132
  # Read Path
107
133
  # ------------------------------------------------------------------
@@ -111,6 +137,7 @@ class LanceDBVectorBackend:
111
137
  query_vector: list[float],
112
138
  top_k: int = 50,
113
139
  tier_filter: list[str] | None = None,
140
+ profile_id: str = "default",
114
141
  ) -> list[tuple[str, float]]:
115
142
  """ANN search with optional tier filter.
116
143
 
@@ -131,7 +158,10 @@ class LanceDBVectorBackend:
131
158
 
132
159
  # Build tier filter string for LanceDB SQL-like where clause
133
160
  tier_str = ", ".join(f"'{t}'" for t in tier_filter)
134
- results = search.where(f"tier IN ({tier_str})").to_list()
161
+ profile_literal = profile_id.replace("'", "''")
162
+ results = search.where(
163
+ f"tier IN ({tier_str}) AND profile_id = '{profile_literal}'"
164
+ ).to_list()
135
165
 
136
166
  # Convert distance → similarity (F-08)
137
167
  return [(r["fact_id"], 1.0 - r["_distance"]) for r in results]
@@ -143,7 +173,9 @@ class LanceDBVectorBackend:
143
173
  # Bulk Import (sqlite-vec → LanceDB)
144
174
  # ------------------------------------------------------------------
145
175
 
146
- def bulk_import_from_sqlite(self, conn: sqlite3.Connection) -> int:
176
+ def bulk_import_from_sqlite(
177
+ self, conn: sqlite3.Connection, profile_id: str = "default",
178
+ ) -> int:
147
179
  """Export embeddings from sqlite-vec → LanceDB.
148
180
 
149
181
  sqlite-vec stores vectors as raw float32 little-endian blobs
@@ -163,11 +195,15 @@ class LanceDBVectorBackend:
163
195
 
164
196
  # Get tiers
165
197
  tier_map: dict[str, str] = {}
198
+ profile_map: dict[str, str] = {}
166
199
  try:
167
200
  for row in conn.execute(
168
- "SELECT fact_id, COALESCE(lifecycle, 'active') FROM atomic_facts"
201
+ "SELECT fact_id, COALESCE(lifecycle, 'active'), profile_id "
202
+ "FROM atomic_facts WHERE profile_id = ?",
203
+ (profile_id,),
169
204
  ):
170
205
  tier_map[row[0]] = row[1]
206
+ profile_map[row[0]] = row[2] or "default"
171
207
  except sqlite3.OperationalError:
172
208
  pass
173
209
 
@@ -184,7 +220,10 @@ class LanceDBVectorBackend:
184
220
  data = []
185
221
  for rowid, blob in rows:
186
222
  fact_id = row_map.get(rowid)
187
- if fact_id is None:
223
+ # The rowid mapping is global, but a staged Scale Engine is
224
+ # explicitly profile-scoped. Do not import a foreign profile by
225
+ # giving it a default tier/profile below.
226
+ if fact_id is None or fact_id not in profile_map:
188
227
  continue
189
228
  try:
190
229
  vector = self._decode_vector_blob(blob)
@@ -196,11 +235,20 @@ class LanceDBVectorBackend:
196
235
  "fact_id": fact_id,
197
236
  "vector": vector,
198
237
  "tier": tier,
199
- "profile_id": "default",
238
+ "profile_id": profile_map.get(fact_id, profile_id),
200
239
  })
201
240
 
202
241
  if data:
203
- self._table.add(data)
242
+ by_profile: dict[str, list[dict[str, Any]]] = {}
243
+ for item in data:
244
+ by_profile.setdefault(item["profile_id"], []).append(item)
245
+ for record_profile_id, records in by_profile.items():
246
+ self.add_vectors(
247
+ [item["fact_id"] for item in records],
248
+ [item["vector"] for item in records],
249
+ [item["tier"] for item in records],
250
+ record_profile_id,
251
+ )
204
252
 
205
253
  logger.info("LanceDB: imported %d vectors from sqlite-vec", len(data))
206
254
  return len(data)
package/bin/slm DELETED
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SuperLocalMemory V3 CLI
3
- # Part of Qualixar | https://superlocalmemory.com
4
-
5
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
- PKG_ROOT="$(dirname "$SCRIPT_DIR")"
7
- SRC_DIR="$PKG_ROOT/src"
8
-
9
- # Handle --version / -v directly (fast path)
10
- for arg in "$@"; do
11
- if [ "$arg" = "--version" ] || [ "$arg" = "-v" ]; then
12
- VER=$(grep '"version"' "$PKG_ROOT/package.json" 2>/dev/null | head -1 | sed 's/.*"\([0-9][0-9.]*\)".*/\1/')
13
- echo "superlocalmemory ${VER:-unknown}"
14
- exit 0
15
- fi
16
- done
17
-
18
- # LLD-06 §6.3 — prefer the PyInstaller-built binary on the hot hook path.
19
- # If the onedir binary exists and isn't disabled, run it directly; the
20
- # binary is stdlib-only and ~30ms cold on Windows. Otherwise fall back
21
- # to the Python CLI below.
22
- SLM_HOOK_BIN="${SLM_HOOK_BINARY:-$HOME/.superlocalmemory/bin/slm-hook/slm-hook}"
23
- if [ "${1:-}" = "hook" ] && [ "${2:-}" = "user_prompt_submit" ] \
24
- && [ -x "$SLM_HOOK_BIN" ] \
25
- && [ "${SLM_HOOK_BINARY_DISABLED:-0}" != "1" ]; then
26
- exec "$SLM_HOOK_BIN"
27
- fi
28
-
29
- # Find Python
30
- PYTHON=""
31
- for cmd in python3 python; do
32
- if command -v "$cmd" &>/dev/null; then
33
- PYTHON="$cmd"
34
- break
35
- fi
36
- done
37
-
38
- if [ -z "$PYTHON" ]; then
39
- echo "Error: Python 3 not found. Install Python 3.11+ from python.org"
40
- exit 1
41
- fi
42
-
43
- # Set PYTHONPATH so Python finds the npm package's src/ directory
44
- if [ -n "$PYTHONPATH" ]; then
45
- export PYTHONPATH="$SRC_DIR:$PYTHONPATH"
46
- else
47
- export PYTHONPATH="$SRC_DIR"
48
- fi
49
-
50
- # Prevent PyTorch Metal/MPS GPU memory reservation
51
- export PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0
52
- export PYTORCH_MPS_MEM_LIMIT=0
53
- export PYTORCH_ENABLE_MPS_FALLBACK=1
54
- export TOKENIZERS_PARALLELISM=false
55
- export TORCH_DEVICE=cpu
56
- export CUDA_VISIBLE_DEVICES=""
57
-
58
- # Run V3 CLI
59
- exec "$PYTHON" -m superlocalmemory.cli.main "$@"
package/bin/slm.bat DELETED
@@ -1,77 +0,0 @@
1
- @echo off
2
- REM SuperLocalMemory V3 - Windows CLI Wrapper
3
- REM Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
4
- REM Licensed under MIT License
5
- REM Repository: https://github.com/qualixar/superlocalmemory
6
-
7
- setlocal enabledelayedexpansion
8
-
9
- REM Resolve the package src/ directory for PYTHONPATH
10
- set "SLM_PKG_DIR=%~dp0..\src"
11
-
12
- REM Handle --version / -v directly (fast path, no Python needed)
13
- if "%~1"=="--version" goto :show_version
14
- if "%~1"=="-v" goto :show_version
15
-
16
- REM LLD-06 §6.3 — prefer the PyInstaller-built binary on the hot hook path.
17
- set "SLM_HOOK_BIN=%USERPROFILE%\.superlocalmemory\bin\slm-hook\slm-hook.exe"
18
- if defined SLM_HOOK_BINARY set "SLM_HOOK_BIN=%SLM_HOOK_BINARY%"
19
- if "%~1"=="hook" if "%~2"=="user_prompt_submit" (
20
- if exist "%SLM_HOOK_BIN%" (
21
- if not "%SLM_HOOK_BINARY_DISABLED%"=="1" (
22
- "%SLM_HOOK_BIN%"
23
- exit /b %ERRORLEVEL%
24
- )
25
- )
26
- )
27
-
28
- REM Find Python 3
29
- where python3 >nul 2>&1
30
- if %ERRORLEVEL% EQU 0 (
31
- set PYTHON_CMD=python3
32
- goto :run
33
- )
34
- where python >nul 2>&1
35
- if %ERRORLEVEL% EQU 0 (
36
- set PYTHON_CMD=python
37
- goto :run
38
- )
39
- where py >nul 2>&1
40
- if %ERRORLEVEL% EQU 0 (
41
- set PYTHON_CMD=py -3
42
- goto :run
43
- )
44
-
45
- echo Error: Python 3.11+ not found.
46
- echo Install from: https://python.org/downloads/
47
- exit /b 1
48
-
49
- :show_version
50
- REM Read version from package.json via findstr
51
- for /f "tokens=2 delims=:," %%a in ('findstr /C:"\"version\"" "%~dp0..\package.json"') do (
52
- set "VER=%%~a"
53
- set "VER=!VER: =!"
54
- echo superlocalmemory !VER!
55
- exit /b 0
56
- )
57
- echo superlocalmemory unknown
58
- exit /b 0
59
-
60
- :run
61
- REM Set PYTHONPATH so Python finds the npm package's src/ directory
62
- if defined PYTHONPATH (
63
- set "PYTHONPATH=%SLM_PKG_DIR%;%PYTHONPATH%"
64
- ) else (
65
- set "PYTHONPATH=%SLM_PKG_DIR%"
66
- )
67
-
68
- REM Prevent PyTorch Metal/MPS GPU memory reservation
69
- set "PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0"
70
- set "PYTORCH_MPS_MEM_LIMIT=0"
71
- set "PYTORCH_ENABLE_MPS_FALLBACK=1"
72
- set "TOKENIZERS_PARALLELISM=false"
73
- set "TORCH_DEVICE=cpu"
74
- set "CUDA_VISIBLE_DEVICES="
75
-
76
- %PYTHON_CMD% -m superlocalmemory.cli.main %*
77
- exit /b %ERRORLEVEL%
package/bin/slm.cmd DELETED
@@ -1,5 +0,0 @@
1
- @echo off
2
- REM SuperLocalMemory V3 - Windows CLI (CMD variant)
3
- REM Delegates to slm.bat which handles PYTHONPATH and --version
4
- call "%~dp0slm.bat" %*
5
- exit /b %ERRORLEVEL%
@@ -1,106 +0,0 @@
1
- # langchain-superlocalmemory
2
-
3
- LangChain chat message history backed by [SuperLocalMemory V2](https://github.com/qualixar/superlocalmemory) -- 100% local, zero cloud.
4
-
5
- Every message stays on your machine in a SQLite database. No API keys, no subscriptions, no telemetry.
6
-
7
- ## Prerequisites
8
-
9
- - Python 3.10+
10
- - [SuperLocalMemory V2](https://github.com/qualixar/superlocalmemory) installed (`~/.superlocalmemory/` must exist)
11
- - `langchain-core >= 1.0.0`
12
-
13
- ## Installation
14
-
15
- ```bash
16
- pip install langchain-superlocalmemory
17
- ```
18
-
19
- Or install from source:
20
-
21
- ```bash
22
- cd integrations/langchain
23
- pip install -e .
24
- ```
25
-
26
- ## Quick Start
27
-
28
- ```python
29
- from langchain_core.messages import AIMessage, HumanMessage
30
- from langchain_superlocalmemory import SuperLocalMemoryChatMessageHistory
31
-
32
- # Create a history for a conversation session
33
- history = SuperLocalMemoryChatMessageHistory(session_id="my-chat-session")
34
-
35
- # Add messages
36
- history.add_messages([
37
- HumanMessage(content="What is SuperLocalMemory?"),
38
- AIMessage(content="It's a local-first memory system for AI assistants."),
39
- ])
40
-
41
- # Retrieve messages (chronological order)
42
- for msg in history.messages:
43
- print(f"{msg.type}: {msg.content}")
44
-
45
- # Clear the session
46
- history.clear()
47
- ```
48
-
49
- ## Features
50
-
51
- - **Local-first storage** -- all data stays in `~/.superlocalmemory/memory.db`
52
- - **Session isolation** -- each `session_id` is completely independent
53
- - **Full LangChain compatibility** -- implements `BaseChatMessageHistory`
54
- - **Persistent across restarts** -- SQLite-backed, survives process exit
55
- - **Works alongside SLM** -- messages are queryable via CLI, MCP, Skills, and REST API
56
- - **All message types** -- HumanMessage, AIMessage, SystemMessage, FunctionMessage, ToolMessage
57
- - **additional_kwargs preserved** -- metadata round-trips through serialization
58
-
59
- ## Multi-Session Example
60
-
61
- ```python
62
- from langchain_superlocalmemory import SuperLocalMemoryChatMessageHistory
63
-
64
- # Two independent conversations
65
- support = SuperLocalMemoryChatMessageHistory(session_id="support-ticket-42")
66
- coding = SuperLocalMemoryChatMessageHistory(session_id="code-review-pr-99")
67
-
68
- # Messages are isolated -- support session cannot see coding session
69
- support.add_messages([HumanMessage(content="My app is crashing")])
70
- coding.add_messages([HumanMessage(content="Review this PR please")])
71
-
72
- assert len(support.messages) == 1
73
- assert len(coding.messages) == 1
74
- ```
75
-
76
- ## Custom Database Path
77
-
78
- By default the package uses `~/.superlocalmemory/memory.db`. You can point to a different database:
79
-
80
- ```python
81
- history = SuperLocalMemoryChatMessageHistory(
82
- session_id="my-session",
83
- db_path="/path/to/custom/memory.db",
84
- )
85
- ```
86
-
87
- ## How It Works
88
-
89
- Each LangChain message is stored as an individual memory entry in SuperLocalMemory V2:
90
-
91
- - **Content**: JSON-serialized message (type, content, additional_kwargs)
92
- - **Tags**: `["langchain", "langchain:session:<session_id>"]`
93
- - **Importance**: 3 (lower than user memories, so chat history does not crowd search results)
94
- - **Project**: `"langchain"`
95
-
96
- This means your LangChain conversations are visible in the SLM dashboard, searchable via `slm recall`, and accessible from any SLM-integrated tool.
97
-
98
- ## License
99
-
100
- AGPL-3.0 -- see [LICENSE](../../LICENSE) for details.
101
-
102
- ## Links
103
-
104
- - [SuperLocalMemory V2 Repository](https://github.com/qualixar/superlocalmemory)
105
- - [Documentation](https://superlocalmemory.com/)
106
- - [LangChain Documentation](https://python.langchain.com/)
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- from langchain_superlocalmemory.chat_message_history import (
5
- SuperLocalMemoryChatMessageHistory,
6
- )
7
-
8
- __all__ = ["SuperLocalMemoryChatMessageHistory"]
9
- __version__ = "0.1.0"
@@ -1,201 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """SuperLocalMemory V2 - LangChain Chat Message History
5
-
6
- Implements LangChain's BaseChatMessageHistory backed by SuperLocalMemory V2's
7
- local SQLite storage. All data stays on your machine -- zero cloud, zero telemetry.
8
-
9
- Usage:
10
- from langchain_superlocalmemory import SuperLocalMemoryChatMessageHistory
11
-
12
- history = SuperLocalMemoryChatMessageHistory(session_id="my-session")
13
- history.add_messages([HumanMessage(content="Hello")])
14
- print(history.messages)
15
- """
16
- import json
17
- import sys
18
- from pathlib import Path
19
- from typing import List, Optional, Sequence
20
-
21
- from langchain_core.chat_history import BaseChatMessageHistory
22
- from langchain_core.messages import (
23
- AIMessage,
24
- BaseMessage,
25
- FunctionMessage,
26
- HumanMessage,
27
- SystemMessage,
28
- ToolMessage,
29
- message_to_dict,
30
- messages_from_dict,
31
- )
32
-
33
- # ---------------------------------------------------------------------------
34
- # MemoryStoreV2 import strategy
35
- # ---------------------------------------------------------------------------
36
- # SuperLocalMemory V2 installs to ~/.superlocalmemory/. We add that path so
37
- # the MemoryStoreV2 class can be imported. If SLM is not installed, we
38
- # raise a clear error at construction time (not import time) so the package
39
- # itself can still be imported for introspection.
40
- # ---------------------------------------------------------------------------
41
-
42
- _SLM_PATH = Path.home() / ".superlocalmemory"
43
- _MemoryStoreV2 = None
44
-
45
-
46
- def _ensure_slm_imported():
47
- """Lazily import MemoryStoreV2, raising a clear error if unavailable."""
48
- global _MemoryStoreV2
49
- if _MemoryStoreV2 is not None:
50
- return _MemoryStoreV2
51
-
52
- slm_path_str = str(_SLM_PATH)
53
- if slm_path_str not in sys.path:
54
- sys.path.insert(0, slm_path_str)
55
-
56
- try:
57
- from superlocalmemory.core.engine import MemoryEngine # type: ignore[import-untyped]
58
-
59
- _MemoryStoreV2 = MemoryStoreV2
60
- return _MemoryStoreV2
61
- except ImportError as exc:
62
- raise ImportError(
63
- "SuperLocalMemory V2 is not installed. "
64
- "Run the installer from https://github.com/qualixar/superlocalmemory "
65
- "or ensure ~/.superlocalmemory/is installed via npm/pip."
66
- ) from exc
67
-
68
-
69
- # ---------------------------------------------------------------------------
70
- # Message (de)serialization helpers
71
- # ---------------------------------------------------------------------------
72
-
73
- # Map from LangChain message type string to the concrete class used for
74
- # deserialization. LangChain's own `messages_from_dict` handles this, but we
75
- # keep a lookup for the fallback path in case the dict format diverges.
76
-
77
- _MESSAGE_TYPE_MAP = {
78
- "human": HumanMessage,
79
- "ai": AIMessage,
80
- "system": SystemMessage,
81
- "function": FunctionMessage,
82
- "tool": ToolMessage,
83
- }
84
-
85
-
86
- def _serialize_message(message: BaseMessage) -> str:
87
- """Serialize a LangChain BaseMessage to a JSON string for SLM storage."""
88
- return json.dumps(message_to_dict(message), ensure_ascii=False)
89
-
90
-
91
- def _deserialize_messages(dicts: List[dict]) -> List[BaseMessage]:
92
- """Deserialize a list of message dicts back to BaseMessage instances.
93
-
94
- Uses LangChain's ``messages_from_dict`` which handles all known message
95
- types including ``additional_kwargs``.
96
- """
97
- return messages_from_dict(dicts)
98
-
99
-
100
- # ---------------------------------------------------------------------------
101
- # Main class
102
- # ---------------------------------------------------------------------------
103
-
104
-
105
- class SuperLocalMemoryChatMessageHistory(BaseChatMessageHistory):
106
- """LangChain chat message history backed by SuperLocalMemory V2.
107
-
108
- Each message is stored as an individual memory entry in the SLM SQLite
109
- database, tagged with the session ID for isolation. This keeps the data
110
- fully local and queryable via any SLM access method (MCP, CLI, Skills,
111
- REST API).
112
-
113
- Parameters
114
- ----------
115
- session_id : str
116
- Unique identifier for the conversation session. Messages from
117
- different session IDs are completely isolated.
118
- db_path : str or None
119
- Path to the SQLite database file. Defaults to
120
- ``~/.superlocalmemory/memory.db``.
121
- """
122
-
123
- # Tag prefix used to isolate LangChain session messages inside SLM.
124
- _TAG_PREFIX = "langchain:session:"
125
-
126
- def __init__(self, session_id: str, db_path: Optional[str] = None) -> None:
127
- self.session_id = session_id
128
- self.db_path = db_path
129
-
130
- MemoryStoreV2 = _ensure_slm_imported()
131
- store_path = Path(db_path) if db_path else None
132
- self._store = MemoryStoreV2(db_path=store_path)
133
-
134
- # -- property: messages ------------------------------------------------
135
-
136
- @property
137
- def messages(self) -> List[BaseMessage]: # type: ignore[override]
138
- """Return all messages for this session, ordered chronologically."""
139
- session_tag = f"{self._TAG_PREFIX}{self.session_id}"
140
-
141
- # Retrieve a generous batch from SLM. We filter by tag in Python
142
- # because list_all does not accept a tag filter parameter.
143
- all_memories = self._store.list_all(limit=10_000)
144
-
145
- # Filter to memories belonging to this session.
146
- session_memories = [
147
- m for m in all_memories if session_tag in (m.get("tags") or [])
148
- ]
149
-
150
- # list_all returns newest-first (ORDER BY created_at DESC).
151
- # We need chronological (oldest-first) order for chat history.
152
- session_memories.sort(key=lambda m: m.get("created_at", ""))
153
-
154
- # Deserialize each memory's content back to a BaseMessage.
155
- message_dicts: List[dict] = []
156
- for mem in session_memories:
157
- try:
158
- parsed = json.loads(mem["content"])
159
- message_dicts.append(parsed)
160
- except (json.JSONDecodeError, KeyError, TypeError):
161
- # Skip malformed entries silently -- they may be non-LangChain
162
- # memories that happen to share the tag pattern.
163
- continue
164
-
165
- if not message_dicts:
166
- return []
167
-
168
- return _deserialize_messages(message_dicts)
169
-
170
- # -- add_messages ------------------------------------------------------
171
-
172
- def add_messages(self, messages: Sequence[BaseMessage]) -> None:
173
- """Persist messages to SuperLocalMemory V2.
174
-
175
- Each message becomes a separate memory entry tagged with the session
176
- identifier. Importance is set to 3 (lower than typical user
177
- memories at 5) so LangChain history does not crowd out higher-value
178
- entries in search results.
179
- """
180
- session_tag = f"{self._TAG_PREFIX}{self.session_id}"
181
-
182
- for message in messages:
183
- serialized = _serialize_message(message)
184
- self._store.add_memory(
185
- content=serialized,
186
- tags=["langchain", session_tag],
187
- importance=3,
188
- project_name="langchain",
189
- )
190
-
191
- # -- clear -------------------------------------------------------------
192
-
193
- def clear(self) -> None:
194
- """Remove all messages for this session from the store."""
195
- session_tag = f"{self._TAG_PREFIX}{self.session_id}"
196
-
197
- all_memories = self._store.list_all(limit=10_000)
198
-
199
- for mem in all_memories:
200
- if session_tag in (mem.get("tags") or []):
201
- self._store.delete_memory(mem["id"])
@@ -1,38 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "langchain-superlocalmemory"
7
- version = "0.1.0"
8
- description = "LangChain chat message history backed by SuperLocalMemory — 100% local, zero cloud"
9
- readme = "README.md"
10
- license = "AGPL-3.0-or-later"
11
- requires-python = ">=3.10"
12
- authors = [
13
- { name = "Varun Pratap Bhardwaj" },
14
- ]
15
- keywords = ["langchain", "memory", "local-first", "privacy", "sqlite", "chat-history", "mcp"]
16
- classifiers = [
17
- "Development Status :: 4 - Beta",
18
- "Intended Audience :: Developers",
19
- "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
20
- "Programming Language :: Python :: 3",
21
- "Programming Language :: Python :: 3.10",
22
- "Programming Language :: Python :: 3.11",
23
- "Programming Language :: Python :: 3.12",
24
- "Programming Language :: Python :: 3.13",
25
- "Topic :: Software Development :: Libraries",
26
- "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
- ]
28
- dependencies = [
29
- "langchain-core>=1.0.0",
30
- ]
31
-
32
- [project.urls]
33
- Homepage = "https://github.com/qualixar/superlocalmemory"
34
- Documentation = "https://superlocalmemory.com/"
35
- Repository = "https://github.com/qualixar/superlocalmemory"
36
-
37
- [tool.pytest.ini_options]
38
- testpaths = ["tests"]
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)