superlocalmemory 3.7.7 → 3.8.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 (262) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +85 -0
  4. package/README.md +199 -29
  5. package/package.json +4 -2
  6. package/plugin/.claude-plugin/plugin.json +2 -2
  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/agents/slm-governance-advisor.md +80 -0
  30. package/plugin-src/agents/slm-loop-runner.md +71 -0
  31. package/plugin-src/agents/slm-memory-advisor.md +10 -5
  32. package/plugin-src/agents/slm-optimize-advisor.md +9 -3
  33. package/plugin-src/commands/slm-loop.md +31 -0
  34. package/plugin-src/hooks/hooks.json +79 -0
  35. package/plugin-src/manifest.json +7 -2
  36. package/plugin-src/requirements.txt +1 -1
  37. package/plugin-src/rules/AGENTS.md +57 -18
  38. package/plugin-src/rules/CLAUDE.md.fragment +8 -8
  39. package/plugin-src/scripts/slm-launch +46 -7
  40. package/plugin-src/settings.json +9 -0
  41. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  42. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  43. package/plugin-src/skills/slm-governance/SKILL.md +248 -0
  44. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  45. package/plugin-src/skills/slm-loop/SKILL.md +99 -0
  46. package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
  47. package/plugin-src/skills/slm-profile/SKILL.md +148 -0
  48. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  49. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  50. package/plugin-src/skills/slm-scope/SKILL.md +176 -0
  51. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  52. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  53. package/pyproject.toml +1 -2
  54. package/scripts/postinstall/validation.js +2 -0
  55. package/scripts/postinstall-interactive.js +74 -2
  56. package/src/superlocalmemory/__init__.py +1 -1
  57. package/src/superlocalmemory/access/__init__.py +3 -0
  58. package/src/superlocalmemory/access/rbac.py +477 -0
  59. package/src/superlocalmemory/cli/commands.py +96 -12
  60. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  61. package/src/superlocalmemory/cli/loop_cmd.py +192 -0
  62. package/src/superlocalmemory/cli/main.py +39 -4
  63. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  64. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  65. package/src/superlocalmemory/cli/pending_store.py +49 -13
  66. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  67. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  68. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  69. package/src/superlocalmemory/compliance/audit.py +6 -0
  70. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  71. package/src/superlocalmemory/compliance/retention.py +176 -45
  72. package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
  73. package/src/superlocalmemory/core/community_summary.py +267 -0
  74. package/src/superlocalmemory/core/config.py +216 -3
  75. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  76. package/src/superlocalmemory/core/context_cache.py +61 -18
  77. package/src/superlocalmemory/core/embedding_worker.py +17 -2
  78. package/src/superlocalmemory/core/embeddings.py +12 -1
  79. package/src/superlocalmemory/core/engine.py +17 -1
  80. package/src/superlocalmemory/core/engine_ingestion.py +29 -0
  81. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  82. package/src/superlocalmemory/core/entity_community.py +178 -0
  83. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  84. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  85. package/src/superlocalmemory/core/key_expander.py +138 -0
  86. package/src/superlocalmemory/core/maintenance.py +23 -0
  87. package/src/superlocalmemory/core/modes.py +1 -1
  88. package/src/superlocalmemory/core/mutations.py +2 -2
  89. package/src/superlocalmemory/core/pii.py +105 -0
  90. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  91. package/src/superlocalmemory/core/recall_pipeline.py +2 -0
  92. package/src/superlocalmemory/core/recall_worker.py +20 -6
  93. package/src/superlocalmemory/core/scale_engine.py +60 -1
  94. package/src/superlocalmemory/core/security_primitives.py +40 -2
  95. package/src/superlocalmemory/core/store_pipeline.py +35 -11
  96. package/src/superlocalmemory/core/worker_pool.py +21 -6
  97. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  98. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  99. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  100. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  101. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  102. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  103. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  104. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  105. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  106. package/src/superlocalmemory/evolution/triggers.py +22 -13
  107. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  108. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  109. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  110. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  111. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  112. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  113. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  114. package/src/superlocalmemory/hooks/portable_kit.py +114 -1
  115. package/src/superlocalmemory/infra/auth_middleware.py +28 -0
  116. package/src/superlocalmemory/infra/backup.py +12 -1
  117. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  118. package/src/superlocalmemory/infra/data_root.py +43 -4
  119. package/src/superlocalmemory/infra/event_bus.py +107 -24
  120. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  121. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  122. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  123. package/src/superlocalmemory/learning/cross_project.py +28 -19
  124. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  125. package/src/superlocalmemory/loops/__init__.py +56 -0
  126. package/src/superlocalmemory/loops/budget.py +58 -0
  127. package/src/superlocalmemory/loops/engine.py +164 -0
  128. package/src/superlocalmemory/loops/ledger.py +243 -0
  129. package/src/superlocalmemory/loops/models.py +152 -0
  130. package/src/superlocalmemory/loops/rules.py +52 -0
  131. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  132. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  133. package/src/superlocalmemory/mcp/profiles.py +103 -0
  134. package/src/superlocalmemory/mcp/server.py +21 -49
  135. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  136. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  137. package/src/superlocalmemory/mcp/tools_core.py +50 -5
  138. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  139. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  140. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  141. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  142. package/src/superlocalmemory/mesh/broker.py +237 -129
  143. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  144. package/src/superlocalmemory/optimize/NOTICE +1 -6
  145. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  146. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  147. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  148. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  149. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  150. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  151. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  152. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  153. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  154. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  155. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  156. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  157. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  158. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  159. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  160. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  161. package/src/superlocalmemory/retrieval/engine.py +168 -26
  162. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  163. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  164. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  165. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  166. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  167. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  168. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  169. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  170. package/src/superlocalmemory/server/api.py +21 -4
  171. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  172. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  173. package/src/superlocalmemory/server/recall_health.py +24 -3
  174. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  175. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  176. package/src/superlocalmemory/server/routes/agents.py +128 -38
  177. package/src/superlocalmemory/server/routes/backup.py +34 -10
  178. package/src/superlocalmemory/server/routes/behavioral.py +13 -12
  179. package/src/superlocalmemory/server/routes/brain.py +21 -5
  180. package/src/superlocalmemory/server/routes/chat.py +72 -16
  181. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  182. package/src/superlocalmemory/server/routes/config_api.py +436 -0
  183. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  184. package/src/superlocalmemory/server/routes/entity.py +9 -4
  185. package/src/superlocalmemory/server/routes/events.py +24 -8
  186. package/src/superlocalmemory/server/routes/evolution.py +135 -17
  187. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  188. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  189. package/src/superlocalmemory/server/routes/insights.py +3 -3
  190. package/src/superlocalmemory/server/routes/learning.py +14 -14
  191. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  192. package/src/superlocalmemory/server/routes/memories.py +221 -49
  193. package/src/superlocalmemory/server/routes/mesh.py +95 -15
  194. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  195. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  196. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  197. package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
  198. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  199. package/src/superlocalmemory/server/routes/stats.py +13 -6
  200. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  201. package/src/superlocalmemory/server/routes/v3_api.py +194 -81
  202. package/src/superlocalmemory/server/routes/ws.py +5 -2
  203. package/src/superlocalmemory/server/security_middleware.py +12 -5
  204. package/src/superlocalmemory/server/ui.py +30 -5
  205. package/src/superlocalmemory/server/unified_daemon.py +431 -75
  206. package/src/superlocalmemory/server/write_identity.py +38 -8
  207. package/src/superlocalmemory/storage/database.py +265 -53
  208. package/src/superlocalmemory/storage/migration_runner.py +53 -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/models.py +4 -0
  217. package/src/superlocalmemory/storage/schema.py +87 -0
  218. package/src/superlocalmemory/storage/schema_v32.py +0 -9
  219. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  220. package/src/superlocalmemory/trust/gate.py +49 -8
  221. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  222. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  223. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  224. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  225. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  226. package/src/superlocalmemory/ui/favicon.svg +35 -4
  227. package/src/superlocalmemory/ui/index.html +306 -173
  228. package/src/superlocalmemory/ui/js/brain.js +5 -20
  229. package/src/superlocalmemory/ui/js/core.js +47 -31
  230. package/src/superlocalmemory/ui/js/dashboard.js +314 -63
  231. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  232. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  233. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  234. package/src/superlocalmemory/ui/js/memories.js +15 -4
  235. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  236. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  237. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  238. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  239. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  240. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  241. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  242. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  243. package/src/superlocalmemory/ui/js/od-backup.js +780 -0
  244. package/src/superlocalmemory/ui/js/od-brain.js +779 -0
  245. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  246. package/src/superlocalmemory/ui/js/od-graph.js +593 -0
  247. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  248. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  249. package/src/superlocalmemory/ui/js/od-memories.js +887 -0
  250. package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
  251. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  252. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  253. package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
  254. package/src/superlocalmemory/ui/js/od-shell.js +593 -0
  255. package/src/superlocalmemory/ui/js/od-skills.js +573 -0
  256. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  257. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  258. package/src/superlocalmemory/ui/js/settings.js +2 -2
  259. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  260. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  261. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  262. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -40,6 +40,14 @@ def _cmd_db_dispatch(args: Namespace) -> None:
40
40
  sys.exit(2)
41
41
 
42
42
 
43
+ def _cmd_mesh_dispatch(args: Namespace) -> None:
44
+ """Route ``slm mesh ...`` inspection subcommands (M-03)."""
45
+ from superlocalmemory.cli.mesh_cmd import cmd_mesh
46
+ rc = cmd_mesh(args)
47
+ if rc:
48
+ sys.exit(rc)
49
+
50
+
43
51
  def _cmd_escape_disable(args: Namespace) -> None:
44
52
  from superlocalmemory.cli.escape_hatch import cmd_disable
45
53
  cmd_disable(args)
@@ -98,6 +106,11 @@ def _cmd_help_optimize(args: Namespace) -> None:
98
106
  cmd_help_optimize(args)
99
107
 
100
108
 
109
+ def _cmd_loop(args: Namespace) -> None:
110
+ from superlocalmemory.cli.loop_cmd import cmd_loop
111
+ cmd_loop(args)
112
+
113
+
101
114
  # ---- end SLM v3.6 Optimize dispatch functions ----
102
115
 
103
116
 
@@ -201,6 +214,8 @@ def dispatch(args: Namespace) -> None:
201
214
  "context": _cmd_context_dispatch,
202
215
  # V3.4.22 LLD-06 additive schema migrations
