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
@@ -17,15 +17,14 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
17
17
  from __future__ import annotations
18
18
 
19
19
  import logging
20
- from pathlib import Path
21
20
  from typing import Callable
22
21
 
23
22
  from mcp.types import ToolAnnotations
24
23
 
25
24
  logger = logging.getLogger(__name__)
26
25
 
27
- MEMORY_DIR = Path.home() / ".superlocalmemory"
28
- DB_PATH = MEMORY_DIR / "memory.db"
26
+ from superlocalmemory.infra.data_root import state_path
27
+ from superlocalmemory.mcp.shared import authorize_mcp_mutation
29
28
 
30
29
 
31
30
  def _try_daemon_post(path: str, body: dict, timeout_s: float = 60.0) -> dict | None:
@@ -62,7 +61,7 @@ def _emit_event(event_type: str, payload: dict | None = None,
62
61
  """Emit an event to the EventBus (best-effort, never raises)."""
63
62
  try:
64
63
  from superlocalmemory.infra.event_bus import EventBus
65
- bus = EventBus.get_instance(str(DB_PATH))
64
+ bus = EventBus.get_instance(str(state_path("memory.db")))
66
65
  bus.emit(event_type, payload=payload, source_agent=source_agent,
67
66
  source_protocol="mcp")
68
67
  except Exception:
@@ -121,14 +120,22 @@ def register_v33_tools(server, get_engine: Callable) -> None:
121
120
  total += int(r["cnt"])
122
121
  result = {"total": total, "transitions": 0, "dry_run_zones": zones}
123
122
  else:
123
+ authorization = authorize_mcp_mutation(
124
+ engine,
125
+ "delete",
126
+ mutation_source="mcp-forgetting-cycle",
127
+ profile_id=pid,
128
+ )
124
129
  result = scheduler.run_decay_cycle(pid, force=True)
130
+ authorization.complete()
125
131
 
126
- _emit_event("forgetting.cycle_complete", {
127
- "profile_id": pid,
128
- "dry_run": dry_run,
129
- "total": result.get("total", 0),
130
- "transitions": result.get("transitions", 0),
131
- })
132
+ if not dry_run:
133
+ _emit_event("forgetting.cycle_complete", {
134
+ "profile_id": pid,
135
+ "dry_run": False,
136
+ "total": result.get("total", 0),
137
+ "transitions": result.get("transitions", 0),
138
+ })
132
139
 
133
140
  return {"success": True, "dry_run": dry_run, **result}
134
141
 
@@ -183,15 +190,23 @@ def register_v33_tools(server, get_engine: Callable) -> None:
183
190
  facts = engine._db.get_all_facts(pid)
184
191
  result = {"total": len(facts), "would_quantize": 0, "dry_run": True}
185
192
  else:
193
+ authorization = authorize_mcp_mutation(
194
+ engine,
195
+ "update",
196
+ mutation_source="mcp-quantization-cycle",
197
+ profile_id=pid,
198
+ )
186
199
  result = scheduler.run_eap_cycle(pid)
200
+ authorization.complete()
187
201
 
188
- _emit_event("eap.cycle_complete", {
189
- "profile_id": pid,
190
- "dry_run": dry_run,
191
- "total": result.get("total", 0),
192
- "downgrades": result.get("downgrades", 0),
193
- "upgrades": result.get("upgrades", 0),
194
- })
202
+ if not dry_run:
203
+ _emit_event("eap.cycle_complete", {
204
+ "profile_id": pid,
205
+ "dry_run": False,
206
+ "total": result.get("total", 0),
207
+ "downgrades": result.get("downgrades", 0),
208
+ "upgrades": result.get("upgrades", 0),
209
+ })
195
210
 
196
211
  return {"success": True, "dry_run": dry_run, **result}
197
212
 
@@ -245,8 +260,15 @@ def register_v33_tools(server, get_engine: Callable) -> None:
245
260
  CognitiveConsolidator,
246
261
  )
247
262
 
263
+ authorization = authorize_mcp_mutation(
264
+ engine,
265
+ "update",
266
+ mutation_source="mcp-cognitive-consolidation",
267
+ profile_id=pid,
268
+ )
248
269
  consolidator = CognitiveConsolidator(db=engine._db)
249
270
  result = consolidator.run_pipeline(pid)
