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
@@ -177,6 +177,34 @@ def _get_forgetting_scheduler(db: Any, config: Any) -> Any:
177
177
  return _forgetting_scheduler_cache[key]
178
178
 
179
179
 
180
+ def release_recall_resources(db: Any) -> None:
181
+ """Release process-level recall singletons owned by a closing database."""
182
+ key = id(db)
183
+ _behavioral_tracker_cache.pop(key, None)
184
+ _forgetting_scheduler_cache.pop(key, None)
185
+
186
+
187
+ def _behavioral_entities(results: list[Any], limit: int = 20) -> list[str]:
188
+ """Read canonical entity IDs from the typed retrieval-result contract.
189
+
190
+ ``RetrievalResult`` is a dataclass whose entity evidence lives on
191
+ ``result.fact.canonical_entities``. Treating it as a SQLite mapping silently
192
+ discarded every entity and disabled behavioral entity learning.
193
+ """
194
+ entities: list[str] = []
195
+ seen: set[str] = set()
196
+ for result in results:
197
+ fact = getattr(result, "fact", None)
198
+ for entity_id in getattr(fact, "canonical_entities", ()) or ():
199
+ if not isinstance(entity_id, str) or not entity_id or entity_id in seen:
200
+ continue
201
+ seen.add(entity_id)
202
+ entities.append(entity_id)
203
+ if len(entities) >= limit:
204
+ return entities
205
+ return entities
206
+
207
+
180
208
  # ---------------------------------------------------------------------------
181
209
  # S8-ARC-04 (v3.4.22): unified ranking entry point.
182
210
  # ---------------------------------------------------------------------------
