superlocalmemory 3.7.8 → 3.8.1

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 (280) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +129 -0
  4. package/README.md +205 -123
  5. package/package.json +12 -3
  6. package/plugin/.claude-plugin/plugin.json +2 -3
  7. package/plugin/CLAUDE.md +8 -8
  8. package/plugin/agents/slm-governance-advisor.md +80 -0
  9. package/plugin/agents/slm-loop-runner.md +71 -0
  10. package/plugin/agents/slm-memory-advisor.md +10 -5
  11. package/plugin/agents/slm-optimize-advisor.md +9 -3
  12. package/plugin/commands/slm-loop.md +31 -0
  13. package/plugin/hooks/hooks.json +79 -0
  14. package/plugin/requirements.txt +1 -1
  15. package/plugin/scripts/slm-launch +46 -7
  16. package/plugin/settings.json +9 -0
  17. package/plugin/skills/slm-cache/SKILL.md +9 -1
  18. package/plugin/skills/slm-compress/SKILL.md +8 -1
  19. package/plugin/skills/slm-governance/SKILL.md +248 -0
  20. package/plugin/skills/slm-graph/SKILL.md +17 -3
  21. package/plugin/skills/slm-loop/SKILL.md +99 -0
  22. package/plugin/skills/slm-mesh/SKILL.md +282 -0
  23. package/plugin/skills/slm-profile/SKILL.md +148 -0
  24. package/plugin/skills/slm-recall/SKILL.md +46 -10
  25. package/plugin/skills/slm-remember/SKILL.md +48 -1
  26. package/plugin/skills/slm-scope/SKILL.md +176 -0
  27. package/plugin/skills/slm-session/SKILL.md +24 -1
  28. package/plugin/skills/slm-status/SKILL.md +18 -1
  29. package/plugin-src/rules/AGENTS.md +57 -18
  30. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  31. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  32. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  33. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  34. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  35. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  36. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  37. package/pyproject.toml +2 -1
  38. package/scripts/postinstall/validation.js +2 -0
  39. package/scripts/postinstall-interactive.js +74 -2
  40. package/src/superlocalmemory/__init__.py +1 -1
  41. package/src/superlocalmemory/access/__init__.py +3 -0
  42. package/src/superlocalmemory/access/rbac.py +477 -0
  43. package/src/superlocalmemory/cli/commands.py +228 -17
  44. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  45. package/src/superlocalmemory/cli/daemon.py +7 -0
  46. package/src/superlocalmemory/cli/loop_cmd.py +187 -0
  47. package/src/superlocalmemory/cli/main.py +49 -8
  48. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  49. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  50. package/src/superlocalmemory/cli/pending_store.py +49 -13
  51. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  52. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  53. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  54. package/src/superlocalmemory/cli/version_banner.py +17 -3
  55. package/src/superlocalmemory/compliance/audit.py +6 -0
  56. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  57. package/src/superlocalmemory/compliance/retention.py +176 -45
  58. package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
  59. package/src/superlocalmemory/core/community_summary.py +267 -0
  60. package/src/superlocalmemory/core/config.py +216 -3
  61. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  62. package/src/superlocalmemory/core/context_cache.py +61 -18
  63. package/src/superlocalmemory/core/embedding_worker.py +21 -7
  64. package/src/superlocalmemory/core/embeddings.py +131 -46
  65. package/src/superlocalmemory/core/engine.py +41 -22
  66. package/src/superlocalmemory/core/engine_ingestion.py +359 -43
  67. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  68. package/src/superlocalmemory/core/entity_community.py +178 -0
  69. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  70. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  71. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  72. package/src/superlocalmemory/core/injection.py +12 -7
  73. package/src/superlocalmemory/core/key_expander.py +138 -0
  74. package/src/superlocalmemory/core/maintenance.py +23 -0
  75. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  76. package/src/superlocalmemory/core/modes.py +1 -1
  77. package/src/superlocalmemory/core/mutations.py +2 -2
  78. package/src/superlocalmemory/core/pii.py +105 -0
  79. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  80. package/src/superlocalmemory/core/recall_pipeline.py +7 -3
  81. package/src/superlocalmemory/core/recall_worker.py +20 -6
  82. package/src/superlocalmemory/core/scale_engine.py +60 -1
  83. package/src/superlocalmemory/core/security_primitives.py +40 -2
  84. package/src/superlocalmemory/core/store_pipeline.py +186 -29
  85. package/src/superlocalmemory/core/worker_pool.py +21 -6
  86. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  87. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  88. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  89. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  90. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  91. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  92. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  93. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  94. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  95. package/src/superlocalmemory/evolution/triggers.py +22 -13
  96. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  97. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  98. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  99. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  100. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  101. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  102. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  103. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  104. package/src/superlocalmemory/hooks/portable_kit.py +148 -3
  105. package/src/superlocalmemory/infra/backup.py +12 -1
  106. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  107. package/src/superlocalmemory/infra/data_root.py +43 -4
  108. package/src/superlocalmemory/infra/event_bus.py +107 -24
  109. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  110. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  111. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  112. package/src/superlocalmemory/learning/cross_project.py +28 -19
  113. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  114. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  115. package/src/superlocalmemory/learning/reward.py +50 -0
  116. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  117. package/src/superlocalmemory/learning/source_quality.py +523 -1
  118. package/src/superlocalmemory/loops/__init__.py +56 -0
  119. package/src/superlocalmemory/loops/budget.py +58 -0
  120. package/src/superlocalmemory/loops/engine.py +164 -0
  121. package/src/superlocalmemory/loops/ledger.py +263 -0
  122. package/src/superlocalmemory/loops/models.py +152 -0
  123. package/src/superlocalmemory/loops/rules.py +52 -0
  124. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  125. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  126. package/src/superlocalmemory/mcp/profiles.py +103 -0
  127. package/src/superlocalmemory/mcp/server.py +32 -79
  128. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  129. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  130. package/src/superlocalmemory/mcp/tools_core.py +12 -4
  131. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  132. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  133. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  134. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  135. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  136. package/src/superlocalmemory/mesh/broker.py +237 -129
  137. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  138. package/src/superlocalmemory/optimize/NOTICE +1 -6
  139. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  140. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  141. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  142. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  143. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  144. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  145. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  146. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  147. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  148. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  149. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  150. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  151. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  152. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  153. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  154. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  155. package/src/superlocalmemory/retrieval/engine.py +221 -47
  156. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  157. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  158. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  159. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  160. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  161. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  162. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  163. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  164. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  165. package/src/superlocalmemory/server/api.py +4 -4
  166. package/src/superlocalmemory/server/config_file.py +90 -0
  167. package/src/superlocalmemory/server/origin.py +50 -0
  168. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  169. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  170. package/src/superlocalmemory/server/recall_health.py +24 -3
  171. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  172. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  173. package/src/superlocalmemory/server/routes/agents.py +128 -38
  174. package/src/superlocalmemory/server/routes/backup.py +317 -70
  175. package/src/superlocalmemory/server/routes/behavioral.py +349 -71
  176. package/src/superlocalmemory/server/routes/brain.py +69 -12
  177. package/src/superlocalmemory/server/routes/chat.py +10 -5
  178. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  179. package/src/superlocalmemory/server/routes/config_api.py +438 -0
  180. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  181. package/src/superlocalmemory/server/routes/entity.py +108 -26
  182. package/src/superlocalmemory/server/routes/events.py +24 -8
  183. package/src/superlocalmemory/server/routes/evolution.py +189 -68
  184. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  185. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  186. package/src/superlocalmemory/server/routes/insights.py +3 -3
  187. package/src/superlocalmemory/server/routes/learning.py +289 -118
  188. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  189. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  190. package/src/superlocalmemory/server/routes/memories.py +182 -57
  191. package/src/superlocalmemory/server/routes/mesh.py +200 -31
  192. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  193. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  194. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  195. package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
  196. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  197. package/src/superlocalmemory/server/routes/stats.py +103 -158
  198. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  199. package/src/superlocalmemory/server/routes/token.py +3 -13
  200. package/src/superlocalmemory/server/routes/v3_api.py +247 -89
  201. package/src/superlocalmemory/server/routes/ws.py +5 -2
  202. package/src/superlocalmemory/server/security_middleware.py +12 -5
  203. package/src/superlocalmemory/server/ui.py +20 -5
  204. package/src/superlocalmemory/server/unified_daemon.py +827 -72
  205. package/src/superlocalmemory/server/write_identity.py +38 -8
  206. package/src/superlocalmemory/storage/database.py +265 -53
  207. package/src/superlocalmemory/storage/migration_runner.py +132 -1
  208. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  209. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  210. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  211. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  212. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  213. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  214. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  215. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  216. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  217. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  218. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  219. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  220. package/src/superlocalmemory/storage/models.py +4 -0
  221. package/src/superlocalmemory/storage/schema.py +136 -1
  222. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  223. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  224. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  225. package/src/superlocalmemory/trust/gate.py +49 -8
  226. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  227. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  228. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  229. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  230. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  231. package/src/superlocalmemory/ui/favicon.svg +35 -4
  232. package/src/superlocalmemory/ui/index.html +303 -173
  233. package/src/superlocalmemory/ui/js/brain.js +5 -20
  234. package/src/superlocalmemory/ui/js/core.js +100 -41
  235. package/src/superlocalmemory/ui/js/dashboard.js +403 -65
  236. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  237. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  238. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  239. package/src/superlocalmemory/ui/js/memories.js +15 -4
  240. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  241. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  242. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  243. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  244. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  245. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  246. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  247. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  248. package/src/superlocalmemory/ui/js/od-backup.js +871 -0
  249. package/src/superlocalmemory/ui/js/od-brain.js +816 -0
  250. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  251. package/src/superlocalmemory/ui/js/od-graph.js +600 -0
  252. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  253. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  254. package/src/superlocalmemory/ui/js/od-memories.js +929 -0
  255. package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
  256. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  257. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  258. package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
  259. package/src/superlocalmemory/ui/js/od-shell.js +809 -0
  260. package/src/superlocalmemory/ui/js/od-skills.js +600 -0
  261. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  262. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  263. package/src/superlocalmemory/ui/js/settings.js +17 -3
  264. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  265. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  266. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  267. package/plugin-src/.mcp.json +0 -12
  268. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  269. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  270. package/plugin-src/hooks/.gitkeep +0 -0
  271. package/plugin-src/hooks/hooks.json +0 -23
  272. package/plugin-src/manifest.json +0 -25
  273. package/plugin-src/requirements.txt +0 -1
  274. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  275. package/plugin-src/scripts/ensure-venv.bat +0 -122
  276. package/plugin-src/scripts/ensure-venv.sh +0 -105
  277. package/plugin-src/scripts/slm-launch +0 -23
  278. package/plugin-src/scripts/slm-launch.bat +0 -23
  279. package/plugin-src/settings.json +0 -16
  280. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -80,6 +80,8 @@ _NO_DAEMON_COMMANDS = {
80
80
  "wrap",
81
81
  # V3.6 Optimize commands that are config read/write only.
82
82
  "optimize", "cache", "compress", "help-optimize",
83
+ # Bounded loops use an in-process engine store, not the daemon.
84
+ "loop",
83
85
  # Lifecycle orchestration must run before any global auto-start hook.
84
86
  "serve", "restart",
85
87
  }
