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
@@ -29,11 +29,21 @@ from typing import Iterator
29
29
 
30
30
  import psutil
31
31
 
32
-
33
- RAM_LOCK_PATH: Path = Path.home() / ".superlocalmemory" / "ram_lock.sem"
32
+ # Public override retained for tests and embedders. ``None`` means resolve the
33
+ # lock dynamically so a long-lived process can switch canonical namespaces
34
+ # without retaining the first root it observed.
35
+ RAM_LOCK_PATH: Path | None = None
34
36
  MIN_FREE_MB: int = 400
35
37
 
36
38
 
39
+ def _ram_lock_path() -> Path:
40
+ if RAM_LOCK_PATH is not None:
41
+ return RAM_LOCK_PATH
42
+ from superlocalmemory.infra.data_root import state_path
43
+
44
+ return state_path("ram_lock.sem")
45
+
46
+
37
47
  @contextmanager
38
48
  def ram_reservation(
39
49
  name: str,
@@ -72,15 +82,16 @@ def ram_reservation(
72
82
  f"{floor_mb}MB"
73
83
  )
74
84
 
75
- RAM_LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)
85
+ lock_path = _ram_lock_path()
86
+ lock_path.parent.mkdir(parents=True, exist_ok=True)
76
87
  # SEC-M6 — tighten the parent dir so the audit marker (``{pid}:{name}``)
77
88
  # is not readable by other UIDs on shared hosts. Idempotent; POSIX-only.
78
89
  try:
79
90
  if os.name == "posix":
80
- os.chmod(RAM_LOCK_PATH.parent, 0o700)
91
+ os.chmod(lock_path.parent, 0o700)
81
92
  except OSError: # pragma: no cover — perms race
82
93
  pass
83
- fd = os.open(str(RAM_LOCK_PATH), os.O_CREAT | os.O_RDWR, 0o600)
94
+ fd = os.open(str(lock_path), os.O_CREAT | os.O_RDWR, 0o600)
84
95
  try:
85
96
  deadline = time.time() + timeout_s
86
97
  while True:
@@ -97,7 +97,7 @@ class LayeredRateLimiter:
97
97
  for k in [p for p, e in self._per_pid.items() if e.last_used < cutoff]:
98
98
  del self._per_pid[k]
99
99
  for k in [a for a, e in self._per_agent.items() if e.last_used < cutoff]:
100
- del self._per_agent[a]
100
+ del self._per_agent[k]
101
101
 
102
102
  def _get_pid(self, pid: int, now: float) -> TokenBucket:
103
103
  entry = self._per_pid.get(pid)
@@ -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