271
+ authorization.complete()
250
272
 
251
273
  _emit_event("ccq.consolidation_complete", {
252
274
  "profile_id": pid,
@@ -338,6 +360,14 @@ def register_v33_tools(server, get_engine: Callable) -> None:
338
360
  dry_run: If True, report orphans but don't kill them.
339
361
  """
340
362
  try:
363
+ engine = get_engine()
364
+ authorization = None
365
+ if not dry_run:
366
+ authorization = authorize_mcp_mutation(
367
+ engine,
368
+ "delete",
369
+ mutation_source="mcp-process-reaper",
370
+ )
341
371
  from superlocalmemory.infra.process_reaper import (
342
372
  cleanup_all_orphans,
343
373
  ReaperConfig,
@@ -345,6 +375,8 @@ def register_v33_tools(server, get_engine: Callable) -> None:
345
375
 
346
376
  config = ReaperConfig()
347
377
  result = cleanup_all_orphans(config, dry_run=dry_run)
378
+ if authorization is not None:
379
+ authorization.complete()
348
380
 
349
381
  return {
350
382
  "success": True,
@@ -446,6 +478,12 @@ def register_v33_tools(server, get_engine: Callable) -> None:
446
478
  daemon_result["via"] = "daemon"
447
479
  return daemon_result
448
480
 
481
+ authorization = authorize_mcp_mutation(
482
+ engine,
483
+ "update",
484
+ mutation_source="mcp-maintenance-cycle",
485
+ profile_id=pid,
486
+ )
449
487
  results = {}
450
488
 
451
489
  # 1. Langevin dynamics step (lifecycle evolution)
@@ -476,6 +514,7 @@ def register_v33_tools(server, get_engine: Callable) -> None:
476
514
  except Exception as exc:
477
515
  results["behavioral"] = {"error": str(exc)}
478
516
 
517
+ authorization.complete()
479
518
  return {"success": True, "profile": pid, **results}
480
519
 
481
520
  except Exception as exc:
@@ -301,6 +301,7 @@ class MeshBroker:
301
301
  "FROM mesh_messages m "
302
302
  "LEFT JOIN mesh_reads r ON m.id = r.message_id AND r.peer_id = ? "
303
303
  "WHERE m.target_type='broadcast' AND m.from_peer != ? "
304
+ "AND r.peer_id IS NULL "
304
305
  "AND (m.expires_at IS NULL OR m.expires_at > ?) "
305
306
  "ORDER BY m.created_at DESC LIMIT 50",
306
307
  (peer_id, peer_id, now),
@@ -316,6 +317,7 @@ class MeshBroker:
316
317
  "FROM mesh_messages m "
317
318
  "LEFT JOIN mesh_reads r ON m.id = r.message_id AND r.peer_id = ? "
318
319
  "WHERE m.target_type='project' AND m.project_path=? AND m.from_peer != ? "
320
+ "AND r.peer_id IS NULL "
319
321
  "AND (m.expires_at IS NULL OR m.expires_at > ?) "
320
322
  "ORDER BY m.created_at DESC LIMIT 50",
321
323
  (peer_id, project_path, peer_id, now),
@@ -21,12 +21,52 @@ import logging
21
21
  import os
22
22
  import threading
23
23
  import time
24
+ import ipaddress
24
25
  from typing import Any
25
26
 
26
27
  import httpx
27
28
 
28
29
  logger = logging.getLogger("superlocalmemory.mesh.remote_sync")
29
30
 
31
+
32
+ def _service_ip_addresses(info: Any) -> list[str]:
33
+ """Return validated textual IPs from current and older Zeroconf APIs."""
34
+ candidates: list[Any] = []
35
+ parsed = getattr(info, "parsed_addresses", None)
36
+ if callable(parsed):
37
+ try:
38
+ candidates.extend(parsed())
39
+ except (OSError, TypeError, ValueError):
40
+ pass
41
+ candidates.extend(getattr(info, "addresses", None) or [])
42
+
43
+ addresses: list[str] = []
44
+ for candidate in candidates:
45
+ try:
46
+ address = ipaddress.ip_address(candidate)
47
+ except (TypeError, ValueError):
48
+ continue
49
+ # An mDNS peer must name a routable endpoint, never a wildcard or
50
+ # multicast destination. Authentication is still enforced by mesh.
51
+ if address.is_unspecified or address.is_multicast:
52
+ continue
53
+ rendered = str(address)
54
+ if rendered not in addresses:
55
+ addresses.append(rendered)
56
+ return addresses
57
+
58
+
59
+ def _peer_url(host: str, port: int) -> str:
60
+ """Format an IP literal safely for an HTTP authority."""
61
+ address = ipaddress.ip_address(host)
62
+ rendered = str(address)
63
+ if address.version == 6:
64
+ # RFC 6874 requires a percent sign in an IPv6 zone identifier to be
65
+ # escaped when the literal appears inside a URI authority.
66
+ rendered = rendered.replace("%", "%25")
67
+ rendered = f"[{rendered}]"
68
+ return f"http://{rendered}:{int(port)}"
69
+
30
70
  # Optional zeroconf for mDNS discovery
31
71
  try:
32
72
  from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
@@ -236,17 +276,15 @@ class RemoteSyncClient:
236
276
  if not ZEROCONF_AVAILABLE:
237
277
  return
238
278
  info = zeroconf.get_service_info(service_type, name)
239
- if info and info.addresses:
240
- # Get first IPv4 address
241
- for addr in info.addresses:
242
- if isinstance(addr, str) and "." in addr: # IPv4
243
- port = info.port or 8765
244
- peer_url = f"http://{addr}:{port}"
245
- self._update_peer_url(addr, port)
246
- logger.info(
247
- "RemoteSyncClient: discovered SLM at %s", peer_url
248
- )
249
- return
279
+ if info:
280
+ for addr in _service_ip_addresses(info):
281
+ port = info.port or 8765
282
+ peer_url = _peer_url(addr, port)
283
+ self._update_peer_url(addr, port)
284
+ logger.info(
285
+ "RemoteSyncClient: discovered SLM at %s", peer_url
286
+ )
287
+ return
250
288
  except Exception as e:
251
289
  logger.debug("RemoteSyncClient: mDNS add_service error: %s", e)
252
290
 
@@ -260,7 +298,7 @@ class RemoteSyncClient:
260
298
 
261
299
  def _update_peer_url(self, host: str, port: int) -> None:
262
300
  """Update peer URL from discovery."""
263
- new_url = f"http://{host}:{port}"
301
+ new_url = _peer_url(host, port)
264
302
  if self._peer_url != new_url:
265
303
  self._peer_url = new_url
266
304
  logger.info("RemoteSyncClient: updated peer URL to %s", new_url)
@@ -79,6 +79,33 @@ class NoOpSemantic(SemanticTier):
79
79
  return False
80
80
 
81
81
 
82
+ class _LazySemanticEmbedder:
83
+ """Start the canonical embedding service only after an opted-in lookup."""
84
+
85
+ def __init__(self) -> None:
86
+ self._lock = threading.Lock()
87
+ self._service = None
88
+
89
+ def __call__(self, text: str) -> list[float] | None:
90
+ if self._service is None:
91
+ with self._lock:
92
+ if self._service is None:
93
+ from superlocalmemory.core.config import SLMConfig
94
+ from superlocalmemory.core.embeddings import EmbeddingService
95
+
96
+ self._service = EmbeddingService(SLMConfig.load().embedding)
97
+ return self._service.embed(text)
98
+
99
+ def close(self) -> None:
100
+ service = self._service
101
+ self._service = None
102
+ if service is not None:
103
+ try:
104
+ service.unload()
105
+ except Exception:
106
+ pass
107
+
108
+
82
109
  # ---------------------------------------------------------------------------
83
110
  # Metrics
84
111
  # ---------------------------------------------------------------------------
@@ -130,13 +157,23 @@ class CacheManager:
130
157
  self._metrics = CacheMetrics()
131
158
 
132
159
  @classmethod
133
- def get_instance(cls) -> "CacheManager":
160
+ def get_instance(
161
+ cls,
162
+ *,
163
+ optimize_config: Any | None = None,
164
+ semantic_embedder: Callable[[str], list[float] | None] | None = None,
165
+ ) -> "CacheManager":
134
166
  if cls._instance is None:
135
167
  with cls._instance_lock:
136
168
  if cls._instance is None:
137
169
  from superlocalmemory.optimize.storage.db import CacheDB as _CacheDB
138
170
  _db = _CacheDB.get_default()
139
171
  cls._instance = cls(db=_db)
172
+ if optimize_config is not None:
173
+ cls._instance.configure_semantic(
174
+ optimize_config,
175
+ embedder=semantic_embedder,
176
+ )
140
177
  return cls._instance
141
178
 
142
179
  @classmethod
@@ -149,6 +186,7 @@ class CacheManager:
149
186
  """Reset the singleton (testing only)."""
150
187
  with cls._instance_lock:
151
188
  if cls._instance is not None:
189
+ cls._instance._close_semantic()
152
190
  try:
153
191
  cls._instance._db.close() # type: ignore[attr-defined]
154
192
  except Exception:
@@ -416,8 +454,46 @@ class CacheManager:
416
454
  MetricsCollector.get_instance().on_miss()
417
455
 
418
456
  def set_semantic_tier(self, tier: SemanticTier) -> None:
457
+ self._close_semantic()
419
458
  self._semantic = tier
420
459
 
460
+ def _close_semantic(self) -> None:
461
+ close = getattr(self._semantic, "close", None)
462
+ if callable(close):
463
+ try:
464
+ close()
465
+ except Exception:
466
+ pass
467
+
468
+ def configure_semantic(
469
+ self,
470
+ optimize_config: Any,
471
+ *,
472
+ embedder: Callable[[str], list[float] | None] | None = None,
473
+ ) -> None:
474
+ """Wire or disable the real semantic tier from live proxy config."""
475
+ enabled = bool(getattr(optimize_config, "semantic_enabled", False))
476
+ if not enabled:
477
+ if not isinstance(self._semantic, NoOpSemantic):
478
+ self.set_semantic_tier(NoOpSemantic())
479
+ return
480
+
481
+ from superlocalmemory.optimize.cache.semantic import VCacheSemantic
482
+
483
+ desired_embedder = embedder or _LazySemanticEmbedder()
484
+ current = self._semantic
485
+ if (
486
+ isinstance(current, VCacheSemantic)
487
+ and current._config == optimize_config
488
+ and (embedder is None or current._embedder is embedder)
489
+ ):
490
+ return
491
+ self.set_semantic_tier(VCacheSemantic(
492
+ db=self._db,
493
+ config=optimize_config,
494
+ embedder=desired_embedder,
495
+ ))
496
+
421
497
  # ---- core request path ----
422
498
 
423
499
  def get_or_call(
@@ -22,7 +22,7 @@ import logging
22
22
  import random
23
23
  import threading
24
24
  import time
25
- from typing import TYPE_CHECKING, Any
25
+ from typing import TYPE_CHECKING, Any, Callable
26
26
 
27
27
  import numpy as np
28
28
 
@@ -77,9 +77,12 @@ class VCacheSemantic(SemanticTier):
77
77
  self,
78
78
  db: "CacheDB",
79
79
  config: "OptimizeConfig",
80
+ *,
81
+ embedder: Callable[[str], list[float] | np.ndarray | None] | None = None,
80
82
  ) -> None:
81
83
  self._db = db
82
84
  self._config = config
85
+ self._embedder = embedder
83
86
  # TODO(v3.7): when entry_count > 10_000, promote to sqlite-vec. Config flag: semantic_use_vec.
84
87
 
85
88
  self._boundary_store = BoundaryStore(
@@ -134,7 +137,13 @@ class VCacheSemantic(SemanticTier):
134
137
  return None
135
138
  try:
136
139
  if embed is None:
137
- return None
140
+ if self._embedder is None:
141
+ return None
142
+ messages = _extract_messages(req)
143
+ system = _extract_system(req)
144
+ embed = self._embedder(self._build_query_text(messages, system))
145
+ if embed is None:
146
+ return None
138
147
  vec = np.asarray(embed, dtype=np.float32)
139
148
  if vec.shape[0] != _EMBED_DIM:
140
149
  logger.debug(
@@ -190,7 +199,13 @@ class VCacheSemantic(SemanticTier):
190
199
  return
191
200
  try:
192
201
  if embed is None:
193
- return
202
+ if self._embedder is None:
203
+ return
204
+ messages = _extract_messages(req)
205
+ system = _extract_system(req)
206
+ embed = self._embedder(self._build_query_text(messages, system))
207
+ if embed is None:
208
+ return
194
209
  messages = _extract_messages(req)
195
210
  system = _extract_system(req)
196
211
  query_text = self._build_query_text(messages, system)
@@ -247,6 +262,11 @@ class VCacheSemantic(SemanticTier):
247
262
  tenant_id, exc, exc_info=True,
248
263
  )
249
264
 
265
+ def close(self) -> None:
266
+ close = getattr(self._embedder, "close", None)
267
+ if callable(close):
268
+ close()
269
+
250
270
  # ------------------------------------------------------------------
251
271
  # Internal lookup
252
272
  # ------------------------------------------------------------------
@@ -17,6 +17,10 @@ from __future__ import annotations
17
17
  import logging
18
18
  import re
19
19
  import threading
20
+ from typing import TYPE_CHECKING
21
+
22
+ if TYPE_CHECKING:
23
+ from superlocalmemory.optimize.storage.db import CacheDB
20
24
 
21
25
  logger = logging.getLogger("slm.optimize.compress.ccr")
22
26
 
@@ -18,11 +18,16 @@ import json
18
18
  import logging
19
19
  import threading
20
20
  from dataclasses import dataclass
21
- from typing import Any
21
+ from typing import TYPE_CHECKING, Any
22
22
 
23
23
  from superlocalmemory.optimize.proxy.lifecycle import ProxyRequest, CompressHook
24
24
  from superlocalmemory.optimize.config.store import ConfigStore
25
25
 
26
+ if TYPE_CHECKING:
27
+ from superlocalmemory.optimize.compress.align import CacheAligner
28
+ from superlocalmemory.optimize.compress.ccr import CCRStore
29
+ from superlocalmemory.optimize.compress.prose_llmlingua import LLMLinguaCompressor
30
+
26
31
  logger = logging.getLogger("slm.optimize.compress.router")
27
32
 
28
33
  _MIN_CHARS_FOR_COMPRESSION: int = 500
@@ -6,8 +6,13 @@ module. They NEVER construct ConfigStore themselves or read optimize.json direct
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ from typing import TYPE_CHECKING
10
+
9
11
  from superlocalmemory.optimize.config.schema import OptimizeConfig
10
12
 
13
+ if TYPE_CHECKING:
14
+ from superlocalmemory.optimize.config.store import ConfigStore
15
+
11
16
  _store: "ConfigStore | None" = None
12
17
 
13
18
 
@@ -17,16 +17,16 @@ import json
17
17
  import logging
18
18
  import os
19
19
  import threading
20
- import time
21
20
  from pathlib import Path
22
21
  from typing import Any, Callable
23
22
 
24
- from superlocalmemory.optimize.config.schema import OptimizeConfig
23
+ from superlocalmemory.infra.data_root import DynamicStatePath
25
24
  from superlocalmemory.optimize.config.defaults import DEFAULT_OPTIMIZE_CONFIG
25
+ from superlocalmemory.optimize.config.schema import OptimizeConfig
26
26
 
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
- _DEFAULT_CONFIG_PATH: Path = Path.home() / ".superlocalmemory" / "optimize.json"
29
+ _DEFAULT_CONFIG_PATH = DynamicStatePath("optimize.json")
30
30
  _POLL_INTERVAL_SECONDS: float = 2.0
31
31
 
32
32
 
@@ -38,7 +38,9 @@ class ConfigStore:
38
38
  config_path: Path | None = None,
39
39
  poll_interval: float = _POLL_INTERVAL_SECONDS,
40
40
  ) -> None:
41
- self._config_path = Path(config_path) if config_path else _DEFAULT_CONFIG_PATH
41
+ self._config_path = Path(
42
+ config_path if config_path is not None else _DEFAULT_CONFIG_PATH
43
+ )
42
44
  self._poll_interval_seconds = float(poll_interval)
43
45
  self._lock = threading.RLock()
44
46
  self._change_callbacks: list[Callable[[OptimizeConfig], None]] = []
@@ -7,6 +7,7 @@ import inspect
7
7
  import json
8
8
  import logging
9
9
  from typing import Any, AsyncIterator, Callable
10
+ from weakref import WeakKeyDictionary
10
11
 
11
12
  import httpx
12
13
  from fastapi.requests import Request
@@ -24,8 +25,10 @@ _get_running_loop = asyncio.get_running_loop
24
25
  logger = logging.getLogger("slm.optimize.proxy.helpers")
25
26
 
26
27
  # Per-callable cache of "does this hook method accept a tenant_id kwarg?".
27
- # Keyed by id() of the bound method's __func__ so it is stable per hook class.
28
- _HOOK_TENANT_SUPPORT: dict[int, bool] = {}
28
+ # Keep the callable itself as the key. Integer id() values can be reused after
29
+ # a hook class is collected, which can apply a stale legacy signature result
30
+ # to a new tenant-aware hook and silently drop tenant isolation.
31
+ _HOOK_TENANT_SUPPORT: WeakKeyDictionary[object, bool] = WeakKeyDictionary()
29
32
 
30
33
 
31
34
  def _accepts_tenant_id(fn: Callable) -> bool:
@@ -42,8 +45,12 @@ def _accepts_tenant_id(fn: Callable) -> bool:
42
45
  raises fails open to a cache MISS, never the shared namespace.
43
46
  """
44
47
  target = getattr(fn, "__func__", fn)
45
- key = id(target)
46
- cached = _HOOK_TENANT_SUPPORT.get(key)
48
+ try:
49
+ cached = _HOOK_TENANT_SUPPORT.get(target)
50
+ except TypeError:
51
+ # Some extension callables cannot be weak-referenced. Inspect them on
52
+ # every use instead of falling back to an unsafe integer identity.
53
+ cached = None
47
54
  if cached is None:
48
55
  try:
49
56
  params = inspect.signature(fn).parameters
@@ -53,7 +60,10 @@ def _accepts_tenant_id(fn: Callable) -> bool:
53
60
  except (ValueError, TypeError):
54
61
  # Builtins / C callables without a signature — assume legacy.
55
62
  cached = False
56
- _HOOK_TENANT_SUPPORT[key] = cached
63
+ try:
64
+ _HOOK_TENANT_SUPPORT[target] = cached
65
+ except TypeError:
66
+ pass
57
67
  return cached
58
68
 
59
69
  # SEC-M-02 (CWE-400): reject oversized bodies to prevent compression-bomb DoS.
@@ -28,9 +28,10 @@ import threading
28
28
  from pathlib import Path
29
29
  from typing import Any
30
30
 
31
+ from superlocalmemory.infra.data_root import state_path
32
+
31
33
  logger = logging.getLogger("slm.optimize.proxy.capture")
32
34
 
33
- _CAPTURE_DIRNAME = ".superlocalmemory"
34
35
  _CAPTURE_FILENAME = "optimize_capture.jsonl"
35
36
  _CAPTURE_ENV = "SLM_OPTIMIZE_CAPTURE"
36
37
  _TRUTHY = frozenset({"1", "true", "yes", "on"})
@@ -51,7 +52,7 @@ def capture_enabled() -> bool:
51
52
 
52
53
 
53
54
  def _capture_path() -> Path:
54
- return Path.home() / _CAPTURE_DIRNAME / _CAPTURE_FILENAME
55
+ return state_path(_CAPTURE_FILENAME)
55
56
 
56
57
 
57
58
  class ShadowCapture:
@@ -17,7 +17,7 @@ from superlocalmemory.optimize.proxy.lifecycle import HookChain
17
17
 
18
18
  logger = logging.getLogger("slm.optimize.proxy")
19
19
 
20
- _PROXY_VERSION = "3.6.3"
20
+ _PROXY_VERSION = "3.7.0"
21
21
  _REQUEST_TIMEOUT_S = 300.0
22
22
  _CONNECT_TIMEOUT_S = 10.0
23
23
  _MAX_CONNECTIONS = 100
@@ -178,7 +178,7 @@ def _load_hooks(config: OptimizeConfig) -> HookChain:
178
178
  if config.cache_enabled:
179
179
  try:
180
180
  from superlocalmemory.optimize.cache.manager import CacheManager
181
- cache_hook = CacheManager.get_instance()
181
+ cache_hook = CacheManager.get_instance(optimize_config=config)
182
182
  except Exception as exc:
183
183
  logger.warning(
184
184
  "cache hook load failed (proxy continues without cache): %s", exc