@@ -112,12 +114,17 @@ def main() -> None:
112
114
  or any(arg in {"-h", "--help"} for arg in sys.argv[1:])
113
115
  or (len(sys.argv) == 2 and sys.argv[1] in {"-v", "--version"})
114
116
  )
117
+ _is_connect_dry_run = (
118
+ len(sys.argv) >= 2
119
+ and sys.argv[1] == "connect"
120
+ and "--dry-run" in sys.argv[2:]
121
+ )
115
122
 
116
123
  # WP-07: lazy first-run init — runs after hook/mcp fast-paths so stdout
117
124
  # is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
118
125
  # Guarded: any failure must not crash the CLI (AC4).
119
126
  _is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
120
- if not _is_mcp_cmd and not _is_metadata_cmd:
127
+ if not _is_mcp_cmd and not _is_metadata_cmd and not _is_connect_dry_run:
121
128
  try:
122
129
  from superlocalmemory.cli._lazy_init import _ensure_initialized
123
130
  _ensure_initialized()
@@ -137,7 +144,7 @@ def main() -> None:
137
144
 
138
145
  # One-time post-upgrade banner — silent for fresh installs and
139
146
  # same-version runs. Guarded against I/O errors internally.
140
- if not _is_mcp_stdio and not _is_metadata_cmd:
147
+ if not _is_mcp_stdio and not _is_metadata_cmd and not _is_connect_dry_run:
141
148
  from superlocalmemory.cli.version_banner import check_and_emit_upgrade_banner