@@ -268,10 +296,10 @@ def apply_adaptive_ranking(
268
296
  Phase 2 (50+): heuristic boosts from recency, access count, trust.
269
297
  Phase 3 (200+): LightGBM ML-based reranking.
270
298
  """
299
+ from superlocalmemory.infra.data_root import state_path
271
300
  from superlocalmemory.learning.feedback import FeedbackCollector
272
- from pathlib import Path
273
301
 
274
- learning_db = Path.home() / ".superlocalmemory" / "learning.db"
302
+ learning_db = state_path("learning.db")
275
303
  if not learning_db.exists():
276
304
  return response
277
305
 
@@ -284,7 +312,8 @@ def apply_adaptive_ranking(
284
312
  from superlocalmemory.learning.ranker import AdaptiveRanker
285
313
  ranker = AdaptiveRanker(signal_count=signal_count)
286
314
 
287
- from datetime import UTC, datetime as _dt
315
+ from datetime import UTC
316
+ from datetime import datetime as _dt
288
317
  _now = _dt.now(UTC)
289
318
 
290
319
  result_dicts = []
@@ -314,6 +343,10 @@ def apply_adaptive_ranking(
314
343
  reranked = ranker.rerank(result_dicts, query_context)
315
344
 
316
345
  # Rebuild response with new ordering
346
+ for item in reranked:
347
+ original = item.get("_original")
348
+ if original is not None and item.get("_adaptive_score") is not None:
349
+ original.ranking_score = float(item["_adaptive_score"])
317
350
  new_results = [d["_original"] for d in reranked]
318
351
 
319
352
  return RecallResponse(
@@ -354,17 +387,13 @@ def apply_v2_adaptive_ranking(
354
387
  try:
355
388
  from pathlib import Path as _P
356
389
 
390
+ from superlocalmemory.infra.data_root import state_path
357
391
  from superlocalmemory.learning.database import LearningDatabase
358
392
  from superlocalmemory.learning.model_cache import load_active
359
393
  from superlocalmemory.learning.ranker import AdaptiveRanker
360
- from superlocalmemory.learning.signals import (
361
- SignalBatch,
362
- SignalCandidate,
363
- enqueue,
364
- )
365
394
 
366
395
  db_path = (_P(learning_db_path) if learning_db_path
367
- else _P.home() / ".superlocalmemory" / "learning.db")
396
+ else state_path("learning.db"))
368
397
  if not db_path.exists():
369
398
  return response
370
399
 
@@ -378,7 +407,8 @@ def apply_v2_adaptive_ranking(
378
407
  )
379
408
 
380
409
  # Build result-dict shape expected by the ranker's rerank() path.
381
- from datetime import UTC, datetime as _dt
410
+ from datetime import UTC
411
+ from datetime import datetime as _dt
382
412
  _now_v2 = _dt.now(UTC)
383
413
 
384
414
  result_dicts: list[dict] = []
@@ -410,6 +440,10 @@ def apply_v2_adaptive_ranking(
410
440
  "profile_id": profile_id,
411
441
  }
412
442
  reranked_dicts = ranker.rerank(result_dicts, query_context)
443
+ for item in reranked_dicts:
444
+ original = item.get("_original")
445
+ if original is not None and item.get("_adaptive_score") is not None:
446
+ original.ranking_score = float(item["_adaptive_score"])
413
447
  new_results = [d["_original"] for d in reranked_dicts
414
448
  if "_original" in d]
415
449
 
@@ -463,9 +497,9 @@ def apply_v2_bandit_ensemble(
463
497
  return response
464
498
 
465
499
  try:
466
- from datetime import datetime as _dt
467
500
  from pathlib import Path as _P
468
501
 
502
+ from superlocalmemory.infra.data_root import state_path
469
503
  from superlocalmemory.learning.bandit import ContextualBandit
470
504
  from superlocalmemory.learning.ensemble import (
471
505
  choose_ensemble,
@@ -479,7 +513,7 @@ def apply_v2_bandit_ensemble(
479
513
  from superlocalmemory.retrieval.engine import apply_channel_weights
480
514
 
481
515
  db_path = (_P(learning_db_path) if learning_db_path
482
- else _P.home() / ".superlocalmemory" / "learning.db")
516
+ else state_path("learning.db"))
483
517
  if not db_path.exists():
484
518
  return response
485
519
 
@@ -597,9 +631,8 @@ def run_recall(
597
631
 
598
632
  Pipeline: retrieval -> agentic sufficiency (if configured) -> post-recall updates.
599
633
 
600
- V3.4.40: ``fast=True`` adds spreading_activation to the per-recall
601
- extra_disabled_channels set, skipping the 5th channel for sub-second
602
- response.
634
+ ``fast=True`` skips spreading activation and remote agentic verification,
635
+ retaining the bounded single-pass retrieval channels.
603
636
  """
604
637
  # Pre-operation hooks
605
638
  hook_ctx = {
@@ -639,7 +672,7 @@ def run_recall(
639
672
  # Single-hop/factual/temporal queries get WORSE with decomposition —
640
673
  # sub-query noise dilutes precision. Mode C (LLM) can trigger broadly.
641
674
  agentic_rounds = config.retrieval.agentic_max_rounds
642
- if agentic_rounds > 0 and response.results:
675
+ if not fast and agentic_rounds > 0 and response.results:
643
676
  max_score = max((r.score for r in response.results), default=0.0)
644
677
  has_llm = llm is not None and getattr(llm, "is_available", False)
645
678
  should_trigger = (
@@ -679,6 +712,9 @@ def run_recall(
679
712
  fact=f, score=1.0 / (i + 1),
680
713
  channel_scores={"agentic": 1.0},
681
714
  confidence=f.confidence,
715
+ relevance_score=1.0 / (i + 1),
716
+ ranking_score=None,
717
+ memory_confidence=f.confidence,
682
718
  evidence_chain=["agentic_round_2"],
683
719
  trust_score=fact_trust,
684
720
  ))
@@ -708,58 +744,18 @@ def run_recall(
708
744
  except Exception as exc:
709
745
  logger.debug("Access log batch store failed: %s", exc)
710
746
 
711
- # V3.4.11: Promote recalled facts back to active tier (single batch UPDATE)
712
- if response.results:
713
- try:
714
- from superlocalmemory.core.tier_manager import promote_on_access_batch
715
- fact_ids = [r.fact.fact_id for r in response.results[:10]]
716
- promote_on_access_batch(db, fact_ids)
717
- except Exception:
718
- pass # tier_manager not available yet — graceful
719
-
720
- # V3.3.16: Behavioral tracking + spaced repetition use module-level
721
- # singletons to avoid creating new objects per recall (was causing
722
- # object accumulation across 304 benchmark recalls).
747
+ # Query telemetry is permitted, but result-derived entities are not fed
748
+ # back as preferences. Retrieval exposure is not positive feedback.
723
749
  try:
724
- # v3.4.7: Extract entities from results for behavioral tracking.
725
- # Was passing wrong param (result_count instead of entities) → TypeError.
726
- entities: list[str] = []
727
- for r in response.results[:10]:
728
- rd = r if isinstance(r, dict) else (dict(r) if hasattr(r, "keys") else {})
729
- ents_json = rd.get("canonical_entities_json", "")
730
- if ents_json:
731
- try:
732
- import json as _json
733
- entities.extend(_json.loads(ents_json))
734
- except (ValueError, TypeError):
735
- pass
736
750
  _get_behavioral_tracker(db).record_query(
737
751
  query=query,
738
752
  query_type=response.query_type,
739
- entities=entities[:20],
753
+ entities=[],
740
754
  profile_id=profile_id,
741
755
  )
742
756
  except Exception as exc:
743
757
  logger.debug("Behavioral tracking: %s", exc)
744
758
 
745
- if response.results:
746
- try:
747
- fsched = _get_forgetting_scheduler(db, config)
748
- for r in response.results[:10]:
749
- fsched.on_access_event(r.fact.fact_id, profile_id)
750
- except Exception as exc:
751
- logger.debug("Spaced repetition update: %s", exc)
752
-
753
- # Phase 3: Hebbian strengthening for co-accessed facts
754
- if auto_linker and response.results:
755
- try:
756
- recalled_ids = [
757
- r.fact.fact_id for r in response.results[:10]
758
- ]
759
- auto_linker.strengthen_co_access(recalled_ids, profile_id)
760
- except Exception as exc:
761
- logger.debug("Hebbian strengthening: %s", exc)
762
-
763
759
  # S8-ARC-04 (v3.4.22): unified ranking entry point. Single env-var
764
760
  # (SLM_RANKING=off|v1|v2|v2-ensemble) controls the pipeline. Legacy
765
761
  # SLM_V2_PIPELINE_DISABLED + SLM_BANDIT_DISABLED still honoured for
@@ -777,54 +773,17 @@ def run_recall(
777
773
  logger.debug("Ranking pipeline skipped: %s", exc)
778
774
 
779
775
  _mark("learning+ranking")
780
- # Reconsolidation: access updates trust + count (neuroscience principle)
781
- if trust_scorer:
782
- for r in response.results:
783
- trust_scorer.update_on_access("fact", r.fact.fact_id, profile_id)
784
-
785
- # Fisher Bayesian update on recall — narrows variance on accessed facts
786
- # so they score higher on subsequent recalls (critical for benchmark: +24pp).
787
- # V3.3.16: Reuse query embedding from retrieval engine cache instead of
788
- # calling embedder.embed() again (which was the memory leak source).
789
- q_var_arr = None
790
- if embedder and hasattr(retrieval_engine, '_query_embedding_cache'):
791
- cached_emb = retrieval_engine._query_embedding_cache.get(query)
792
- if cached_emb is not None:
793
- import numpy as _np
794
- _, q_var_list = embedder.compute_fisher_params(cached_emb)
795
- q_var_arr = _np.array(q_var_list, dtype=_np.float64)
796
-
797
- for r in response.results:
798
- updates: dict[str, object] = {
799
- "access_count": r.fact.access_count + 1,
800
- }
801
- if (q_var_arr is not None
802
- and r.fact.fisher_variance
803
- and len(r.fact.fisher_variance) == len(q_var_arr)
804
- and r.fact.access_count >= 3):
805
- import numpy as _np
806
- f_var = _np.array(r.fact.fisher_variance, dtype=_np.float64)
807
- new_var = 1.0 / (1.0 / _np.maximum(f_var, 0.05) + 1.0 / _np.maximum(q_var_arr, 0.05))
808
- new_var = _np.clip(new_var, 0.05, 2.0)
809
- updates["fisher_variance"] = new_var.tolist()
810
- db.update_fact(r.fact.fact_id, updates)
776
+ # Deliberately no trust, Fisher, retention, lifecycle, popularity, or graph
777
+ # mutation here. Those state transitions require a separately authenticated
778
+ # positive/negative outcome; merely returning a result is an exposure.
811
779
 
812
780
  # Post-operation hooks (audit, trust signal, learning)
813
781
  hook_ctx["result_count"] = len(response.results)
814
782
  hook_ctx["query_type"] = response.query_type
815
783
  hooks.run_post("recall", hook_ctx)
816
784
 
817
- # v3.5.0 (M2): soft-normalize ALL scores to [0,1] after the full pipeline.
818
- # The retrieval engine _build_results normalizes, but the ranking pipeline
819
- # (v1/v2/bandit-ensemble) re-weights and may produce scores outside [0,1].
820
- # This final sigmoid catches every path (MCP/CLI/Dashboard) and is monotonic.
821
- if response.results:
822
- import math as _mn
823
- max_s = max((r.score for r in response.results), default=1.0)
824
- scale = 2.0 / max(1.0, max_s)
825
- for r in response.results:
826
- r.score = round(1.0 / (1.0 + _mn.exp(-r.score * scale)), 4)
827
- r.confidence = min(1.0, r.score * 2.0)
785
+ from superlocalmemory.core.score_contract import finalize_score_contract
786
+ finalize_score_contract(response)
828
787
 
829
788
  # LLD-00 §3 — stamp HMAC markers on every result so post_tool_outcome_hook
830
789
  # can validate fact_ids observed in downstream tool output.
@@ -78,7 +78,10 @@ def _handle_recall(
78
78
 
79
79
  # v3.6.6: same shared chokepoint as the daemon HTTP route + CLI fallback,
80
80
  # so the MCP WorkerPool subprocess path returns identical budgeted output.
81
- from superlocalmemory.server.recall_serializer import serialize_recall_response
81
+ from superlocalmemory.server.recall_serializer import (
82
+ recall_response_metadata,
83
+ serialize_recall_response,
84
+ )
82
85
  _rc = getattr(engine._config, "retrieval", None)
83
86
  results, no_confident_match = serialize_recall_response(
84
87
  response,
@@ -99,13 +102,43 @@ def _handle_recall(
99
102
  "total_candidates": getattr(response, "total_candidates", 0),
100
103
  "results": results,
101
104
  "no_confident_match": no_confident_match,
105
+ **recall_response_metadata(response),
102
106
  }
103
107
 
104
108
 
105
109
  def _handle_store(content: str, metadata: dict) -> dict:
106
110
  engine = _get_engine()
107
- session_id = metadata.pop("session_id", "")
108
- fact_ids = engine.store(content, session_id=session_id, metadata=metadata)
111
+ values = dict(metadata or {})
112
+ session_id = str(values.pop("session_id", "") or "")
113
+ scope = str(values.pop("scope", "personal") or "personal")
114
+ shared_with = list(values.pop("shared_with", []) or [])
115
+ idempotency_key = str(values.pop("idempotency_key", "") or "")
116
+ from superlocalmemory.core.engine_ingestion import (
117
+ canonical_store,
118
+ local_trusted_actor_id,
119
+ )
120
+ operation = canonical_store(
121
+ engine,
122
+ content,
123
+ source_type="mcp-offline-worker",
124
+ trusted_actor_id=local_trusted_actor_id("recall-worker"),
125
+ metadata=values,
126
+ scope=scope,
127
+ shared_with=shared_with,
128
+ session_id=session_id,
129
+ idempotency_key=idempotency_key,
130
+ return_receipt=True,
131
+ )
132
+ if hasattr(operation, "fact_ids"):
133
+ fact_ids = list(operation.fact_ids)
134
+ operation_id = getattr(operation, "operation_id", None)
135
+ materialization_state = getattr(
136
+ getattr(operation, "state", None), "value", "complete"
137
+ )
138
+ else:
139
+ fact_ids = list(operation)
140
+ operation_id = None
141
+ materialization_state = "complete"
109
142
 
110
143
  # Generate and persist summary immediately after store (Mode A heuristic, B/C LLM)
111
144
  if fact_ids:
@@ -125,7 +158,15 @@ def _handle_store(content: str, metadata: dict) -> dict:
125
158
  except Exception:
126
159
  pass # Summary is non-critical
127
160
 
128
- return {"ok": True, "fact_ids": fact_ids, "count": len(fact_ids)}
161
+ pending = materialization_state != "complete"
162
+ return {
163
+ "ok": True,
164
+ "fact_ids": fact_ids,
165
+ "count": len(fact_ids),
166
+ "operation_id": operation_id,
167
+ "pending_id": operation_id if pending else None,
168
+ "materialization_state": materialization_state,
169
+ }
129
170
 
130
171
 
131
172
  def _handle_get_memory_facts(memory_id: str) -> dict:
@@ -154,65 +195,40 @@ def _handle_get_memory_facts(memory_id: str) -> dict:
154
195
  }
155
196
 
156
197
 
157
- def _handle_delete_memory(fact_id: str, agent_id: str = "system") -> dict:
158
- """Delete a specific atomic fact by ID with audit logging."""
198
+ def _handle_delete_memory(
199
+ fact_id: str,
200
+ source_agent_id: str = "system",
201
+ ) -> dict:
202
+ """Delete a fact after capability-derived authorization."""
159
203
  engine = _get_engine()
160
- pid = engine.profile_id
161
- rows = engine._db.execute(
162
- "SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ? LIMIT 1",
163
- (fact_id, pid),
204
+ from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
205
+ from superlocalmemory.core.mutations import delete_fact_authorized
206
+
207
+ return delete_fact_authorized(
208
+ engine,
209
+ fact_id,
210
+ trusted_actor_id=local_trusted_actor_id("recall-worker"),
211
+ source_agent_id=source_agent_id,
164
212
  )
165
- if not rows:
166
- return {"ok": False, "error": f"Memory {fact_id} not found"}
167
- content_preview = dict(rows[0]).get("content", "")[:80]
168
- engine._db.delete_fact(fact_id)
169
- # Audit log
170
- import logging as _logging
171
- _logging.getLogger("superlocalmemory.audit").info(
172
- "DELETE fact_id=%s by agent=%s content=%s", fact_id[:16], agent_id, content_preview,
173
- )
174
- return {"ok": True, "deleted": fact_id, "content_preview": content_preview}
175
213
 
176
214
 
177
- def _handle_update_memory(fact_id: str, content: str, agent_id: str = "system") -> dict:
178
- """Update content of a specific atomic fact with audit logging."""
215
+ def _handle_update_memory(
216
+ fact_id: str,
217
+ content: str,
218
+ source_agent_id: str = "system",
219
+ ) -> dict:
220
+ """Update a fact after capability-derived authorization."""
179
221
  engine = _get_engine()
180
- pid = engine.profile_id
181
- rows = engine._db.execute(
182
- "SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ? LIMIT 1",
183
- (fact_id, pid),
184
- )
185
- if not rows:
186
- return {"ok": False, "error": f"Memory {fact_id} not found"}
187
- old_content = dict(rows[0]).get("content", "")[:80]
188
- # V3.3.12: Re-embed updated content so semantic search + BM25 stay consistent.
189
- # Previously only the text column was updated, leaving stale embeddings.
190
- updates: dict = {"content": content}
191
- if engine._embedder:
192
- try:
193
- new_emb = engine._embedder.embed(content)
194
- if new_emb:
195
- updates["embedding"] = new_emb
196
- fm, fv = engine._embedder.compute_fisher_params(new_emb)
197
- updates["fisher_mean"] = fm
198
- updates["fisher_variance"] = fv
199
- except Exception:
200
- pass
201
- engine._db.update_fact(fact_id, updates)
202
- # Update BM25 index for the new content
203
- if hasattr(engine, '_retrieval_engine') and engine._retrieval_engine:
204
- bm25 = getattr(engine._retrieval_engine, '_bm25', None)
205
- if bm25:
206
- try:
207
- bm25.add(fact_id, content, pid)
208
- except Exception:
209
- pass
210
- import logging as _logging
211
- _logging.getLogger("superlocalmemory.audit").info(
212
- "UPDATE fact_id=%s by agent=%s old=%s new=%s",
213
- fact_id[:16], agent_id, old_content, content[:80],
222
+ from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
223
+ from superlocalmemory.core.mutations import update_fact_authorized
224
+
225
+ return update_fact_authorized(
226
+ engine,
227
+ fact_id,
228
+ content,
229
+ trusted_actor_id=local_trusted_actor_id("recall-worker"),
230
+ source_agent_id=source_agent_id,
214
231
  )
215
- return {"ok": True, "fact_id": fact_id, "content": content}
216
232
 
217
233
 
218
234
  def _handle_summarize(texts: list[str], mode: str) -> dict:
@@ -299,14 +315,15 @@ def _worker_main() -> None:
299
315
  _respond(result)
300
316
  elif cmd == "delete_memory":
301
317
  result = _handle_delete_memory(
302
- req.get("fact_id", ""), req.get("agent_id", "system"),
318
+ req.get("fact_id", ""),
319
+ req.get("source_agent_id", req.get("agent_id", "system")),
303
320
  )
304
321
  _respond(result)
305
322
  elif cmd == "update_memory":
306
323
  result = _handle_update_memory(
307
324
  req.get("fact_id", ""),
308
325
  req.get("content", ""),
309
- req.get("agent_id", "system"),
326
+ req.get("source_agent_id", req.get("agent_id", "system")),
310
327
  )
311
328
  _respond(result)
312
329
  elif cmd == "get_memory_facts":
@@ -10,7 +10,7 @@ via an advisory lock mechanism. Persisted as JSON for
10
10
  survival across process restarts.
11
11
 
12
12
  Part of Qualixar | Author: Varun Pratap Bhardwaj
13
- License: Elastic-2.0
13
+ License: AGPL-3.0-or-later
14
14
  """
15
15
 
16
16
  from __future__ import annotations