203
216
  "db": _cmd_db_dispatch,
217
+ # V3.7.9 M-03 — terminal mesh inspection
218
+ "mesh": _cmd_mesh_dispatch,
204
219
  # V3.4.22 Stage 8 SB-5 — MASTER-PLAN §8 escape hatches.
205
220
  "disable": _cmd_escape_disable,
206
221
  "enable": _cmd_escape_enable,
@@ -218,6 +233,8 @@ def dispatch(args: Namespace) -> None:
218
233
  "compress": _cmd_compress,
219
234
  "proxy": _cmd_proxy,
220
235
  "help-optimize": _cmd_help_optimize,
236
+ # V3.8.0 bounded loops (gate-verified agent loops, SLM-backed ledger)
237
+ "loop": _cmd_loop,
221
238
  }
222
239
  handler = handlers.get(args.command)
223
240
  if handler:
@@ -345,6 +362,11 @@ def cmd_serve(args: Namespace) -> None:
345
362
  else:
346
363
  from superlocalmemory.infra.data_root import state_path
347
364
  print(f"Failed to start daemon. Check {state_path('logs', 'daemon.log')}")
365
+ # INT-H-02: exit non-zero so callers can detect the failure. The plugin
366
+ # launcher (plugin/scripts/slm-launch) guards on this exit code and
367
+ # refuses to open a direct MCP writer against a broken daemon; without
368
+ # the non-zero exit that guard was a dead no-op.
369
+ sys.exit(1)
348
370
 
349
371
 
350
372
  # -- Ingestion Adapters (V3.4.3) ------------------------------------------
@@ -610,6 +632,8 @@ def cmd_config(args: Namespace) -> None:
610
632
  elif action == "set":
611
633
  _ALLOWED_CONFIG_KEYS = {
612
634
  "evolution.enabled", "evolution.backend", "evolution.max_evolutions_per_cycle",
635
+ "evolution.mutation_model", "evolution.verify_model",
636
+ "evolution.confirm_model",
613
637
  "mesh_enabled", "daemon_idle_timeout", "entity_compilation_enabled",
614
638
  "graph_backend", "vector_backend", "scale_engine_state",
615
639
  "scope.default_scope", "scope.recall_include_global",
@@ -677,6 +701,31 @@ def cmd_config(args: Namespace) -> None:
677
701
  print(f"Error: {message}")
678
702
  sys.exit(1)
679
703
 
704
+ _MODEL_KEYS = {
705
+ "evolution.mutation_model", "evolution.verify_model",
706
+ "evolution.confirm_model",
707
+ }
708
+ if key in _MODEL_KEYS:
709
+ from superlocalmemory.evolution.model_selection import _MODEL_ALIASES
710
+
711
+ accepted = set(_MODEL_ALIASES) | {"", "auto"}
712
+ sval = str(parsed_value)
713
+ if sval not in accepted:
714
+ allowed = ", ".join(["auto", *sorted(_MODEL_ALIASES)])
715
+ message = f"{key} must be one of: {allowed}"
716
+ if use_json:
717
+ from superlocalmemory.cli.json_output import json_print
718
+ json_print("config", error={
719
+ "code": "INVALID_VALUE", "message": message,
720
+ })
721
+ else:
722
+ print(f"Error: {message}")
723
+ sys.exit(1)
724
+ # Normalise the "auto" sentinel to the empty string the
725
+ # resolver treats as "pick the cheapest for the backend".
726
+ if sval == "auto":
727
+ parsed_value = ""
728
+
680
729
  if key.startswith("scope."):
681
730
  from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
682
731
 
@@ -727,6 +776,18 @@ def cmd_config(args: Namespace) -> None:
727
776
  })