142
149
  if check_and_emit_upgrade_banner(_ver):
143
150
  # First post-upgrade invocation: apply the data-dir migration if
@@ -276,6 +283,14 @@ def main() -> None:
276
283
  db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
277
284
  db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
278
285
 
286
+ # -- Mesh inspection (v3.7.9, M-03) --------------------------------
287
+ mesh_p = sub.add_parser("mesh", help="Inspect the local agent mesh (status/peers)")
288
+ mesh_p.add_argument(
289
+ "mesh_action",
290
+ choices=("status", "peers"),
291
+ help="status: broker health + stats; peers: active peer sessions",
292
+ )
293
+
279
294
  # -- Memory Operations ---------------------------------------------
280
295
  remember_p = sub.add_parser("remember", help="Store a memory (extracts facts, builds graph)")
281
296
  remember_p.add_argument("content", help="Content to remember")
@@ -304,9 +319,15 @@ def main() -> None:
304
319
  help=f"Max results (default {CANONICAL_RECALL_LIMIT})",
305
320
  )
306
321
  recall_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
322
+ recall_p.add_argument(
323
+ "--window", default="",
324
+ help="Restrict results to an event-time range: a relative span "
325
+ "(24h, 7d, 30d, 1y) or an explicit range (2026-07-01..2026-07-31). "
326
+ "Default: no time filter.",
327
+ )
307
328
  recall_p.add_argument(
308
329
  "--fast", action="store_true",
309
- help="Skip spreading activation and remote agentic verification for a "
330
+ help="Skip graph-assisted candidate expansion and remote agentic verification for a "
310
331
  "latency-bounded response. Other configured retrieval channels still run. "
311
332
  "Use when you need recall before a tool call (e.g. before WebSearch).",
312
333
  )
