superlocalmemory 3.6.23 → 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 (301) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +271 -71
  3. package/bin/slm-npm +43 -89
  4. package/ide/configs/antigravity-mcp.json +2 -2
  5. package/ide/configs/chatgpt-desktop-mcp.json +1 -1
  6. package/ide/configs/claude-desktop-mcp.json +2 -2
  7. package/ide/configs/windsurf-mcp.json +2 -2
  8. package/ide/hooks/context-hook.js +6 -2
  9. package/ide/hooks/post-recall-hook.js +7 -3
  10. package/ide/hooks/tool-event-hook.sh +2 -1
  11. package/package.json +18 -10
  12. package/plugin/.claude-plugin/plugin.json +1 -1
  13. package/plugin/_GENERATED.md +1 -1
  14. package/plugin/agents/slm-memory-advisor.md +1 -1
  15. package/plugin/requirements.txt +1 -1
  16. package/plugin/skills/slm-session/SKILL.md +1 -1
  17. package/plugin-src/rules/AGENTS.md +1 -1
  18. package/pyproject.toml +40 -8
  19. package/scripts/postinstall-interactive.js +17 -94
  20. package/scripts/postinstall.js +185 -258
  21. package/scripts/preuninstall.js +9 -50
  22. package/src/superlocalmemory/__init__.py +2 -2
  23. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  24. package/src/superlocalmemory/attribution/signer.py +34 -19
  25. package/src/superlocalmemory/attribution/watermark.py +1 -1
  26. package/src/superlocalmemory/cli/_lazy_init.py +3 -5
  27. package/src/superlocalmemory/cli/commands.py +453 -191
  28. package/src/superlocalmemory/cli/context_commands.py +5 -4
  29. package/src/superlocalmemory/cli/daemon.py +282 -187
  30. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  31. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  32. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  33. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  34. package/src/superlocalmemory/cli/main.py +128 -31
  35. package/src/superlocalmemory/cli/pending_store.py +54 -38
  36. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  37. package/src/superlocalmemory/cli/service_installer.py +57 -52
  38. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  39. package/src/superlocalmemory/cli/version_banner.py +2 -1
  40. package/src/superlocalmemory/code_graph/config.py +3 -1
  41. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  42. package/src/superlocalmemory/core/config.py +65 -20
  43. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  44. package/src/superlocalmemory/core/context_cache.py +56 -8
  45. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  46. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  47. package/src/superlocalmemory/core/embeddings.py +54 -18
  48. package/src/superlocalmemory/core/engine.py +150 -104
  49. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  50. package/src/superlocalmemory/core/engine_wiring.py +2 -0
  51. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  52. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  53. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  54. package/src/superlocalmemory/core/health_monitor.py +4 -2
  55. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  56. package/src/superlocalmemory/core/injection.py +69 -18
  57. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  58. package/src/superlocalmemory/core/maintenance.py +1 -1
  59. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  60. package/src/superlocalmemory/core/mutations.py +143 -0
  61. package/src/superlocalmemory/core/platform_utils.py +7 -4
  62. package/src/superlocalmemory/core/ram_lock.py +16 -5
  63. package/src/superlocalmemory/core/rate_limit.py +1 -1
  64. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  65. package/src/superlocalmemory/core/recall_worker.py +76 -59
  66. package/src/superlocalmemory/core/registry.py +1 -1
  67. package/src/superlocalmemory/core/scale_engine.py +293 -0
  68. package/src/superlocalmemory/core/score_contract.py +62 -0
  69. package/src/superlocalmemory/core/security_primitives.py +3 -1
  70. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  71. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  72. package/src/superlocalmemory/core/tier_manager.py +32 -20
  73. package/src/superlocalmemory/core/worker_pool.py +13 -4
  74. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  75. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  76. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  77. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  78. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  79. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  80. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  81. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  82. package/src/superlocalmemory/encoding/foresight.py +2 -0
  83. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  84. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  85. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  86. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  87. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  88. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  89. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  90. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  91. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  92. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  93. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  94. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  95. package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
  96. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  97. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  98. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  99. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  100. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  101. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  102. package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
  103. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  104. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  105. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  106. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  107. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  108. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  109. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  110. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  111. package/src/superlocalmemory/infra/backup.py +7 -5
  112. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  113. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  114. package/src/superlocalmemory/infra/data_root.py +199 -0
  115. package/src/superlocalmemory/infra/event_bus.py +3 -1
  116. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  117. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  118. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  119. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  120. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  121. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  122. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  123. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  124. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  125. package/src/superlocalmemory/learning/ensemble.py +11 -0
  126. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  127. package/src/superlocalmemory/learning/feedback.py +1 -1
  128. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  129. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  130. package/src/superlocalmemory/learning/ranker.py +4 -1
  131. package/src/superlocalmemory/learning/source_quality.py +1 -1
  132. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  133. package/src/superlocalmemory/llm/backbone.py +1 -1
  134. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  135. package/src/superlocalmemory/math/fisher.py +1 -1
  136. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  137. package/src/superlocalmemory/math/hopfield.py +1 -1
  138. package/src/superlocalmemory/math/langevin.py +1 -1
  139. package/src/superlocalmemory/math/polar_quant.py +3 -4
  140. package/src/superlocalmemory/math/qjl.py +1 -1
  141. package/src/superlocalmemory/math/sheaf.py +1 -1
  142. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  143. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  144. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  145. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  146. package/src/superlocalmemory/mcp/server.py +39 -13
  147. package/src/superlocalmemory/mcp/shared.py +69 -3
  148. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  149. package/src/superlocalmemory/mcp/tools_core.py +128 -29
  150. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  151. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  152. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  153. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  154. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  155. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  156. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  157. package/src/superlocalmemory/mesh/broker.py +2 -0
  158. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  159. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  160. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  161. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  162. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  163. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  164. package/src/superlocalmemory/optimize/config/store.py +6 -4
  165. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  166. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  167. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  168. package/src/superlocalmemory/optimize/storage/db.py +12 -12
  169. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  170. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  171. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  172. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  173. package/src/superlocalmemory/retrieval/engine.py +169 -79
  174. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  175. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  176. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  177. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  178. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  179. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  180. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  181. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  182. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  183. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  184. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  185. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  186. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  187. package/src/superlocalmemory/server/api.py +10 -7
  188. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  189. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  190. package/src/superlocalmemory/server/route_mutations.py +84 -0
  191. package/src/superlocalmemory/server/routes/agents.py +8 -6
  192. package/src/superlocalmemory/server/routes/brain.py +14 -12
  193. package/src/superlocalmemory/server/routes/chat.py +29 -12
  194. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  195. package/src/superlocalmemory/server/routes/helpers.py +8 -63
  196. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  197. package/src/superlocalmemory/server/routes/memories.py +104 -43
  198. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  199. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  200. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  201. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  202. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  203. package/src/superlocalmemory/server/security_middleware.py +1 -1
  204. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  205. package/src/superlocalmemory/server/write_identity.py +147 -0
  206. package/src/superlocalmemory/storage/access_log.py +4 -3
  207. package/src/superlocalmemory/storage/database.py +118 -25
  208. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  209. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  210. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  211. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  212. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  213. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  214. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  215. package/src/superlocalmemory/storage/models.py +16 -0
  216. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  217. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  218. package/src/superlocalmemory/ui/favicon.svg +5 -0
  219. package/src/superlocalmemory/ui/index.html +1 -0
  220. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  221. package/src/superlocalmemory/ui/js/core.js +49 -8
  222. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  223. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  224. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  225. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  226. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  227. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  228. package/src/superlocalmemory/ui/js/settings.js +4 -2
  229. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  230. package/bin/slm +0 -59
  231. package/bin/slm.bat +0 -77
  232. package/bin/slm.cmd +0 -5
  233. package/ide/integrations/langchain/README.md +0 -106
  234. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  235. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  236. package/ide/integrations/langchain/pyproject.toml +0 -38
  237. package/ide/integrations/langchain/tests/__init__.py +0 -3
  238. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  239. package/ide/integrations/langchain/tests/test_security.py +0 -117
  240. package/ide/integrations/llamaindex/README.md +0 -81
  241. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  242. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  243. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  244. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  245. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  246. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  247. package/plugin-src/.mcp.json +0 -12
  248. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  249. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  250. package/plugin-src/hooks/.gitkeep +0 -0
  251. package/plugin-src/hooks/hooks.json +0 -23
  252. package/plugin-src/manifest.json +0 -25
  253. package/plugin-src/requirements.txt +0 -1
  254. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  255. package/plugin-src/scripts/ensure-venv.bat +0 -122
  256. package/plugin-src/scripts/ensure-venv.sh +0 -105
  257. package/plugin-src/scripts/slm-launch +0 -15
  258. package/plugin-src/scripts/slm-launch.bat +0 -17
  259. package/plugin-src/settings.json +0 -16
  260. package/plugin-src/skills/slm-cache/SKILL.md +0 -140
  261. package/plugin-src/skills/slm-compress/SKILL.md +0 -143
  262. package/plugin-src/skills/slm-graph/SKILL.md +0 -300
  263. package/plugin-src/skills/slm-recall/SKILL.md +0 -204
  264. package/plugin-src/skills/slm-remember/SKILL.md +0 -194
  265. package/plugin-src/skills/slm-session/SKILL.md +0 -207
  266. package/plugin-src/skills/slm-status/SKILL.md +0 -149
  267. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  268. package/scripts/_savings_math.py +0 -270
  269. package/scripts/build-dmg.sh +0 -417
  270. package/scripts/build-plugin.js +0 -742
  271. package/scripts/build-slm-hook.ps1 +0 -40
  272. package/scripts/build-slm-hook.sh +0 -45
  273. package/scripts/build_entry.py +0 -452
  274. package/scripts/ci/stage5b_gate.sh +0 -50
  275. package/scripts/dogfood_savings.py +0 -490
  276. package/scripts/generate-thumbnails.py +0 -218
  277. package/scripts/install-skills.ps1 +0 -4
  278. package/scripts/install-skills.sh +0 -5
  279. package/scripts/install.ps1 +0 -701
  280. package/scripts/install.sh +0 -1015
  281. package/scripts/postinstall_binary.js +0 -287
  282. package/scripts/prepack.js +0 -33
  283. package/scripts/release_manifest.py +0 -273
  284. package/scripts/slm-hook.spec +0 -56
  285. package/scripts/start-dashboard.ps1 +0 -52
  286. package/scripts/start-dashboard.sh +0 -41
  287. package/scripts/sync-wiki.ps1 +0 -127
  288. package/scripts/sync-wiki.sh +0 -82
  289. package/scripts/test-dmg.sh +0 -161
  290. package/scripts/test-npm-package.ps1 +0 -252
  291. package/scripts/test-npm-package.sh +0 -207
  292. package/scripts/verify-install.ps1 +0 -294
  293. package/scripts/verify-install.sh +0 -266
  294. package/scripts/verify-v27.ps1 +0 -301
  295. package/scripts/verify-v27.sh +0 -233
  296. package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
  297. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  298. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  299. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  300. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  301. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -79,11 +79,11 @@ class BackendOrchestrator:
79
79
  # 1. Apply schema (if not already applied)
80
80
  self._apply_schema_v345()
81
81
 
82
- # 2. Initialize TierManager (always)
82
+ # 2. Initialize TierManager (always). Backends are refreshed after
83
+ # optional projections have been opened below.
83
84
  try:
84
- from superlocalmemory.core.tier_manager import evaluate_tiers, set_backends
85
+ from superlocalmemory.core.tier_manager import evaluate_tiers
85
86
  self._tiers = evaluate_tiers
86
- set_backends(cozo=self._cozo, lancedb=self._lancedb)
87
87
  logger.info("BackendOrchestrator: TierManager initialized")
88
88
  except Exception as exc:
89
89
  logger.warning("TierManager init failed (non-fatal): %s", exc)
@@ -97,6 +97,16 @@ class BackendOrchestrator:
97
97
  except Exception as exc:
98
98
  logger.warning("Initial rebalance failed (non-fatal): %s", exc)
99
99
 
100
+ # Backends may be installed with the product, but installing a wheel
101
+ # is not authorization to mutate an existing data root. Only a
102
+ # verified, explicit promotion may initialize and migrate projections.
103
+ if getattr(self._config, "scale_engine_state", "local_core") != "promoted":
104
+ logger.info(
105
+ "Scale Engine remains on Local Core (state=%s)",
106
+ getattr(self._config, "scale_engine_state", "local_core"),
107
+ )
108
+ return
109
+
100
110
  # 4. Initialize CozoDB if available