728
777
  else:
729
778
  print(f"{key}: {old_value} -> {parsed_value}")
779
+ if key == "evolution.enabled" and parsed_value is True:
780
+ print(
781
+ "\n⚠ Skill evolution is now ON. It makes background "
782
+ "LLM calls during consolidation\n"
783
+ " (capped at 10 calls/cycle, 3 cycles/day). It defaults "
784
+ "to the lowest-cost model\n"
785
+ " for your backend (Claude → Haiku, Ollama → "
786
+ "local/free).\n"
787
+ " Pick models: slm config set evolution.mutation_model "
788
+ "<auto|haiku|sonnet|ollama>\n"
789
+ " Turn off: slm config set evolution.enabled false"
790
+ )
730
791
 
731
792
  else:
732
793
  if use_json:
@@ -1271,10 +1332,12 @@ def cmd_recall(args: Namespace) -> None:
1271
1332
  scope_qs += f"&include_global={str(include_global).lower()}"
1272
1333
  if include_shared is not None:
1273
1334
  scope_qs += f"&include_shared={str(include_shared).lower()}"
1335
+ _window = getattr(args, "window", "") or ""
1336
+ window_qs = f"&window={quote(_window)}" if _window else ""
1274
1337
  result = daemon_request(
1275
1338
  "GET",
1276
1339
  f"/recall?q={quote(args.query)}&limit={args.limit}"
1277
- f"&session_id={quote(session_id)}{fast_qs}{scope_qs}",
1340
+ f"&session_id={quote(session_id)}{fast_qs}{scope_qs}{window_qs}",
1278
1341
  )
1279
1342
  if result and "results" in result:
1280
1343
  # Format daemon response same as engine response
@@ -1294,8 +1357,10 @@ def cmd_recall(args: Namespace) -> None:
1294
1357
  for i, r in enumerate(result["results"], 1):
1295
1358
  print(f" {i}. [{r['score']:.2f}] {r['content']}")
1296
1359
  return
1297
- except Exception:
1298
- pass # Fall through to direct engine
1360
+ except Exception as _exc: # noqa: BLE001
1361
+ logger.warning(
1362
+ "Daemon recall failed, falling back to direct engine: %s", _exc
1363
+ )
1299
1364
 
1300
1365
  from superlocalmemory.core.config import SLMConfig
1301
1366
  from superlocalmemory.core.engine import MemoryEngine
@@ -1310,6 +1375,7 @@ def cmd_recall(args: Namespace) -> None:
1310
1375
  fast=getattr(args, "fast", False),
1311
1376
  include_global=include_global,
1312
1377
  include_shared=include_shared,
1378
+ window=getattr(args, "window", "") or None,
1313
1379
  )
1314
1380
  except Exception as exc:
1315
1381
  if use_json:
@@ -1394,12 +1460,32 @@ def cmd_forget(args: Namespace) -> None:
1394
1460
  from superlocalmemory.core.config import SLMConfig
1395
1461
 
1396
1462
  use_json = getattr(args, 'json', False)
1463
+ dry_run = getattr(args, 'dry_run', False)
1464
+ raw_query = getattr(args, 'query', None)
1465
+
1466
+ # F3: `query` is optional so `slm forget --dry-run` can preview every memory.
1467
+ # Deletion ALWAYS requires an explicit query — a bare `slm forget` must never
1468
+ # mass-delete. In dry-run mode a missing query means "match all" (preview).
1469
+ if raw_query is None and not dry_run:
1470
+ msg = (
1471
+ "A query is required to delete. Preview everything with "
1472
+ "'slm forget --dry-run', or delete matches with 'slm forget <query>'."
1473
+ )
1474
+ if use_json:
1475
+ from superlocalmemory.cli.json_output import json_print
1476
+ json_print("forget", error={"code": "QUERY_REQUIRED", "message": msg})
1477
+ else:
1478
+ print(msg)
1479
+ sys.exit(2)
1480
+
1481
+ query_lower = "" if raw_query is None else raw_query.lower()
1482
+ query_label = raw_query if raw_query is not None else "*all*"
1483
+
1397
1484
  try:
1398
1485
  config = SLMConfig.load()
1399
1486
  engine = MemoryEngine(config)
1400
1487
  engine.initialize()
1401
1488
  facts = engine._db.get_all_facts(engine.profile_id)
1402
- query_lower = args.query.lower()
1403
1489
  matches = [f for f in facts if query_lower in f.content.lower()]
1404
1490
  except Exception as exc:
1405
1491
  if use_json:
@@ -1408,8 +1494,6 @@ def cmd_forget(args: Namespace) -> None:
1408
1494
  sys.exit(1)
1409
1495
  raise
1410
1496
 
1411
- dry_run = getattr(args, 'dry_run', False)
1412
-
1413
1497
  def delete_fact_authorized_for_cli(fact_id: str) -> None:
1414
1498
  from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
1415
1499
  from superlocalmemory.core.mutations import delete_fact_authorized
@@ -1450,12 +1534,12 @@ def cmd_forget(args: Namespace) -> None:
1450
1534
  "matches": match_items,
1451
1535
  "hint": "Add --yes to confirm deletion",
1452
1536
  }, next_actions=[
1453
- {"command": f"slm forget '{args.query}' --json --yes", "description": "Confirm deletion"},
1537
+ {"command": f"slm forget '{query_label}' --json --yes", "description": "Confirm deletion"},
1454
1538
  ])
1455
1539
  return
1456
1540
 
1457
1541
  if not matches:
1458
- print(f"No memories matching '{args.query}'")
1542
+ print(f"No memories matching '{query_label}'")
1459
1543
  return
1460
1544
  print(f"Found {len(matches)} matching memories:")
1461
1545
  for f in matches[:10]:
@@ -1835,8 +1919,8 @@ def cmd_health(args: Namespace) -> None:
1835
1919
 
1836
1920
  print("Math Layer Health:")
1837
1921
  print(f" Total facts: {len(facts)}")
1838
- print(f" Fisher-Rao indexed: {fisher_count}/{len(facts)}")
1839
- print(f" Langevin positioned: {langevin_count}/{len(facts)}")
1922
+ print(f" Math layer indexed: {fisher_count}/{len(facts)}")
1923
+ print(f" Lifecycle positioned: {langevin_count}/{len(facts)}")
1840
1924
  print(f" Mode: {config.mode.value.upper()}")
1841
1925
 
1842
1926
 
@@ -1999,7 +2083,7 @@ def cmd_doctor(args: Namespace) -> None:
1999
2083
 
2000
2084
  # 3. Search deps
2001
2085
  search_mods = {"sentence_transformers": "sentence-transformers", "torch": "torch",
2002
- "sklearn": "scikit-learn", "geoopt": "geoopt"}
2086
+ "sklearn": "scikit-learn"}
2003
2087
  search_ok = []
2004
2088
  for mod, pkg in search_mods.items():
2005
2089
  try:
@@ -2008,7 +2092,7 @@ def cmd_doctor(args: Namespace) -> None:
2008
2092
  except Exception: # dependency import may fail after module discovery
2009
2093
  pass
2010
2094
  if len(search_ok) == len(search_mods):
2011
- _check("Search deps", "PASS", "sentence-transformers, torch, sklearn, geoopt")
2095
+ _check("Search deps", "PASS", "sentence-transformers, torch, sklearn")
2012
2096
  else:
2013
2097
  missing = set(search_mods) - set(search_ok)
2014
2098
  _check("Search deps", "WARN", f"Missing: {', '.join(missing)}",
@@ -118,8 +118,13 @@ def cmd_compress_prose(args: Namespace) -> None:
118
118
  cfg = store.get()
119
119
 
120
120
  fields: dict = {"compress_prose": (value == "on")}
121
- if value == "on" and not cfg.compress_enabled:
122
- fields["compress_enabled"] = True
121
+ if value == "on":
122
+ # Prose (Layer 2) only fires in aggressive mode, so turning it on sets a
123
+ # COHERENT state — it can never be left enabled-but-inert in safe mode.
124
+ if not cfg.compress_enabled:
125
+ fields["compress_enabled"] = True
126
+ if cfg.compress_mode != "aggressive":
127
+ fields["compress_mode"] = "aggressive"
123
128
 
124
129
  try:
125
130
  cfg = dataclasses.replace(cfg, **fields)
@@ -129,13 +134,18 @@ def cmd_compress_prose(args: Namespace) -> None:
129
134
  sys.exit(1)
130
135
 
131
136
  if use_json:
132
- print(json.dumps({"status": "ok", "compress_prose": value == "on"}))
137
+ print(json.dumps({
138
+ "status": "ok",
139
+ "compress_prose": value == "on",
140
+ "compress_mode": cfg.compress_mode,
141
+ }))
133
142
  return
134
143
 
135
144
  print(f"Prose compression (LLMLingua-2): {'ENABLED' if value == 'on' else 'DISABLED'}.")
136
145
  if value == "on":
137
- print(" Requires: compress_mode=aggressive and llmlingua package installed.")
138
- print(" Run: slm compress mode aggressive (if not already set)")
139
- if value == "on" and "compress_enabled" in fields:
140
- print(" (also enabled global compress)")
146
+ if "compress_mode" in fields:
147
+ print(" Compression mode set to 'aggressive' (required for Layer 2).")
148
+ print(" Requires the llmlingua package for lossy prose compression.")
149
+ if "compress_enabled" in fields:
150
+ print(" (also enabled global compression)")
141
151
  print("Daemon hot-reload: active within 2s.")
@@ -0,0 +1,192 @@
1
+ """``slm loop`` — inspect and demonstrate bounded loops backed by SLM memory.
2
+
3
+ Subcommands:
4
+
5
+ * ``slm loop demo`` run a built-in, keyless convergence demo (stub
6
+ proposer + deterministic gate) and persist every
7
+ lap to SLM memory. Proves the engine + ledger
8
+ end to end without a credentialed agent.
9
+ * ``slm loop history [--name]`` list recorded loop runs from SLM memory.
10
+ * ``slm loop show <run_id>`` show every lap of one run.
11
+
12
+ The durable value here is that a loop's history lives in the same SLM data
13
+ root as everything else the agent remembers — queryable via ``slm recall`` and
14
+ visible in the dashboard. Reads and the demo run through an in-process engine
15
+ store rooted at the active data root (``SLM_DATA_DIR`` or
16
+ ``~/.superlocalmemory``); point ``SLM_DATA_DIR`` elsewhere to sandbox a run.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ from argparse import Namespace
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ from superlocalmemory.loops import (
28
+ Bounds,
29
+ LapResult,
30
+ SLMMemoryLedger,
31
+ Verdict,
32
+ open_engine_store,
33
+ run_bounded_loop,
34
+ )
35
+
36
+
37
+ def _data_root() -> Path:
38
+ value = (
39
+ os.environ.get("SLM_DATA_DIR")
40
+ or os.environ.get("SL_MEMORY_PATH")
41
+ or os.environ.get("SLM_HOME")
42
+ )
43
+ return Path(value).expanduser() if value else Path.home() / ".superlocalmemory"
44
+
45
+
46
+ class _FailOpenLedger:
47
+ """Wrap a ledger so a memory write hiccup never aborts a running loop.
48
+
49
+ A ledger is observability; the loop's correctness comes from the gate.
50
+ Write errors are counted and surfaced after the run rather than raised
51
+ mid-flight, so we never silently pretend they did not happen.
52
+ """
53
+
54
+ def __init__(self, inner: Any) -> None:
55
+ self._inner = inner
56
+ self.write_errors: list[str] = []
57
+
58
+ def record(self, entry: Any) -> None:
59
+ try:
60
+ self._inner.record(entry)
61
+ except Exception as exc: # pragma: no cover - defensive path
62
+ self.write_errors.append(f"lap {getattr(entry, 'lap', '?')}: {exc}")
63
+
64
+ def laps(self, run_id: str) -> list:
65
+ return self._inner.laps(run_id)
66
+
67
+ def runs(self, name: str) -> list:
68
+ return self._inner.runs(name)
69
+
70
+
71
+ def _open_ledger() -> tuple[SLMMemoryLedger, Any]:
72
+ store = open_engine_store(_data_root() / "memory.db")
73
+ return SLMMemoryLedger(store), store
74
+
75
+
76
+ def cmd_loop(args: Namespace) -> None:
77
+ action = getattr(args, "loop_command", None)
78
+ if action == "demo":
79
+ _cmd_demo(args)
80
+ elif action == "history":
81
+ _cmd_history(args)
82
+ elif action == "show":
83
+ _cmd_show(args)
84
+ else:
85
+ print("Usage: slm loop {demo|history|show} [options]")
86
+
87
+
88
+ def _cmd_demo(args: Namespace) -> None:
89
+ """Run the convergence demo: the gate fails twice, then passes on lap 3."""
90
+ iterations = int(getattr(args, "iterations", 10) or 10)
91
+ as_json = bool(getattr(args, "json", False))
92
+ pass_on = 3
93
+
94
+ ledger, store = _open_ledger()
95
+ guarded = _FailOpenLedger(ledger)
96
+ try:
97
+ outcome = run_bounded_loop(
98
+ "convergence-demo",
99
+ bounds=Bounds(max_iterations=iterations),
100
+ runner=lambda lap: LapResult(changed=True, tokens=8),
101
+ gate=lambda lap: Verdict(lap >= pass_on, f"demo gate: lap {lap}"),
102
+ ledger=guarded,
103
+ )
104
+ laps = guarded.laps(outcome.run_id)
105
+ finally:
106
+ store.close()
107
+
108
+ if as_json:
109
+ print(json.dumps({
110
+ "status": outcome.status.value,
111
+ "reason": outcome.reason,
112
+ "laps": outcome.laps,
113
+ "run_id": outcome.run_id,
114
+ "ledger": [
115
+ {"lap": e.lap, "decision": e.decision, "passed": e.passed}
116
+ for e in laps
117
+ ],
118
+ "write_errors": guarded.write_errors,
119
+ }, indent=2))
120
+ return
121
+
122
+ mark = "✓" if outcome.ok else "✗"
123
+ print(f"{mark} [{outcome.status.value}] {outcome.reason} (laps: {outcome.laps})")
124
+ for e in laps:
125
+ gate = "gate-pass" if e.passed else "gate-fail"
126
+ print(f" lap {e.lap}: {e.decision:<8} {gate} {e.detail}")
127
+ print(f"run_id: {outcome.run_id} (recall with tag loop:convergence-demo)")
128
+ if guarded.write_errors:
129
+ print(f"WARNING: {len(guarded.write_errors)} ledger write error(s): "
130
+ f"{guarded.write_errors[0]}")
131
+
132
+
133
+ def _cmd_history(args: Namespace) -> None:
134
+ name = getattr(args, "name", None) or "convergence-demo"
135
+ as_json = bool(getattr(args, "json", False))
136
+ ledger, store = _open_ledger()
137
+ try:
138
+ run_ids = ledger.runs(name)
139
+ rows = []
140
+ for rid in run_ids:
141
+ laps = ledger.laps(rid)
142
+ last = laps[-1] if laps else None
143
+ rows.append({
144
+ "run_id": rid,
145
+ "laps": len(laps),
146
+ "final": last.decision if last else "unknown",
147
+ "ts": last.ts if last else "",
148
+ })
149
+ finally:
150
+ store.close()
151
+
152
+ if as_json:
153
+ print(json.dumps({"name": name, "runs": rows}, indent=2))
154
+ return
155
+ if not rows:
156
+ print(f"No recorded runs for loop '{name}'.")
157
+ return
158
+ print(f"Runs for loop '{name}':")
159
+ for r in rows:
160
+ print(f" {r['run_id']:<28} laps={r['laps']:<3} final={r['final']:<8} {r['ts']}")
161
+
162
+
163
+ def _cmd_show(args: Namespace) -> None:
164
+ run_id = getattr(args, "run_id", None)
165
+ as_json = bool(getattr(args, "json", False))
166
+ if not run_id:
167
+ print("Usage: slm loop show <run_id>")
168
+ return
169
+ ledger, store = _open_ledger()
170
+ try:
171
+ laps = ledger.laps(run_id)
172
+ finally:
173
+ store.close()
174
+
175
+ if as_json:
176
+ print(json.dumps({
177
+ "run_id": run_id,
178
+ "laps": [
179
+ {"lap": e.lap, "ts": e.ts, "decision": e.decision,
180
+ "passed": e.passed, "detail": e.detail, "budget": e.budget}
181
+ for e in laps
182
+ ],
183
+ }, indent=2))
184
+ return
185
+ if not laps:
186
+ print(f"No ledger entries for run '{run_id}'.")
187
+ return
188
+ print(f"Loop run {run_id} ({laps[0].name}):")
189
+ for e in laps:
190
+ gate = "gate-pass" if e.passed else "gate-fail"
191
+ print(f" lap {e.lap}: {e.decision:<8} {gate} {e.detail} "
192
+ f"[tokens={e.budget.get('tokens', 0)}]")
@@ -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
  }
@@ -276,6 +278,14 @@ def main() -> None:
276
278
  db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
277
279
  db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
278
280
 
281
+ # -- Mesh inspection (v3.7.9, M-03) --------------------------------
282
+ mesh_p = sub.add_parser("mesh", help="Inspect the local agent mesh (status/peers)")
283
+ mesh_p.add_argument(
284
+ "mesh_action",
285
+ choices=("status", "peers"),
286
+ help="status: broker health + stats; peers: active peer sessions",
287
+ )
288
+
279
289
  # -- Memory Operations ---------------------------------------------
280
290
  remember_p = sub.add_parser("remember", help="Store a memory (extracts facts, builds graph)")
281
291
  remember_p.add_argument("content", help="Content to remember")
@@ -304,9 +314,15 @@ def main() -> None:
304
314
  help=f"Max results (default {CANONICAL_RECALL_LIMIT})",
305
315
  )
306
316
  recall_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
317
+ recall_p.add_argument(
318
+ "--window", default="",
319
+ help="Restrict results to an event-time range: a relative span "
320
+ "(24h, 7d, 30d, 1y) or an explicit range (2026-07-01..2026-07-31). "
321
+ "Default: no time filter.",
322
+ )
307
323
  recall_p.add_argument(
308
324
  "--fast", action="store_true",
309
- help="Skip spreading activation and remote agentic verification for a "
325
+ help="Skip graph-assisted candidate expansion and remote agentic verification for a "
310
326
  "latency-bounded response. Other configured retrieval channels still run. "
311
327
  "Use when you need recall before a tool call (e.g. before WebSearch).",
312
328
  )
@@ -332,8 +348,8 @@ def main() -> None:
332
348
  )
333
349
 
334
350
  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")
351
+ forget_p.add_argument("query", nargs="?", default=None, help="Query to match for deletion. Optional with --dry-run (previews all memories).")
352
+ forget_p.add_argument("--dry-run", action="store_true", default=False, help="Preview matches without deleting. With no query, previews every memory.")
337
353
  forget_p.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
338
354
  forget_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
339
355
 
@@ -361,7 +377,7 @@ def main() -> None:
361
377
  help="Show extended status: migration log, daemon port, disabled marker, last version",
362
378
  )
363
379
 
364
- health_p = sub.add_parser("health", help="Math layer health (Fisher-Rao, Sheaf, Langevin)")
380
+ health_p = sub.add_parser("health", help="Math layer health (scoring, consistency, and lifecycle layers)")
365
381
  health_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
366
382
 
367
383
  trace_p = sub.add_parser("trace", help="Recall with per-channel score breakdown")
@@ -774,6 +790,25 @@ def main() -> None:
774
790
 
775
791
  # ---- end SLM v3.6 Optimize subcommands ----
776
792
 
793
+ # slm loop demo|history|show — bounded, gate-verified agent loops (v3.8.0)
794
+ loop_p = sub.add_parser(
795
+ "loop",
796
+ help="Bounded loops: gate-verified agent loops with an SLM-backed ledger",
797
+ )
798
+ loop_sub = loop_p.add_subparsers(dest="loop_command", title="loop subcommands")
799
+ loop_demo_p = loop_sub.add_parser(
800
+ "demo", help="Run the keyless convergence demo (proves engine + ledger)")
801
+ loop_demo_p.add_argument(
802
+ "--iterations", type=int, default=10, help="Max iterations (default: 10)")
803
+ loop_hist_p = loop_sub.add_parser("history", help="List recorded loop runs")
804
+ loop_hist_p.add_argument(
805
+ "--name", default=None, help="Loop name (default: convergence-demo)")
806
+ loop_show_p = loop_sub.add_parser("show", help="Show every lap of one run")
807
+ loop_show_p.add_argument("run_id", help="Run id (from history)")
808
+ for _sp in loop_sub.choices.values():
809
+ _sp.add_argument("--json", action="store_true",
810
+ help="Output structured JSON (agent-native)")
811
+
777
812
  args = parser.parse_args()
778
813
 
779
814
  if not args.command:
@@ -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})")