@@ -332,8 +353,8 @@ def main() -> None:
332
353
  )
333
354
 
334
355
  forget_p = sub.add_parser("forget", help="Delete memories matching a query (fuzzy)")
335
- forget_p.add_argument("query", help="Query to match for deletion")
336
- forget_p.add_argument("--dry-run", action="store_true", default=False, help="Preview matches without deleting")
356
+ forget_p.add_argument("query", nargs="?", default=None, help="Query to match for deletion. Optional with --dry-run (previews all memories).")
357
+ forget_p.add_argument("--dry-run", action="store_true", default=False, help="Preview matches without deleting. With no query, previews every memory.")
337
358
  forget_p.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
338
359
  forget_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
339
360
 
@@ -361,7 +382,7 @@ def main() -> None:
361
382
  help="Show extended status: migration log, daemon port, disabled marker, last version",
362
383
  )
363
384
 
364
- health_p = sub.add_parser("health", help="Math layer health (Fisher-Rao, Sheaf, Langevin)")
385
+ health_p = sub.add_parser("health", help="Math layer health (scoring, consistency, and lifecycle layers)")
365
386
  health_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
366
387
 
367
388
  trace_p = sub.add_parser("trace", help="Recall with per-channel score breakdown")
@@ -774,6 +795,25 @@ def main() -> None:
774
795
 
775
796
  # ---- end SLM v3.6 Optimize subcommands ----
776
797
 
798
+ # slm loop demo|history|show — bounded, gate-verified agent loops (v3.8.0)
799
+ loop_p = sub.add_parser(
800
+ "loop",
801
+ help="Bounded loops: gate-verified agent loops with an SLM-backed ledger",
802
+ )
803
+ loop_sub = loop_p.add_subparsers(dest="loop_command", title="loop subcommands")
804
+ loop_demo_p = loop_sub.add_parser(
805
+ "demo", help="Run the keyless convergence demo (proves engine + ledger)")
806
+ loop_demo_p.add_argument(
807
+ "--iterations", type=int, default=10, help="Max iterations (default: 10)")
808
+ loop_hist_p = loop_sub.add_parser("history", help="List recorded loop runs")
809
+ loop_hist_p.add_argument(
810
+ "--name", default=None, help="Loop name (default: convergence-demo)")
811
+ loop_show_p = loop_sub.add_parser("show", help="Show every lap of one run")
812
+ loop_show_p.add_argument("run_id", help="Run id (from history)")
813
+ for _sp in loop_sub.choices.values():
814
+ _sp.add_argument("--json", action="store_true",
815
+ help="Output structured JSON (agent-native)")
816
+
777
817
  args = parser.parse_args()