101
111
  cozo_available = self._detect_cozo()
102
112
  if cozo_available:
@@ -107,20 +117,18 @@ class BackendOrchestrator:
107
117
  if lancedb_available:
108
118
  self._init_lancedb()
109
119
 
110
- # 6. Auto-migrate
120
+ # A promoted stage is already parity-verified. Never rebuild it at
121
+ # startup: automatic migration would bypass the staged lifecycle and
122
+ # could make the active projection diverge from canonical SQLite.
111
123
  if self._cozo:
112
- status = self._cozo_status()
113
- if status in ("not_initialized", "migrating"):
114
- if status == "migrating":
115
- logger.warning("CozoDB migration interrupted — rebuilding")
116
- self._migrate_cozo()
117
-
124
+ self._update_status("cozo", "active", self._cozo.health_check().get("edges", 0))
118
125
  if self._lancedb:
119
- status = self._lancedb_status()
120
- if status in ("not_initialized", "migrating"):
121
- if status == "migrating":
122
- logger.warning("LanceDB migration interrupted — rebuilding")
123
- self._migrate_lancedb()
126
+ self._update_status("lancedb", "active", self._lancedb.health_check().get("vectors", 0))
127
+ try:
128
+ from superlocalmemory.core.tier_manager import set_backends
129
+ set_backends(cozo=self._cozo, lancedb=self._lancedb)
130
+ except Exception as exc:
131
+ logger.warning("TierManager backend registration failed (non-fatal): %s", exc)
124
132
 
125
133
  logger.info("BackendOrchestrator: daemon ready (cozo=%s, lancedb=%s)",
126
134
  "active" if self._cozo and self._cozo_status() == "active" else "off",
@@ -149,15 +157,38 @@ class BackendOrchestrator:
149
157
  self._sync_fact_embedding(fact)
150
158
 
151
159
  def _sync_fact_entities(self, fact: Any) -> None:
152
- """Sync fact's entities and edges to CozoDB."""
160
+ """Synchronize one fact's canonical entity bridge and fact edges."""
153
161
  try:
162
+ # Retrying ingestion must not retain stale fact/entity links.
163
+ self._cozo.remove_fact(fact.fact_id)
154
164
  entities = getattr(fact, "canonical_entities", []) or []
165
+ profile_id = getattr(fact, "profile_id", "default") or "default"
155
166
  for eid in entities:
156
- self._cozo.add_entity(eid, eid, "concept", {})
157
- # Add edges from this entity to existing ones
158
- for other in entities:
159
- if other != eid:
160
- self._cozo.add_edge(eid, other, "co_occurs", 1.0)
167
+ rows = self._db.execute(
168
+ "SELECT canonical_name, entity_type, fact_count FROM canonical_entities "
169
+ "WHERE entity_id = ? AND profile_id = ?",
170
+ (eid, profile_id),
171
+ )
172
+ if rows:
173
+ entity = dict(rows[0])
174
+ self._cozo.add_entity(
175
+ eid,
176
+ entity.get("canonical_name") or eid,
177
+ entity.get("entity_type") or "concept",
178
+ {"fact_count": int(entity.get("fact_count") or 0)},
179
+ profile_id,
180
+ )
181
+ self._cozo.add_fact_entities(fact.fact_id, entities, profile_id)
182
+ for row in self._db.execute(
183
+ "SELECT source_id, target_id, edge_type, weight FROM graph_edges "
184
+ "WHERE profile_id = ? AND (source_id = ? OR target_id = ?)",
185
+ (profile_id, fact.fact_id, fact.fact_id),
186
+ ):
187
+ edge = dict(row)
188
+ self._cozo.add_edge(
189
+ edge["source_id"], edge["target_id"], edge.get("edge_type") or "related",
190
+ float(edge.get("weight") or 1.0), profile_id=profile_id,
191
+ )
161
192
  except Exception as exc:
162
193
  logger.debug("CozoDB incremental sync skipped: %s", exc)
163
194
 
@@ -169,10 +200,30 @@ class BackendOrchestrator:
169
200
  tier = getattr(fact, "lifecycle", "active")
170
201
  self._lancedb.add_vectors(
171
202
  [fact.fact_id], [embedding], [tier],
203
+ getattr(fact, "profile_id", "default") or "default",
172
204
  )
173
205
  except Exception as exc:
174
206
  logger.debug("LanceDB incremental sync skipped: %s", exc)
175
207
 
208
+ def sync_deleted_fact(self, fact_id: str) -> None:
209
+ """Remove a fact from derived projections after canonical deletion."""
210
+ if self._cozo and self._cozo_status() == "active":
211
+ try:
212
+ self._cozo.remove_fact(fact_id)
213
+ except Exception as exc:
214
+ logger.warning("Cozo deletion sync failed for %s: %s", fact_id[:16], exc)
215
+ if self._lancedb and self._lancedb_status() == "active":
216
+ try:
217
+ self._lancedb.remove_vector(fact_id)
218
+ except Exception as exc:
219
+ logger.warning("Lance deletion sync failed for %s: %s", fact_id[:16], exc)
220
+
221
+ def sync_changed_fact(self, fact_id: str) -> None:
222
+ """Refresh projections after an authorized canonical fact update."""
223
+ fact = self._db.get_fact(fact_id)
224
+ if fact is not None:
225
+ self.sync_new_fact(fact)
226
+
176
227
  # ------------------------------------------------------------------
177
228
  # Backend Access
178
229
  # ------------------------------------------------------------------
@@ -189,6 +240,15 @@ class BackendOrchestrator:
189
240
  return self._lancedb
190
241
  return None
191
242
 
243
+ def graph_retrieval_ready(self) -> bool:
244
+ """Whether Cozo can be injected into entity recall.
245
+
246
+ Cozo carries both canonical entity mappings and fact graph edges. The
247
+ entity channel still shadows every projected result against SQLite and
248
+ fails closed on any mismatch, so availability never weakens recall.
249
+ """
250
+ return bool(self._cozo and self._cozo_status() == "active")
251
+
192
252
  # ------------------------------------------------------------------
193
253
  # Health Check
194
254
  # ------------------------------------------------------------------
@@ -13,13 +13,14 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
13
13
  from __future__ import annotations
14
14
 
15
15
  import logging
16
- from dataclasses import dataclass, field
16
+ from dataclasses import asdict, dataclass, field
17
17
  from pathlib import Path
18
18
 
19
- logger = logging.getLogger(__name__)
20
-
19
+ from superlocalmemory.infra.data_root import DynamicStatePath, canonical_data_root
21
20
  from superlocalmemory.storage.models import Mode
22
21
 
22
+ logger = logging.getLogger(__name__)
23
+
23
24
 
24
25
  # ---------------------------------------------------------------------------
25
26
  # Canonical limits (WP-02 — single source of truth across all surfaces)
@@ -35,7 +36,7 @@ CANONICAL_RECALL_LIMIT: int = 20
35
36
  # Default Paths
36
37
  # ---------------------------------------------------------------------------
37
38
 
38
- DEFAULT_BASE_DIR = Path.home() / ".superlocalmemory"
39
+ DEFAULT_BASE_DIR = DynamicStatePath()
39
40
  DEFAULT_DB_NAME = "memory.db"
40
41
  DEFAULT_PROFILES_FILE = "profiles.json"
41
42
  CURRENT_MODE_FILE = "current_mode"
@@ -43,6 +44,11 @@ CURRENT_MODE_FILE = "current_mode"
43
44
  _MODE_CONFIG_NAMES: dict | None = None
44
45
 
45
46
 
47
+ def _runtime_base_dir() -> Path:
48
+ """Resolve the process namespace when a config root is not explicit."""
49
+ return canonical_data_root()
50
+
51
+
46
52
  # ---------------------------------------------------------------------------
47
53
  # Embedding Config
48
54
  # ---------------------------------------------------------------------------
@@ -768,7 +774,7 @@ class SLMConfig:
768
774
  """
769
775
 
770
776
  mode: Mode = Mode.A
771
- base_dir: Path = DEFAULT_BASE_DIR
777
+ base_dir: Path = field(default_factory=_runtime_base_dir)
772
778
  db_path: Path | None = None # Computed from base_dir if None
773
779
  active_profile: str = "default"
774
780
 
@@ -801,6 +807,9 @@ class SLMConfig:
801
807
  # v3.5.0: scaling backends — "sqlite" / "cozo" / "auto" / "lancedb" / "sqlite-vec" / "auto".
802
808
  graph_backend: str = "auto" # "auto" = cozo if pycozo installed, else sqlite
803
809
  vector_backend: str = "auto" # "auto" = lancedb if installed, else sqlite-vec
810
+ # Scale Engine is installed separately from activation. Existing roots
811
+ # stay on SQLite until a staged parity check promotes these projections.
812
+ scale_engine_state: str = "local_core" # local_core | prepared | verified | promoted
804
813
  evolution: EvolutionConfig = field(default_factory=EvolutionConfig)
805
814
  health: HealthConfig = field(default_factory=HealthConfig)
806
815
 
@@ -824,16 +833,22 @@ class SLMConfig:
824
833
  @classmethod
825
834
  def load(cls, config_path: Path | None = None) -> SLMConfig:
826
835
  """Load config from JSON file. Returns default Mode A if file doesn't exist."""
827
- # WP-07: resolve base dir through slm_home() at call time so all 3 env
828
- # aliases (SLM_DATA_DIR SL_MEMORY_PATH SLM_HOME) are honoured.
836
+ # V3.7: process namespace selection is authoritative. A persisted
837
+ # base_dir may bootstrap a legacy custom root only when no environment
838
+ # alias selected the process namespace.
829
839
  try:
830
- from superlocalmemory.cli._lazy_init import slm_home as _slm_home
831
- _runtime_base = _slm_home()
840
+ from superlocalmemory.infra.data_root import (
841
+ canonical_data_root,
842
+ environment_data_root,
843
+ )
844
+ _environment_base = environment_data_root()
845
+ _runtime_base = canonical_data_root()
832
846
  except Exception:
847
+ _environment_base = None
833
848
  _runtime_base = DEFAULT_BASE_DIR
834
849
  path = config_path or (_runtime_base / "config.json")
835
850
  if not path.exists():
836
- return cls.for_mode(Mode.A)
851
+ return cls.for_mode(Mode.A, base_dir=_runtime_base)
837
852
  import json
838
853
  try:
839
854
  data = json.loads(path.read_text())
@@ -843,13 +858,21 @@ class SLMConfig:
843
858
  logger.warning(
844
859
  "config.json unreadable/corrupt (%s) — using Mode A default", exc
845
860
  )
846
- return cls.for_mode(Mode.A)
861
+ return cls.for_mode(Mode.A, base_dir=_runtime_base)
847
862
  mode = Mode(data.get("mode", "a"))
848
863
  llm_data = data.get("llm", {})
849
864
  emb_data = data.get("embedding", {})
850
865
  # V3.5.9: read base_dir before constructing config so for_mode() builds
851
866
  # db_path from the user's directory, not DEFAULT_BASE_DIR.
852
- raw_base_dir = Path(data.get("base_dir", str(_runtime_base)))
867
+ if _environment_base is not None:
868
+ raw_base_dir = _environment_base
869
+ else:
870
+ try:
871
+ raw_base_dir = canonical_data_root(
872
+ configured_base_dir=data.get("base_dir", _runtime_base),
873
+ )
874
+ except Exception:
875
+ raw_base_dir = _runtime_base
853
876
  config = cls.for_mode(
854
877
  mode,
855
878
  llm_provider=llm_data.get("provider", ""),
@@ -865,6 +888,13 @@ class SLMConfig:
865
888
  base_dir=raw_base_dir,
866
889
  )
867
890
  config.active_profile = data.get("active_profile", "default")
891
+ config.graph_backend = data.get("graph_backend", "auto")
892
+ config.vector_backend = data.get("vector_backend", "auto")
893
+ state = data.get("scale_engine_state", "local_core")
894
+ config.scale_engine_state = (
895
+ state if state in {"local_core", "prepared", "verified", "promoted"}
896
+ else "local_core"
897
+ )
868
898
 
869
899
  # V3.3 config fields (additive — defaults work if missing from JSON)
870
900
  fg = data.get("forgetting", {})
@@ -1011,6 +1041,9 @@ class SLMConfig:
1011
1041
  data = {
1012
1042
  "mode": effective_mode,
1013
1043
  "active_profile": self.active_profile,
1044
+ "graph_backend": self.graph_backend,
1045
+ "vector_backend": self.vector_backend,
1046
+ "scale_engine_state": self.scale_engine_state,
1014
1047
  "base_dir": str(self.base_dir), # V3.5.9: persist so load() can restore custom paths
1015
1048
  "llm": {
1016
1049
  "provider": self.llm.provider,
@@ -1026,11 +1059,10 @@ class SLMConfig:
1026
1059
  "api_key": self.embedding.api_key,
1027
1060
  "deployment_name": self.embedding.deployment_name,
1028
1061
  },
1029
- "retrieval": {
1030
- "use_cross_encoder": self.retrieval.use_cross_encoder,
1031
- "cross_encoder_model": self.retrieval.cross_encoder_model,
1032
- "cross_encoder_backend": self.retrieval.cross_encoder_backend,
1033
- },
1062
+ # Persist the complete retrieval contract. Saving only the
1063
+ # cross-encoder subset silently reset channel limits, evidence
1064
+ # floors, and agentic settings on the next mode/provider change.
1065
+ "retrieval": asdict(self.retrieval),
1034
1066
  }
1035
1067
 
1036
1068
  # V3.4.11: Persist evolution config (C-CONFIGSAVE fix)
@@ -1165,6 +1197,7 @@ class SLMConfig:
1165
1197
  base_dir=_base,
1166
1198
  embedding=_a_emb,
1167
1199
  llm=LLMConfig(), # No LLM
1200
+ temporal_validator=TemporalValidatorConfig(mode="a"),
1168
1201
  retrieval=RetrievalConfig(
1169
1202
  # V3.3.2: ONNX cross-encoder enabled for all modes (~200MB)
1170
1203
  use_cross_encoder=True,
@@ -1206,6 +1239,7 @@ class SLMConfig:
1206
1239
  api_base=llm_api_base or "http://localhost:11434",
1207
1240
  api_key=llm_api_key or "",
1208
1241
  ),
1242
+ temporal_validator=TemporalValidatorConfig(mode="b"),
1209
1243
  retrieval=RetrievalConfig(
1210
1244
  # V3.3.2: ONNX cross-encoder enabled for all modes (~200MB)
1211
1245
  use_cross_encoder=True,
@@ -1226,14 +1260,24 @@ class SLMConfig:
1226
1260
  api_endpoint=embedding_endpoint,
1227
1261
  api_key=embedding_key,
1228
1262
  )
1229
- else:
1263
+ elif embedding_endpoint:
1230
1264
  _c_emb = EmbeddingConfig(
1231
- model_name="text-embedding-3-large",
1232
- dimension=3072,
1265
+ model_name=embedding_model_name or "text-embedding-3-large",
1266
+ dimension=embedding_dimension or 3072,
1233
1267
  api_endpoint=embedding_endpoint,
1234
1268
  api_key=embedding_key,
1235
1269
  deployment_name=embedding_deployment,
1236
1270
  )
1271
+ else:
1272
+ # Mode C upgrades LLM capability; it must not silently require a
1273
+ # second paid embedding provider. A 3072-dim cloud embedding
1274
+ # contract without an endpoint/key was auto-routed to the local
1275
+ # 768-dim Ollama embedder, which made ingestion fail at vector
1276
+ # materialization. Cloud embeddings remain an explicit opt-in.
1277
+ _c_emb = EmbeddingConfig(
1278
+ model_name="nomic-ai/nomic-embed-text-v1.5",
1279
+ dimension=768,
1280
+ )
1237
1281
  return cls(
1238
1282
  mode=mode,
1239
1283
  base_dir=_base,
@@ -1244,6 +1288,7 @@ class SLMConfig:
1244
1288
  api_key=llm_api_key,
1245
1289
  api_base=llm_api_base,
1246
1290
  ),
1291
+ temporal_validator=TemporalValidatorConfig(mode="c"),
1247
1292
  channel_weights=ChannelWeights(
1248
1293
  semantic=1.5,
1249
1294
  bm25=1.2,
@@ -160,10 +160,9 @@ class ConsolidationEngine:
160
160
  def mine(self, *a, **kw): return []
161
161
  from superlocalmemory.hooks.auto_parameterize import AutoParameterizeHook
162
162
  from superlocalmemory.core.config import ParameterizationConfig
163
- from pathlib import Path as _Path
164
-
165
163
  p_config = getattr(self._slm_config, "parameterization", ParameterizationConfig())
166
- learning_db = str(_Path.home() / ".superlocalmemory" / "learning.db")
164
+ from superlocalmemory.infra.data_root import state_path
165
+ learning_db = str(state_path("learning.db"))
167
166
  beh_store = BehavioralPatternStore(learning_db)
168
167
  cross_proj = CrossProjectAggregator(self._db)
169
168
  wf_miner = WorkflowMiner(self._db)
@@ -483,10 +482,13 @@ class ConsolidationEngine:
483
482
  continue
484
483
 
485
484
  # Promote: active -> warm (lifecycle transition)
486
- self._db.execute(
487
- "UPDATE atomic_facts SET lifecycle = 'warm' "
488
- "WHERE fact_id = ? AND lifecycle = 'active'",
489
- (fact_id,),
485
+ from superlocalmemory.core.lifecycle_state import set_fact_lifecycle_zone
486
+ set_fact_lifecycle_zone(
487
+ self._db,
488
+ [fact_id],
489
+ "warm",
490
+ profile_id=profile_id,
491
+ from_atomic=("active",),
490
492
  )
491
493
  promoted += 1
492
494
 
@@ -27,6 +27,7 @@ import hashlib
27
27
  import hmac
28
28
  import json
29
29
  import os
30
+ import secrets
30
31
  import sqlite3
31
32
  import time
32
33
  from dataclasses import dataclass, field
@@ -38,10 +39,10 @@ from superlocalmemory.core.security_primitives import (
38
39
  redact_secrets,
39
40
  safe_resolve,
40
41
  )
42
+ from superlocalmemory.infra.data_root import DynamicStatePath, canonical_data_root
41
43
 
42
-
43
- CACHE_DB_DEFAULT: Path = Path.home() / ".superlocalmemory" / "active_brain_cache.db"
44
- INSTALL_TOKEN_DEFAULT: Path = Path.home() / ".superlocalmemory" / ".install_token"
44
+ CACHE_DB_DEFAULT = DynamicStatePath("active_brain_cache.db")
45
+ INSTALL_TOKEN_DEFAULT = DynamicStatePath(".install_token")
45
46
 
46
47
  TTL_SECONDS: int = 120
47
48
  CLEANUP_HORIZON_SECONDS: int = 600
@@ -90,6 +91,44 @@ def _read_install_token(home: Path) -> str | None:
90
91
  return token or None
91
92
 
92
93
 
94
+ def _ensure_install_token_at(home: Path) -> str:
95
+ """Create/read the binding token for an explicit cache namespace.
96
+
97
+ The shared security primitive owns the canonical namespace. This bounded
98
+ variant preserves ``home_dir`` as a real override without mutating global
99
+ path state or routing an explicit cache through the canonical token.
100
+ """
101
+ if home.resolve(strict=False) == canonical_data_root():
102
+ return ensure_install_token()
103
+
104
+ token_path = home / ".install_token"
105
+ try:
106
+ token = token_path.read_text(encoding="utf-8").strip()
107
+ except FileNotFoundError:
108
+ token = ""
109
+ if token:
110
+ return token
111
+
112
+ token = secrets.token_hex(32)
113
+ flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
114
+ if hasattr(os, "O_NOFOLLOW"):
115
+ flags |= os.O_NOFOLLOW
116
+ try:
117
+ fd = os.open(str(token_path), flags, 0o600)
118
+ try:
119
+ os.write(fd, token.encode("utf-8"))
120
+ finally:
121
+ os.close(fd)
122
+ except FileExistsError:
123
+ existing = token_path.read_text(encoding="utf-8").strip()
124
+ if existing:
125
+ return existing
126
+ raise RuntimeError(f"install token is empty: {token_path}")
127
+ if os.name != "nt":
128
+ os.chmod(token_path, 0o600)
129
+ return token
130
+
131
+
93
132
  # ---------------------------------------------------------------------------
94
133
  # Writer (daemon-side)
95
134
  # ---------------------------------------------------------------------------
@@ -108,10 +147,14 @@ class ContextCache:
108
147
  db_path: Path | None = None,
109
148
  home_dir: Path | None = None,
110
149
  ) -> None:
111
- self._home = home_dir or (Path.home() / ".superlocalmemory")
150
+ self._home = Path(home_dir) if home_dir is not None else canonical_data_root()
112
151
  self._home.mkdir(parents=True, exist_ok=True)
113
152
 
114
- raw = db_path or (self._home / "active_brain_cache.db")
153
+ raw = db_path or (
154
+ Path(CACHE_DB_DEFAULT)
155
+ if home_dir is None
156
+ else self._home / "active_brain_cache.db"
157
+ )
115
158
  self._db_path = safe_resolve(self._home, Path(raw).name) \
116
159
  if Path(raw).parent == self._home else \
117
160
  safe_resolve(self._home, raw)
@@ -181,7 +224,7 @@ class ContextCache:
181
224
  );
182
225
  """
183
226
  )
184
- token = ensure_install_token()
227
+ token = _ensure_install_token_at(self._home)
185
228
  now = int(time.time())
186
229
  self._write_conn.execute(
187
230
  "INSERT OR IGNORE INTO slm_meta (key, value, created_at) "
@@ -313,12 +356,17 @@ def read_entry_fast(
313
356
  - stdlib-only — no heavy imports, no daemon HTTP call.
314
357
  """
315
358
  try:
316
- home = home_dir or (Path.home() / ".superlocalmemory")
359
+ home = Path(home_dir) if home_dir is not None else canonical_data_root()
317
360
  if not home.exists():
318
361
  return None
319
362
 
320
363
  requested = db_path or Path(
321
- os.environ.get("SLM_CACHE_DB") or (home / "active_brain_cache.db"),
364
+ os.environ.get("SLM_CACHE_DB")
365
+ or (
366
+ Path(CACHE_DB_DEFAULT)
367
+ if home_dir is None
368
+ else home / "active_brain_cache.db"
369
+ ),
322
370
  )
323
371
  try:
324
372
  resolved = safe_resolve(home, Path(requested).resolve())