778
818
 
779
819
  if not args.command:
@@ -781,8 +821,9 @@ def main() -> None:
781
821
  sys.exit(0)
782
822
 
783
823
  # V3.3.19: Auto-trigger setup wizard on first use
784
- from superlocalmemory.cli.setup_wizard import check_first_use
785
- check_first_use(args.command)
824
+ if not (args.command == "connect" and getattr(args, "dry_run", False)):
825
+ from superlocalmemory.cli.setup_wizard import check_first_use
826
+ check_first_use(args.command)
786
827
 
787
828
  # V3.4.4: Auto-start daemon for all commands that need it.
788
829
  # SLM is always-on — close laptop, reboot, crash: daemon auto-recovers.
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later
3
+ """`slm mesh` — inspect the local agent mesh from the terminal (M-03, v3.7.9).
4
+
5
+ Before 3.7.9 the mesh was reachable only via MCP tools, the dashboard, and
6
+ Claude Code skills — there was no terminal command to check broker health or
7
+ list peer sessions. This is a thin, read-only wrapper over the same
8
+ capability-authenticated `/mesh/*` daemon endpoints the MCP tools use.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from argparse import Namespace
14
+
15
+ _ACTION_ENDPOINTS = {
16
+ "status": "/mesh/status",
17
+ "peers": "/mesh/peers",
18
+ }
19
+
20
+
21
+ def cmd_mesh(args: Namespace) -> int:
22
+ action = getattr(args, "mesh_action", None)
23
+ endpoint = _ACTION_ENDPOINTS.get(action)
24
+ if endpoint is None:
25
+ print("Usage: slm mesh {status|peers}")
26
+ return 2
27
+
28
+ from superlocalmemory.cli.daemon import daemon_request
29
+
30
+ result = daemon_request("GET", endpoint)
31
+ if result is None:
32
+ print(
33
+ "Mesh: cannot reach the daemon broker. Is the daemon running? "
34
+ "(slm serve start)"
35
+ )
36
+ return 1
37
+ print(json.dumps(result, indent=2, sort_keys=True))
38
+ return 0
@@ -98,6 +98,9 @@ def cmd_optimize_status(args: Namespace) -> None:
98
98
  print(f" Compress: {'enabled' if cfg.compress_enabled else 'disabled'}"
99
99
  f" (mode: {cfg.compress_mode},"
100
100
  f" prose/L2: {'ON' if cfg.compress_prose else 'OFF'})")
101
+ if cfg.compress_prose or cfg.compress_mode == "aggressive":
102
+ print(" note: the live proxy applies LOSSLESS compression only;"
103
+ " lossy Layer-2 (prose) runs via the slm_compress tool.")
101
104
  proxy_status = f"running on :{OPTIMIZE_DEFAULT_PORT}" if proxy_running else "not running"
102
105
  print(f" Proxy: {proxy_status}")
103
106
  print(f" Config: ~/.superlocalmemory/optimize.json (version {cfg.config_version})")
@@ -43,6 +43,7 @@ _MAX_RETRY_DELAY_SECONDS = 3600
43
43
  _SCHEMA = """
44
44
  CREATE TABLE IF NOT EXISTS pending_memories (
45
45
  id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ profile_id TEXT NOT NULL DEFAULT 'default',
46
47
  content TEXT NOT NULL,
47
48
  tags TEXT DEFAULT '',
48
49
  metadata TEXT DEFAULT '{}',
@@ -62,6 +63,12 @@ def _get_db(base_dir: Path | None = None) -> sqlite3.Connection:
62
63
  db_path = d / _PENDING_DB
63
64
  conn = sqlite3.connect(str(db_path), timeout=5)
64
65
  conn.execute("PRAGMA journal_mode=WAL")
66
+ # C4: pending queue can hold not-yet-materialized memory content owner-only.
67
+ try:
68
+ from superlocalmemory.core.security_primitives import harden_db_perms
69
+ harden_db_perms(db_path)
70
+ except Exception:
71
+ pass
65
72
  conn.execute(_SCHEMA)
66
73
  columns = {
67
74
  row[1]
@@ -73,6 +80,15 @@ def _get_db(base_dir: Path | None = None) -> sqlite3.Connection:
73
80
  "next_retry_at REAL DEFAULT 0"
74
81
  )
75
82
  conn.commit()
83
+ # Per-profile isolation: a queued item must materialize under the profile
84
+ # that was active when it was enqueued — never under whatever profile is
85
+ # active at drain time. Existing rows backfill to 'default'.
86
+ if "profile_id" not in columns:
87
+ conn.execute(
88
+ "ALTER TABLE pending_memories ADD COLUMN "
89
+ "profile_id TEXT NOT NULL DEFAULT 'default'"
90
+ )
91
+ conn.commit()
76
92
  # Pre-V3.7 rows were terminally hidden after three failures. Restore them
77
93
  # to the retry queue; M018 makes replay idempotent and no raw evidence may
78
94
  # remain stranded solely because an older version exhausted its counter.
@@ -89,8 +105,12 @@ def store_pending(
89
105
  tags: str = "",
90
106
  metadata: dict | None = None,
91
107
  base_dir: Path | None = None,
108
+ profile_id: str = "default",
92
109
  ) -> int:
93
- """Store content in pending table. Returns the row ID.
110
+ """Store content in pending table under a profile. Returns the row ID.
111
+
112
+ ``profile_id`` captures the profile active at ENQUEUE time so a later
113
+ profile switch can never redirect this memory to a different profile.
94
114
 
95
115
  This is intentionally FAST — no engine init, no embedding, no model loading.
96
116
  Just a raw SQLite INSERT (~0.1s).
@@ -98,9 +118,11 @@ def store_pending(
98
118
  conn = _get_db(base_dir)
99
119
  try:
100
120
  cur = conn.execute(
101
- "INSERT INTO pending_memories (content, tags, metadata, created_at, status) "
102
- "VALUES (?, ?, ?, ?, 'pending')",
103
- (content, tags, json.dumps(metadata or {}), time.strftime("%Y-%m-%dT%H:%M:%S")),
121
+ "INSERT INTO pending_memories "
122
+ "(profile_id, content, tags, metadata, created_at, status) "
123
+ "VALUES (?, ?, ?, ?, ?, 'pending')",
124
+ (profile_id, content, tags, json.dumps(metadata or {}),
125
+ time.strftime("%Y-%m-%dT%H:%M:%S")),
104
126
  )
105
127
  conn.commit()
106
128
  return cur.lastrowid or 0
@@ -108,20 +130,34 @@ def store_pending(
108
130
  conn.close()
109
131
 
110
132
 
111
- def get_pending(base_dir: Path | None = None, limit: int = 50) -> list[dict]:
112
- """Get unprocessed pending memories."""
133
+ def get_pending(
134
+ base_dir: Path | None = None,
135
+ limit: int = 50,
136
+ profile_id: str | None = None,
137
+ ) -> list[dict]:
138
+ """Get unprocessed pending memories, optionally scoped to one profile.
139
+
140
+ The drain passes ``profile_id`` = its engine's active profile so it only
141
+ ever claims items enqueued under that profile. Items for other profiles
142
+ wait until their profile is active — never materialized under the wrong one.
143
+ """
113
144
  conn = _get_db(base_dir)
114
145
  try:
115
- rows = conn.execute(
116
- "SELECT id, content, tags, metadata, created_at, retry_count "
146
+ query = (
147
+ "SELECT id, content, tags, metadata, created_at, retry_count, profile_id "
117
148
  "FROM pending_memories WHERE status = 'pending' "
118
- "AND COALESCE(next_retry_at, 0) <= ? "
119
- "ORDER BY id ASC LIMIT ?",
120
- (time.time(), limit),
121
- ).fetchall()
149
+ "AND COALESCE(next_retry_at, 0) <= ?"
150
+ )
151
+ params: list = [time.time()]
152
+ if profile_id is not None:
153
+ query += " AND profile_id = ?"
154
+ params.append(profile_id)
155
+ query += " ORDER BY id ASC LIMIT ?"
156
+ params.append(limit)
157
+ rows = conn.execute(query, params).fetchall()
122
158
  return [
123
159
  {"id": r[0], "content": r[1], "tags": r[2], "metadata": r[3],
124
- "created_at": r[4], "retry_count": r[5]}
160
+ "created_at": r[4], "retry_count": r[5], "profile_id": r[6]}
125
161
  for r in rows
126
162
  ]
127
163
  finally:
@@ -99,6 +99,10 @@ def cmd_proxy(args: Namespace) -> None:
99
99
  print("Or run: slm wrap claude")
100
100
  print()
101
101
  print("Proxy ready.")
102
+ print()
103
+ print("Note: 'slm proxy' enables the proxy independently — it does not")
104
+ print("flip the master optimize switch, so 'slm optimize status' may")
105
+ print("show OFF while the proxy is running.")
102
106
  else:
103
107
  print("Error: proxy failed to start. Check logs.", file=sys.stderr)
104
108
  sys.exit(1)
@@ -37,10 +37,16 @@ def cmd_db_scale(args: Namespace) -> int:
37
37
  if not args.stage_id:
38
38
  raise ScaleEngineError("promote requires --stage-id (see `slm db scale status`)")
39
39
  result = manager.promote(args.stage_id)
40
+ # The running daemon reads scale_engine_state once at startup; the
41
+ # promoted Cozo/Lance backends are only wired in on the next start.
42
+ # Without this flag users see a clean promote and keep hitting
43
+ # SQLite-only silently (no error, no speedup).
44
+ result = {**result, "restart_required": True}
40
45
  elif action == "rollback":
41
46
  if not args.backup_id:
42
47
  raise ScaleEngineError("rollback requires --backup-id (see `slm db scale status`)")
43
48
  result = manager.rollback(args.backup_id)
49
+ result = {**result, "restart_required": True}
44
50
  else:
45
51
  raise ScaleEngineError(f"unknown Scale Engine action: {action}")
46
52
  except (ScaleEngineError, CanonicalVectorError) as exc:
@@ -114,17 +114,19 @@ def _download_model(model_name: str, label: str) -> bool:
114
114
  print(f"\n Downloading {label}: {model_name}")
115
115
  print(f" (this may take a few minutes on first run)\n")
116
116
 
117
+ # H-03: pass the model name as argv, never interpolated into executed
118
+ # source, so a crafted model_name cannot become arbitrary Python.
117
119
  script = (
118
- f"import sys; "
119
- f"from sentence_transformers import SentenceTransformer; "
120
- f"m = SentenceTransformer('{model_name}', trust_remote_code=True); "
121
- f"d = m.get_sentence_embedding_dimension(); "
122
- f"print(f'OK dim={{d}}'); "
120
+ "import sys; "
121
+ "from sentence_transformers import SentenceTransformer; "
122
+ "m = SentenceTransformer(sys.argv[1], trust_remote_code=True); "
123
+ "d = m.get_sentence_embedding_dimension(); "
124
+ "print(f'OK dim={d}'); "
123
125
  )
124
126
 
125
127
  try:
126
128
  result = subprocess.run(
127
- [sys.executable, "-c", script],
129
+ [sys.executable, "-c", script, model_name],
128
130
  timeout=600, # 10 min for large model downloads
129
131
  capture_output=False, # Show download progress
130
132
  text=True,
@@ -156,15 +158,16 @@ def _download_reranker(model_name: str) -> bool:
156
158
  print(f"\n Downloading reranker: {model_name}")
157
159
  print(f" (cross-encoder for result re-ranking)\n")
158
160
 
161
+ # H-03: model name via argv, never interpolated into executed source.
159
162
  script = (
160
- f"from sentence_transformers import CrossEncoder; "
161
- f"m = CrossEncoder('{model_name}', trust_remote_code=True); "
162
- f"print('OK'); "
163
+ "import sys; from sentence_transformers import CrossEncoder; "
164
+ "m = CrossEncoder(sys.argv[1], trust_remote_code=True); "
165
+ "print('OK'); "
163
166
  )
164
167
 
165
168
  try:
166
169
  result = subprocess.run(
167
- [sys.executable, "-c", script],
170
+ [sys.executable, "-c", script, model_name],
168
171
  timeout=300,
169
172
  capture_output=False,
170
173
  text=True,
@@ -195,16 +198,17 @@ def _download_compressor(model_name: str) -> bool:
195
198
  print(f"\n Downloading compression model: {model_name}")
196
199
  print(f" (LLMLingua-2 prose compressor, ~560MB — aggressive mode only)\n")
197
200
 
201
+ # H-03: model name via argv, never interpolated into executed source.
198
202
  script = (
199
- "from llmlingua import PromptCompressor; "
200
- f"PromptCompressor(model_name='{model_name}', use_llmlingua2=True, "
203
+ "import sys; from llmlingua import PromptCompressor; "
204
+ "PromptCompressor(model_name=sys.argv[1], use_llmlingua2=True, "
201
205
  "device_map='cpu'); "
202
206
  "print('OK')"
203
207
  )
204
208
 
205
209
  try:
206
210
  result = subprocess.run(
207
- [sys.executable, "-c", script],
211
+ [sys.executable, "-c", script, model_name],
208
212
  timeout=900, # 560MB on a slow link can exceed 5 min
209
213
  capture_output=False,
210
214
  text=True,
@@ -494,6 +498,11 @@ def run_wizard(auto: bool = False) -> None:
494
498
 
495
499
  # -- Step 4: Download models --
496
500
  print()
501
+ # H-06 (CVE-2025-14926): a malicious HuggingFace checkpoint can execute code
502
+ # at load time. SLM only downloads its pinned defaults, but warn users who
503
+ # point config at a custom model.
504
+ print(" ⚠ Only install models from sources you trust — a malicious model")
505
+ print(" checkpoint can run code on your machine at load time. See SECURITY.md.")
497
506
  print("─── Step 4/10: Download Embedding Model ───")
498
507
 
499
508
  if _embedding_is_remote(config):
@@ -26,6 +26,15 @@ _VERSION_PATTERN = re.compile(r"^[0-9A-Za-z.\-+_]{1,32}$")
26
26
 
27
27
  _MAX_MARKER_BYTES = 64 # a semver string is ≤ 32 chars; 64 is plenty
28
28
 
29
+ _RELEASE_NOTES: dict[str, tuple[str, ...]] = {
30
+ "3.8.1": (
31
+ "Large existing databases no longer delay daemon readiness",
32
+ "Failed enrichment retries stop after ten automatic attempts",
33
+ "Dashboard panes stay mounted between navigation changes",
34
+ "Local model workers stay warm for a 30-minute working session",
35
+ ),
36
+ }
37
+
29
38
 
30
39
  def _data_dir() -> Path:
31
40
  from superlocalmemory.infra.data_root import canonical_data_root
@@ -116,11 +125,16 @@ def _banner(prior: str | None, current: str) -> str:
116
125
  header = (f"SuperLocalMemory upgraded from {prior} to {current}"
117
126
  if prior else
118
127
  f"SuperLocalMemory upgraded to {current} (from an earlier version)")
128
+ notes = _RELEASE_NOTES.get(
129
+ current,
130
+ (
131
+ "The installed version has changed; existing memory remains in place",
132
+ "See the release notes for version-specific changes",
133
+ ),
134
+ )
119
135
  return "\n".join([
120
136
  header,
121
- " - Multi-IDE MCP processes now share a worker — large RAM drop",
122
- " - Feedback and learning signals flow from every IDE to the daemon",
123
- " - Silent data migration complete; no manual steps required",
137
+ *(f" - {note}" for note in notes),
124
138
  "Run `slm doctor` to verify your setup.",
125
139
  "",
126
140
  ])
@@ -92,6 +92,12 @@ class AuditChain:
92
92
  conn = sqlite3.connect(path)
93
93
  conn.execute("PRAGMA journal_mode=WAL")
94
94
  conn.row_factory = sqlite3.Row
95
+ # C4: audit chain holds a tamper-evident record — keep it owner-only.
96
+ try:
97
+ from superlocalmemory.core.security_primitives import harden_db_perms
98
+ harden_db_perms(path)
99
+ except Exception:
100
+ pass
95
101
  return conn
96
102
 
97
103
  def _make_conn(self) -> sqlite3.Connection: