superlocalmemory 4.1.13 → 4.1.14

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 (740) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +3 -3
  4. package/package.json +1 -8
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/CLAUDE.md +3 -3
  7. package/plugin/agents/slm-governance-advisor.md +1 -1
  8. package/plugin/agents/slm-loop-runner.md +1 -1
  9. package/plugin/agents/slm-memory-advisor.md +1 -1
  10. package/plugin/agents/slm-optimize-advisor.md +1 -1
  11. package/plugin/requirements.txt +1 -1
  12. package/plugin/skills/slm-cache/SKILL.md +1 -1
  13. package/plugin/skills/slm-compress/SKILL.md +1 -1
  14. package/plugin/skills/slm-governance/SKILL.md +1 -1
  15. package/plugin/skills/slm-graph/SKILL.md +1 -1
  16. package/plugin/skills/slm-loop/SKILL.md +1 -1
  17. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  18. package/plugin/skills/slm-profile/SKILL.md +1 -1
  19. package/plugin/skills/slm-recall/SKILL.md +1 -1
  20. package/plugin/skills/slm-remember/SKILL.md +1 -1
  21. package/plugin/skills/slm-scope/SKILL.md +1 -1
  22. package/plugin/skills/slm-session/SKILL.md +1 -1
  23. package/plugin/skills/slm-status/SKILL.md +1 -1
  24. package/plugin-src/agents/slm-memory-advisor.md +1 -1
  25. package/plugin-src/agents/slm-optimize-advisor.md +1 -1
  26. package/plugin-src/rules/AGENTS.md +1 -1
  27. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  31. package/plugin-src/skills/slm-loop/SKILL.md +1 -1
  32. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  33. package/plugin-src/skills/slm-profile/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  36. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  37. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  38. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  39. package/scripts/postinstall.js +71 -2
  40. package/pyproject.toml +0 -250
  41. package/src/superlocalmemory/__init__.py +0 -82
  42. package/src/superlocalmemory/access/__init__.py +0 -3
  43. package/src/superlocalmemory/access/rbac.py +0 -575
  44. package/src/superlocalmemory/attribution/__init__.py +0 -9
  45. package/src/superlocalmemory/attribution/signer.py +0 -173
  46. package/src/superlocalmemory/attribution/watermark.py +0 -189
  47. package/src/superlocalmemory/brain/__init__.py +0 -5
  48. package/src/superlocalmemory/brain/truth.py +0 -418
  49. package/src/superlocalmemory/cli/__init__.py +0 -5
  50. package/src/superlocalmemory/cli/__main__.py +0 -17
  51. package/src/superlocalmemory/cli/_lazy_init.py +0 -115
  52. package/src/superlocalmemory/cli/cache_cmd.py +0 -198
  53. package/src/superlocalmemory/cli/commands.py +0 -4710
  54. package/src/superlocalmemory/cli/compress_cmd.py +0 -151
  55. package/src/superlocalmemory/cli/context_commands.py +0 -193
  56. package/src/superlocalmemory/cli/daemon.py +0 -909
  57. package/src/superlocalmemory/cli/db_migrate.py +0 -150
  58. package/src/superlocalmemory/cli/diagnostics_cmd.py +0 -101
  59. package/src/superlocalmemory/cli/escape_hatch.py +0 -220
  60. package/src/superlocalmemory/cli/evidence_cmd.py +0 -103
  61. package/src/superlocalmemory/cli/gdpr_cmd.py +0 -792
  62. package/src/superlocalmemory/cli/gdpr_io.py +0 -109
  63. package/src/superlocalmemory/cli/help_cmd.py +0 -197
  64. package/src/superlocalmemory/cli/host_upgrades.py +0 -189
  65. package/src/superlocalmemory/cli/ingest_cmd.py +0 -327
  66. package/src/superlocalmemory/cli/json_output.py +0 -81
  67. package/src/superlocalmemory/cli/loop_cmd.py +0 -187
  68. package/src/superlocalmemory/cli/main.py +0 -1145
  69. package/src/superlocalmemory/cli/mesh_cmd.py +0 -38
  70. package/src/superlocalmemory/cli/migrate_cmd.py +0 -55
  71. package/src/superlocalmemory/cli/ops_cmd.py +0 -281
  72. package/src/superlocalmemory/cli/optimize_cmd.py +0 -179
  73. package/src/superlocalmemory/cli/optimize_constants.py +0 -31
  74. package/src/superlocalmemory/cli/pending_store.py +0 -296
  75. package/src/superlocalmemory/cli/proxy_cmd.py +0 -108
  76. package/src/superlocalmemory/cli/scale_engine_cmd.py +0 -56
  77. package/src/superlocalmemory/cli/service_installer.py +0 -373
  78. package/src/superlocalmemory/cli/setup_wizard.py +0 -1162
  79. package/src/superlocalmemory/cli/summary_cmd.py +0 -215
  80. package/src/superlocalmemory/cli/version_banner.py +0 -202
  81. package/src/superlocalmemory/cli/wizard_v3426_options.py +0 -129
  82. package/src/superlocalmemory/code_graph/__init__.py +0 -46
  83. package/src/superlocalmemory/code_graph/blast_radius.py +0 -177
  84. package/src/superlocalmemory/code_graph/bridge/__init__.py +0 -36
  85. package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +0 -490
  86. package/src/superlocalmemory/code_graph/bridge/event_listeners.py +0 -206
  87. package/src/superlocalmemory/code_graph/bridge/fact_enricher.py +0 -159
  88. package/src/superlocalmemory/code_graph/bridge/hebbian_linker.py +0 -170
  89. package/src/superlocalmemory/code_graph/bridge/maintenance.py +0 -220
  90. package/src/superlocalmemory/code_graph/bridge/temporal_checker.py +0 -152
  91. package/src/superlocalmemory/code_graph/changes.py +0 -363
  92. package/src/superlocalmemory/code_graph/communities.py +0 -299
  93. package/src/superlocalmemory/code_graph/config.py +0 -154
  94. package/src/superlocalmemory/code_graph/database.py +0 -526
  95. package/src/superlocalmemory/code_graph/extractors/__init__.py +0 -95
  96. package/src/superlocalmemory/code_graph/extractors/python.py +0 -413
  97. package/src/superlocalmemory/code_graph/extractors/typescript.py +0 -556
  98. package/src/superlocalmemory/code_graph/flows.py +0 -350
  99. package/src/superlocalmemory/code_graph/git_hooks.py +0 -226
  100. package/src/superlocalmemory/code_graph/graph_engine.py +0 -295
  101. package/src/superlocalmemory/code_graph/graph_store.py +0 -335
  102. package/src/superlocalmemory/code_graph/incremental.py +0 -200
  103. package/src/superlocalmemory/code_graph/models.py +0 -130
  104. package/src/superlocalmemory/code_graph/parser.py +0 -687
  105. package/src/superlocalmemory/code_graph/resolver.py +0 -321
  106. package/src/superlocalmemory/code_graph/search.py +0 -460
  107. package/src/superlocalmemory/code_graph/service.py +0 -95
  108. package/src/superlocalmemory/code_graph/watcher.py +0 -207
  109. package/src/superlocalmemory/compliance/__init__.py +0 -0
  110. package/src/superlocalmemory/compliance/abac.py +0 -204
  111. package/src/superlocalmemory/compliance/audit.py +0 -385
  112. package/src/superlocalmemory/compliance/eu_ai_act.py +0 -101
  113. package/src/superlocalmemory/compliance/gdpr.py +0 -1479
  114. package/src/superlocalmemory/compliance/lifecycle.py +0 -158
  115. package/src/superlocalmemory/compliance/retention.py +0 -415
  116. package/src/superlocalmemory/compliance/scheduler.py +0 -217
  117. package/src/superlocalmemory/contracts/__init__.py +0 -1
  118. package/src/superlocalmemory/contracts/schemas/agent-experience-v1.schema.json +0 -92
  119. package/src/superlocalmemory/contracts/schemas/agent-integration-contract-v2.schema.json +0 -46
  120. package/src/superlocalmemory/contracts/schemas/cognitive-turn-receipt-v1.schema.json +0 -59
  121. package/src/superlocalmemory/contracts/v402.py +0 -62
  122. package/src/superlocalmemory/core/__init__.py +0 -0
  123. package/src/superlocalmemory/core/actor_context.py +0 -166
  124. package/src/superlocalmemory/core/admission.py +0 -769
  125. package/src/superlocalmemory/core/backend_orchestrator.py +0 -637
  126. package/src/superlocalmemory/core/block_hygiene.py +0 -147
  127. package/src/superlocalmemory/core/community_summary.py +0 -267
  128. package/src/superlocalmemory/core/component_healer.py +0 -144
  129. package/src/superlocalmemory/core/component_registry.py +0 -514
  130. package/src/superlocalmemory/core/config.py +0 -2204
  131. package/src/superlocalmemory/core/consolidation_engine.py +0 -983
  132. package/src/superlocalmemory/core/context_cache.py +0 -574
  133. package/src/superlocalmemory/core/derivation_lineage.py +0 -246
  134. package/src/superlocalmemory/core/embedding_worker.py +0 -208
  135. package/src/superlocalmemory/core/embeddings.py +0 -1052
  136. package/src/superlocalmemory/core/engine.py +0 -1395
  137. package/src/superlocalmemory/core/engine_capabilities.py +0 -24
  138. package/src/superlocalmemory/core/engine_ingestion.py +0 -983
  139. package/src/superlocalmemory/core/engine_lock.py +0 -75
  140. package/src/superlocalmemory/core/engine_wiring.py +0 -776
  141. package/src/superlocalmemory/core/entity_community.py +0 -178
  142. package/src/superlocalmemory/core/error_envelope.py +0 -60
  143. package/src/superlocalmemory/core/evidence_bundle.py +0 -528
  144. package/src/superlocalmemory/core/fact_consolidator.py +0 -812
  145. package/src/superlocalmemory/core/file_lock.py +0 -92
  146. package/src/superlocalmemory/core/graph_analyzer.py +0 -456
  147. package/src/superlocalmemory/core/graph_metrics.py +0 -597
  148. package/src/superlocalmemory/core/graph_pruner.py +0 -939
  149. package/src/superlocalmemory/core/health_monitor.py +0 -338
  150. package/src/superlocalmemory/core/hooks.py +0 -65
  151. package/src/superlocalmemory/core/ingest_gate.py +0 -133
  152. package/src/superlocalmemory/core/ingest_policy.py +0 -38
  153. package/src/superlocalmemory/core/ingestion_command.py +0 -1042
  154. package/src/superlocalmemory/core/injection.py +0 -434
  155. package/src/superlocalmemory/core/install_detector.py +0 -131
  156. package/src/superlocalmemory/core/key_expander.py +0 -138
  157. package/src/superlocalmemory/core/lifecycle_state.py +0 -153
  158. package/src/superlocalmemory/core/maintenance.py +0 -777
  159. package/src/superlocalmemory/core/maintenance_scheduler.py +0 -507
  160. package/src/superlocalmemory/core/materialization_control.py +0 -20
  161. package/src/superlocalmemory/core/mcp_embedder_proxy.py +0 -89
  162. package/src/superlocalmemory/core/memory_health.py +0 -266
  163. package/src/superlocalmemory/core/mode_capability.py +0 -111
  164. package/src/superlocalmemory/core/modes.py +0 -168
  165. package/src/superlocalmemory/core/mutations.py +0 -688
  166. package/src/superlocalmemory/core/ollama_embedder.py +0 -266
  167. package/src/superlocalmemory/core/ollama_validator.py +0 -315
  168. package/src/superlocalmemory/core/operation_policy.py +0 -92
  169. package/src/superlocalmemory/core/operation_policy_registry.py +0 -542
  170. package/src/superlocalmemory/core/operation_request.py +0 -127
  171. package/src/superlocalmemory/core/ops_remediation.py +0 -542
  172. package/src/superlocalmemory/core/pii.py +0 -105
  173. package/src/superlocalmemory/core/platform_utils.py +0 -138
  174. package/src/superlocalmemory/core/profiles.py +0 -234
  175. package/src/superlocalmemory/core/progressive_abstraction.py +0 -208
  176. package/src/superlocalmemory/core/projection_drain.py +0 -380
  177. package/src/superlocalmemory/core/queue_consumer.py +0 -168
  178. package/src/superlocalmemory/core/ram_lock.py +0 -160
  179. package/src/superlocalmemory/core/rate_limit.py +0 -151
  180. package/src/superlocalmemory/core/recall_gate.py +0 -95
  181. package/src/superlocalmemory/core/recall_pipeline.py +0 -1337
  182. package/src/superlocalmemory/core/recall_queue.py +0 -377
  183. package/src/superlocalmemory/core/recall_worker.py +0 -414
  184. package/src/superlocalmemory/core/registry.py +0 -121
  185. package/src/superlocalmemory/core/remember_admission.py +0 -161
  186. package/src/superlocalmemory/core/remember_runtime.py +0 -1190
  187. package/src/superlocalmemory/core/remote_mode.py +0 -214
  188. package/src/superlocalmemory/core/reranker_worker.py +0 -338
  189. package/src/superlocalmemory/core/safe_fs.py +0 -108
  190. package/src/superlocalmemory/core/scale_autopromote.py +0 -196
  191. package/src/superlocalmemory/core/scale_engine.py +0 -915
  192. package/src/superlocalmemory/core/score_contract.py +0 -82
  193. package/src/superlocalmemory/core/security_primitives.py +0 -672
  194. package/src/superlocalmemory/core/session_identity.py +0 -98
  195. package/src/superlocalmemory/core/shadow_router.py +0 -319
  196. package/src/superlocalmemory/core/slm_disabled.py +0 -85
  197. package/src/superlocalmemory/core/status_contract.py +0 -108
  198. package/src/superlocalmemory/core/store_pipeline.py +0 -1404
  199. package/src/superlocalmemory/core/summarizer.py +0 -200
  200. package/src/superlocalmemory/core/tier_manager.py +0 -461
  201. package/src/superlocalmemory/core/topic_signature.py +0 -156
  202. package/src/superlocalmemory/core/transactions/__init__.py +0 -78
  203. package/src/superlocalmemory/core/transactions/concrete_owners.py +0 -604
  204. package/src/superlocalmemory/core/transactions/erasure.py +0 -825
  205. package/src/superlocalmemory/core/transactions/manifest.py +0 -255
  206. package/src/superlocalmemory/core/transactions/manifest_key.py +0 -155
  207. package/src/superlocalmemory/core/transactions/obligations.py +0 -272
  208. package/src/superlocalmemory/core/transactions/owners.py +0 -114
  209. package/src/superlocalmemory/core/transactions/reconciler.py +0 -285
  210. package/src/superlocalmemory/core/transactions/service.py +0 -330
  211. package/src/superlocalmemory/core/worker_pool.py +0 -377
  212. package/src/superlocalmemory/core/working_memory.py +0 -288
  213. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  214. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +0 -374
  215. package/src/superlocalmemory/dynamics/eap_scheduler.py +0 -294
  216. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +0 -171
  217. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +0 -227
  218. package/src/superlocalmemory/encoding/__init__.py +0 -0
  219. package/src/superlocalmemory/encoding/auto_linker.py +0 -308
  220. package/src/superlocalmemory/encoding/cognitive_consolidator.py +0 -899
  221. package/src/superlocalmemory/encoding/consolidator.py +0 -472
  222. package/src/superlocalmemory/encoding/context_generator.py +0 -175
  223. package/src/superlocalmemory/encoding/emotional.py +0 -189
  224. package/src/superlocalmemory/encoding/entity_reflexion.py +0 -200
  225. package/src/superlocalmemory/encoding/entity_resolver.py +0 -687
  226. package/src/superlocalmemory/encoding/entropy_gate.py +0 -101
  227. package/src/superlocalmemory/encoding/fact_extractor.py +0 -877
  228. package/src/superlocalmemory/encoding/foresight.py +0 -93
  229. package/src/superlocalmemory/encoding/graph_builder.py +0 -346
  230. package/src/superlocalmemory/encoding/observation_builder.py +0 -177
  231. package/src/superlocalmemory/encoding/prospective_markers.py +0 -262
  232. package/src/superlocalmemory/encoding/scene_builder.py +0 -410
  233. package/src/superlocalmemory/encoding/signal_inference.py +0 -90
  234. package/src/superlocalmemory/encoding/temporal_parser.py +0 -432
  235. package/src/superlocalmemory/encoding/temporal_validator.py +0 -572
  236. package/src/superlocalmemory/encoding/type_router.py +0 -237
  237. package/src/superlocalmemory/evaluation/__init__.py +0 -13
  238. package/src/superlocalmemory/evaluation/calibration.py +0 -308
  239. package/src/superlocalmemory/evolution/__init__.py +0 -29
  240. package/src/superlocalmemory/evolution/blind_verifier.py +0 -122
  241. package/src/superlocalmemory/evolution/budget.py +0 -356
  242. package/src/superlocalmemory/evolution/evolution_store.py +0 -619
  243. package/src/superlocalmemory/evolution/llm_dispatch.py +0 -559
  244. package/src/superlocalmemory/evolution/model_selection.py +0 -175
  245. package/src/superlocalmemory/evolution/mutation_generator.py +0 -226
  246. package/src/superlocalmemory/evolution/skill_activator.py +0 -270
  247. package/src/superlocalmemory/evolution/skill_evolver.py +0 -928
  248. package/src/superlocalmemory/evolution/triggers.py +0 -376
  249. package/src/superlocalmemory/evolution/types.py +0 -114
  250. package/src/superlocalmemory/graph/__init__.py +0 -9
  251. package/src/superlocalmemory/graph/cozo_adjacency.py +0 -122
  252. package/src/superlocalmemory/graph/cozo_backend.py +0 -751
  253. package/src/superlocalmemory/hooks/__init__.py +0 -3
  254. package/src/superlocalmemory/hooks/_outcome_common.py +0 -523
  255. package/src/superlocalmemory/hooks/adapter_base.py +0 -347
  256. package/src/superlocalmemory/hooks/antigravity_adapter.py +0 -171
  257. package/src/superlocalmemory/hooks/auto_capture.py +0 -133
  258. package/src/superlocalmemory/hooks/auto_invoker.py +0 -521
  259. package/src/superlocalmemory/hooks/auto_parameterize.py +0 -147
  260. package/src/superlocalmemory/hooks/auto_recall.py +0 -191
  261. package/src/superlocalmemory/hooks/auto_recall_hook.py +0 -251
  262. package/src/superlocalmemory/hooks/before_web_hook.py +0 -131
  263. package/src/superlocalmemory/hooks/claude_code_hooks.py +0 -637
  264. package/src/superlocalmemory/hooks/codex_assets.py +0 -251
  265. package/src/superlocalmemory/hooks/codex_hooks.py +0 -186
  266. package/src/superlocalmemory/hooks/context_payload.py +0 -311
  267. package/src/superlocalmemory/hooks/copilot_adapter.py +0 -208
  268. package/src/superlocalmemory/hooks/cross_platform_connector.py +0 -90
  269. package/src/superlocalmemory/hooks/cursor_adapter.py +0 -173
  270. package/src/superlocalmemory/hooks/hook_daemon.py +0 -295
  271. package/src/superlocalmemory/hooks/hook_handlers.py +0 -822
  272. package/src/superlocalmemory/hooks/ide_connector.py +0 -246
  273. package/src/superlocalmemory/hooks/memory_protocol.py +0 -158
  274. package/src/superlocalmemory/hooks/portable_kit.py +0 -755
  275. package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -183
  276. package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +0 -351
  277. package/src/superlocalmemory/hooks/prewarm_auth.py +0 -187
  278. package/src/superlocalmemory/hooks/rules_engine.py +0 -99
  279. package/src/superlocalmemory/hooks/session_registry.py +0 -330
  280. package/src/superlocalmemory/hooks/stop_outcome_hook.py +0 -138
  281. package/src/superlocalmemory/hooks/sync_loop.py +0 -114
  282. package/src/superlocalmemory/hooks/topic_shift_hook.py +0 -302
  283. package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -131
  284. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +0 -210
  285. package/src/superlocalmemory/infra/__init__.py +0 -3
  286. package/src/superlocalmemory/infra/auth_middleware.py +0 -145
  287. package/src/superlocalmemory/infra/backup.py +0 -974
  288. package/src/superlocalmemory/infra/backup_obligations.py +0 -423
  289. package/src/superlocalmemory/infra/cache_manager.py +0 -267
  290. package/src/superlocalmemory/infra/cloud_backup.py +0 -788
  291. package/src/superlocalmemory/infra/daemon_identity.py +0 -300
  292. package/src/superlocalmemory/infra/data_root.py +0 -238
  293. package/src/superlocalmemory/infra/event_bus.py +0 -637
  294. package/src/superlocalmemory/infra/local_diagnostics.py +0 -327
  295. package/src/superlocalmemory/infra/pid_manager.py +0 -193
  296. package/src/superlocalmemory/infra/process_identity.py +0 -180
  297. package/src/superlocalmemory/infra/process_reaper.py +0 -624
  298. package/src/superlocalmemory/infra/rate_limiter.py +0 -228
  299. package/src/superlocalmemory/infra/self_heal.py +0 -401
  300. package/src/superlocalmemory/infra/version_integrity.py +0 -229
  301. package/src/superlocalmemory/ingestion/__init__.py +0 -13
  302. package/src/superlocalmemory/ingestion/adapter_manager.py +0 -255
  303. package/src/superlocalmemory/ingestion/base_adapter.py +0 -171
  304. package/src/superlocalmemory/ingestion/calendar_adapter.py +0 -349
  305. package/src/superlocalmemory/ingestion/credentials.py +0 -125
  306. package/src/superlocalmemory/ingestion/gmail_adapter.py +0 -378
  307. package/src/superlocalmemory/ingestion/parsers.py +0 -100
  308. package/src/superlocalmemory/ingestion/transcript_adapter.py +0 -161
  309. package/src/superlocalmemory/integrations/__init__.py +0 -1
  310. package/src/superlocalmemory/integrations/bounded_loops_mcp.py +0 -431
  311. package/src/superlocalmemory/integrations/bounded_loops_v051.py +0 -236
  312. package/src/superlocalmemory/learning/__init__.py +0 -0
  313. package/src/superlocalmemory/learning/adaptive.py +0 -172
  314. package/src/superlocalmemory/learning/arm_catalog.py +0 -97
  315. package/src/superlocalmemory/learning/assertion_miner.py +0 -403
  316. package/src/superlocalmemory/learning/bandit.py +0 -654
  317. package/src/superlocalmemory/learning/bandit_cache.py +0 -131
  318. package/src/superlocalmemory/learning/behavioral.py +0 -542
  319. package/src/superlocalmemory/learning/bootstrap.py +0 -298
  320. package/src/superlocalmemory/learning/consolidation_cycle.py +0 -398
  321. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +0 -115
  322. package/src/superlocalmemory/learning/consolidation_worker.py +0 -261
  323. package/src/superlocalmemory/learning/cross_project.py +0 -408
  324. package/src/superlocalmemory/learning/database.py +0 -698
  325. package/src/superlocalmemory/learning/dedup_hnsw.py +0 -413
  326. package/src/superlocalmemory/learning/engagement.py +0 -487
  327. package/src/superlocalmemory/learning/engagement_features.py +0 -279
  328. package/src/superlocalmemory/learning/ensemble.py +0 -309
  329. package/src/superlocalmemory/learning/entity_compiler.py +0 -356
  330. package/src/superlocalmemory/learning/fact_outcome_joins.py +0 -207
  331. package/src/superlocalmemory/learning/features.py +0 -138
  332. package/src/superlocalmemory/learning/feedback.py +0 -724
  333. package/src/superlocalmemory/learning/forgetting_scheduler.py +0 -375
  334. package/src/superlocalmemory/learning/hnsw_dedup.py +0 -69
  335. package/src/superlocalmemory/learning/labeler.py +0 -85
  336. package/src/superlocalmemory/learning/legacy_migration.py +0 -316
  337. package/src/superlocalmemory/learning/lightgbm_subprocess.py +0 -236
  338. package/src/superlocalmemory/learning/memory_merge.py +0 -175
  339. package/src/superlocalmemory/learning/model_cache.py +0 -267
  340. package/src/superlocalmemory/learning/model_rollback.py +0 -281
  341. package/src/superlocalmemory/learning/outcome_queue.py +0 -306
  342. package/src/superlocalmemory/learning/outcomes.py +0 -286
  343. package/src/superlocalmemory/learning/pattern_miner.py +0 -465
  344. package/src/superlocalmemory/learning/pattern_miner_constants.py +0 -90
  345. package/src/superlocalmemory/learning/pcos.py +0 -291
  346. package/src/superlocalmemory/learning/project_context.py +0 -366
  347. package/src/superlocalmemory/learning/propensity.py +0 -131
  348. package/src/superlocalmemory/learning/ranker.py +0 -300
  349. package/src/superlocalmemory/learning/ranker_common.py +0 -163
  350. package/src/superlocalmemory/learning/ranker_retrain_legacy.py +0 -210
  351. package/src/superlocalmemory/learning/ranker_retrain_online.py +0 -423
  352. package/src/superlocalmemory/learning/reward.py +0 -888
  353. package/src/superlocalmemory/learning/reward_archive.py +0 -223
  354. package/src/superlocalmemory/learning/reward_boost.py +0 -211
  355. package/src/superlocalmemory/learning/reward_from_outcomes.py +0 -365
  356. package/src/superlocalmemory/learning/reward_model.py +0 -144
  357. package/src/superlocalmemory/learning/reward_proxy.py +0 -578
  358. package/src/superlocalmemory/learning/shadow_test.py +0 -524
  359. package/src/superlocalmemory/learning/signal_kinds.py +0 -79
  360. package/src/superlocalmemory/learning/signal_worker.py +0 -268
  361. package/src/superlocalmemory/learning/signals.py +0 -646
  362. package/src/superlocalmemory/learning/skill_performance_miner.py +0 -422
  363. package/src/superlocalmemory/learning/source_quality.py +0 -828
  364. package/src/superlocalmemory/learning/trigram_index.py +0 -548
  365. package/src/superlocalmemory/learning/workflows.py +0 -309
  366. package/src/superlocalmemory/llm/__init__.py +0 -0
  367. package/src/superlocalmemory/llm/backbone.py +0 -364
  368. package/src/superlocalmemory/loops/__init__.py +0 -58
  369. package/src/superlocalmemory/loops/budget.py +0 -58
  370. package/src/superlocalmemory/loops/engine.py +0 -174
  371. package/src/superlocalmemory/loops/ledger.py +0 -298
  372. package/src/superlocalmemory/loops/models.py +0 -152
  373. package/src/superlocalmemory/loops/rules.py +0 -52
  374. package/src/superlocalmemory/math/__init__.py +0 -0
  375. package/src/superlocalmemory/math/ebbinghaus.py +0 -352
  376. package/src/superlocalmemory/math/fisher.py +0 -356
  377. package/src/superlocalmemory/math/fisher_quantized.py +0 -255
  378. package/src/superlocalmemory/math/hopfield.py +0 -282
  379. package/src/superlocalmemory/math/langevin.py +0 -411
  380. package/src/superlocalmemory/math/polar_quant.py +0 -414
  381. package/src/superlocalmemory/math/qjl.py +0 -115
  382. package/src/superlocalmemory/math/sheaf.py +0 -261
  383. package/src/superlocalmemory/math/turbo_quant.py +0 -318
  384. package/src/superlocalmemory/mcp/__init__.py +0 -0
  385. package/src/superlocalmemory/mcp/_daemon_proxy.py +0 -203
  386. package/src/superlocalmemory/mcp/_pool_adapter.py +0 -181
  387. package/src/superlocalmemory/mcp/_stdin_guard.py +0 -60
  388. package/src/superlocalmemory/mcp/agent_context.py +0 -115
  389. package/src/superlocalmemory/mcp/cli_fallback.py +0 -602
  390. package/src/superlocalmemory/mcp/http_transport.py +0 -85
  391. package/src/superlocalmemory/mcp/profiles.py +0 -154
  392. package/src/superlocalmemory/mcp/resources.py +0 -281
  393. package/src/superlocalmemory/mcp/server.py +0 -480
  394. package/src/superlocalmemory/mcp/session_binding.py +0 -98
  395. package/src/superlocalmemory/mcp/shared.py +0 -112
  396. package/src/superlocalmemory/mcp/tools.py +0 -18
  397. package/src/superlocalmemory/mcp/tools_active.py +0 -958
  398. package/src/superlocalmemory/mcp/tools_brain.py +0 -298
  399. package/src/superlocalmemory/mcp/tools_code_graph.py +0 -1717
  400. package/src/superlocalmemory/mcp/tools_context.py +0 -239
  401. package/src/superlocalmemory/mcp/tools_core.py +0 -1112
  402. package/src/superlocalmemory/mcp/tools_evolution.py +0 -343
  403. package/src/superlocalmemory/mcp/tools_learning.py +0 -393
  404. package/src/superlocalmemory/mcp/tools_loops.py +0 -345
  405. package/src/superlocalmemory/mcp/tools_mesh.py +0 -429
  406. package/src/superlocalmemory/mcp/tools_ops.py +0 -115
  407. package/src/superlocalmemory/mcp/tools_optimize.py +0 -322
  408. package/src/superlocalmemory/mcp/tools_summaries.py +0 -147
  409. package/src/superlocalmemory/mcp/tools_v28.py +0 -292
  410. package/src/superlocalmemory/mcp/tools_v3.py +0 -398
  411. package/src/superlocalmemory/mcp/tools_v33.py +0 -507
  412. package/src/superlocalmemory/mesh/__init__.py +0 -12
  413. package/src/superlocalmemory/mesh/broker.py +0 -812
  414. package/src/superlocalmemory/mesh/broker_security.py +0 -470
  415. package/src/superlocalmemory/mesh/discovery.py +0 -365
  416. package/src/superlocalmemory/mesh/lock_protocol.py +0 -313
  417. package/src/superlocalmemory/mesh/node_identity.py +0 -97
  418. package/src/superlocalmemory/mesh/outbox_remote.py +0 -429
  419. package/src/superlocalmemory/mesh/remote_sync.py +0 -829
  420. package/src/superlocalmemory/mesh/state_sync.py +0 -286
  421. package/src/superlocalmemory/migrations/__init__.py +0 -5
  422. package/src/superlocalmemory/migrations/v3_4_25_to_v3_4_26.py +0 -144
  423. package/src/superlocalmemory/optimize/NOTICE +0 -6
  424. package/src/superlocalmemory/optimize/__init__.py +0 -0
  425. package/src/superlocalmemory/optimize/adapters/__init__.py +0 -68
  426. package/src/superlocalmemory/optimize/adapters/_agent_registry.py +0 -120
  427. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +0 -112
  428. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +0 -122
  429. package/src/superlocalmemory/optimize/adapters/wrap.py +0 -228
  430. package/src/superlocalmemory/optimize/cache/__init__.py +0 -31
  431. package/src/superlocalmemory/optimize/cache/boundary_store.py +0 -488
  432. package/src/superlocalmemory/optimize/cache/centroid_store.py +0 -199
  433. package/src/superlocalmemory/optimize/cache/context_key.py +0 -67
  434. package/src/superlocalmemory/optimize/cache/exact.py +0 -88
  435. package/src/superlocalmemory/optimize/cache/invalidation.py +0 -36
  436. package/src/superlocalmemory/optimize/cache/key_builder.py +0 -111
  437. package/src/superlocalmemory/optimize/cache/manager.py +0 -737
  438. package/src/superlocalmemory/optimize/cache/semantic.py +0 -637
  439. package/src/superlocalmemory/optimize/cache/stampede.py +0 -50
  440. package/src/superlocalmemory/optimize/compress/__init__.py +0 -17
  441. package/src/superlocalmemory/optimize/compress/align.py +0 -159
  442. package/src/superlocalmemory/optimize/compress/ccr.py +0 -116
  443. package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +0 -71
  444. package/src/superlocalmemory/optimize/compress/router.py +0 -667
  445. package/src/superlocalmemory/optimize/config/__init__.py +0 -56
  446. package/src/superlocalmemory/optimize/config/defaults.py +0 -43
  447. package/src/superlocalmemory/optimize/config/schema.py +0 -327
  448. package/src/superlocalmemory/optimize/config/store.py +0 -270
  449. package/src/superlocalmemory/optimize/metrics/__init__.py +0 -8
  450. package/src/superlocalmemory/optimize/metrics/counters.py +0 -155
  451. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -87
  452. package/src/superlocalmemory/optimize/metrics/exporters.py +0 -77
  453. package/src/superlocalmemory/optimize/metrics/persistence.py +0 -115
  454. package/src/superlocalmemory/optimize/proxy/__init__.py +0 -28
  455. package/src/superlocalmemory/optimize/proxy/_helpers.py +0 -730
  456. package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +0 -375
  457. package/src/superlocalmemory/optimize/proxy/capture.py +0 -550
  458. package/src/superlocalmemory/optimize/proxy/gemini_surface.py +0 -528
  459. package/src/superlocalmemory/optimize/proxy/lifecycle.py +0 -126
  460. package/src/superlocalmemory/optimize/proxy/openai_surface.py +0 -465
  461. package/src/superlocalmemory/optimize/proxy/server.py +0 -199
  462. package/src/superlocalmemory/optimize/proxy/vertex_surface.py +0 -246
  463. package/src/superlocalmemory/optimize/storage/__init__.py +0 -0
  464. package/src/superlocalmemory/optimize/storage/db.py +0 -1185
  465. package/src/superlocalmemory/optimize/storage/schema.py +0 -205
  466. package/src/superlocalmemory/parameterization/__init__.py +0 -47
  467. package/src/superlocalmemory/parameterization/cross_project.py +0 -12
  468. package/src/superlocalmemory/parameterization/pattern_extractor.py +0 -584
  469. package/src/superlocalmemory/parameterization/pii_filter.py +0 -106
  470. package/src/superlocalmemory/parameterization/prompt_injector.py +0 -219
  471. package/src/superlocalmemory/parameterization/prompt_lifecycle.py +0 -281
  472. package/src/superlocalmemory/parameterization/soft_prompt_generator.py +0 -542
  473. package/src/superlocalmemory/parameterization/workflow_miner.py +0 -17
  474. package/src/superlocalmemory/reliability/__init__.py +0 -45
  475. package/src/superlocalmemory/reliability/join_liveness.py +0 -301
  476. package/src/superlocalmemory/reliability/prior_distance.py +0 -243
  477. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  478. package/src/superlocalmemory/retrieval/agentic.py +0 -367
  479. package/src/superlocalmemory/retrieval/ann_index.py +0 -235
  480. package/src/superlocalmemory/retrieval/bm25_channel.py +0 -451
  481. package/src/superlocalmemory/retrieval/bridge_discovery.py +0 -253
  482. package/src/superlocalmemory/retrieval/channel_registry.py +0 -154
  483. package/src/superlocalmemory/retrieval/channel_status.py +0 -117
  484. package/src/superlocalmemory/retrieval/engine.py +0 -1615
  485. package/src/superlocalmemory/retrieval/entity_channel.py +0 -994
  486. package/src/superlocalmemory/retrieval/forgetting_filter.py +0 -160
  487. package/src/superlocalmemory/retrieval/fusion.py +0 -81
  488. package/src/superlocalmemory/retrieval/graph_adjacency.py +0 -219
  489. package/src/superlocalmemory/retrieval/hopfield_channel.py +0 -465
  490. package/src/superlocalmemory/retrieval/profile_channel.py +0 -105
  491. package/src/superlocalmemory/retrieval/quantization_aware_search.py +0 -147
  492. package/src/superlocalmemory/retrieval/remote_reranker.py +0 -758
  493. package/src/superlocalmemory/retrieval/reranker.py +0 -674
  494. package/src/superlocalmemory/retrieval/scope_policy.py +0 -126
  495. package/src/superlocalmemory/retrieval/semantic_channel.py +0 -638
  496. package/src/superlocalmemory/retrieval/spreading.py +0 -288
  497. package/src/superlocalmemory/retrieval/spreading_activation.py +0 -616
  498. package/src/superlocalmemory/retrieval/strategy.py +0 -248
  499. package/src/superlocalmemory/retrieval/temporal_channel.py +0 -433
  500. package/src/superlocalmemory/retrieval/temporal_frame.py +0 -102
  501. package/src/superlocalmemory/retrieval/temporal_utils.py +0 -122
  502. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +0 -499
  503. package/src/superlocalmemory/retrieval/time_window.py +0 -181
  504. package/src/superlocalmemory/retrieval/vector_store.py +0 -863
  505. package/src/superlocalmemory/server/__init__.py +0 -1
  506. package/src/superlocalmemory/server/api.py +0 -310
  507. package/src/superlocalmemory/server/asset_versions.py +0 -171
  508. package/src/superlocalmemory/server/bandit_loops.py +0 -158
  509. package/src/superlocalmemory/server/config_file.py +0 -90
  510. package/src/superlocalmemory/server/consolidation_runner.py +0 -140
  511. package/src/superlocalmemory/server/egress_policy.py +0 -258
  512. package/src/superlocalmemory/server/loopback.py +0 -85
  513. package/src/superlocalmemory/server/middleware/__init__.py +0 -11
  514. package/src/superlocalmemory/server/middleware/security_headers.py +0 -144
  515. package/src/superlocalmemory/server/origin.py +0 -55
  516. package/src/superlocalmemory/server/profile_runtime.py +0 -515
  517. package/src/superlocalmemory/server/rbac_enforce.py +0 -194
  518. package/src/superlocalmemory/server/recall_health.py +0 -343
  519. package/src/superlocalmemory/server/recall_serializer.py +0 -320
  520. package/src/superlocalmemory/server/route_mutations.py +0 -104
  521. package/src/superlocalmemory/server/routes/__init__.py +0 -4
  522. package/src/superlocalmemory/server/routes/abstraction.py +0 -314
  523. package/src/superlocalmemory/server/routes/adapters.py +0 -63
  524. package/src/superlocalmemory/server/routes/agents.py +0 -303
  525. package/src/superlocalmemory/server/routes/backup.py +0 -869
  526. package/src/superlocalmemory/server/routes/behavioral.py +0 -659
  527. package/src/superlocalmemory/server/routes/brain.py +0 -1892
  528. package/src/superlocalmemory/server/routes/chat.py +0 -393
  529. package/src/superlocalmemory/server/routes/compliance.py +0 -533
  530. package/src/superlocalmemory/server/routes/config_api.py +0 -703
  531. package/src/superlocalmemory/server/routes/data_io.py +0 -329
  532. package/src/superlocalmemory/server/routes/entity.py +0 -237
  533. package/src/superlocalmemory/server/routes/events.py +0 -214
  534. package/src/superlocalmemory/server/routes/evolution.py +0 -510
  535. package/src/superlocalmemory/server/routes/helpers.py +0 -499
  536. package/src/superlocalmemory/server/routes/ingest.py +0 -137
  537. package/src/superlocalmemory/server/routes/insights.py +0 -366
  538. package/src/superlocalmemory/server/routes/learning.py +0 -834
  539. package/src/superlocalmemory/server/routes/learning_telemetry.py +0 -154
  540. package/src/superlocalmemory/server/routes/lifecycle.py +0 -184
  541. package/src/superlocalmemory/server/routes/memories.py +0 -1661
  542. package/src/superlocalmemory/server/routes/mesh.py +0 -517
  543. package/src/superlocalmemory/server/routes/mesh_lock.py +0 -54
  544. package/src/superlocalmemory/server/routes/mesh_state.py +0 -63
  545. package/src/superlocalmemory/server/routes/optimize.py +0 -197
  546. package/src/superlocalmemory/server/routes/prewarm.py +0 -173
  547. package/src/superlocalmemory/server/routes/profiles.py +0 -292
  548. package/src/superlocalmemory/server/routes/ratelimit.py +0 -132
  549. package/src/superlocalmemory/server/routes/rbac.py +0 -366
  550. package/src/superlocalmemory/server/routes/stats.py +0 -385
  551. package/src/superlocalmemory/server/routes/tiers.py +0 -222
  552. package/src/superlocalmemory/server/routes/timeline.py +0 -258
  553. package/src/superlocalmemory/server/routes/token.py +0 -90
  554. package/src/superlocalmemory/server/routes/v3_api.py +0 -3023
  555. package/src/superlocalmemory/server/routes/ws.py +0 -171
  556. package/src/superlocalmemory/server/security_middleware.py +0 -89
  557. package/src/superlocalmemory/server/ui.py +0 -354
  558. package/src/superlocalmemory/server/unified_daemon.py +0 -6326
  559. package/src/superlocalmemory/server/write_identity.py +0 -195
  560. package/src/superlocalmemory/storage/__init__.py +0 -0
  561. package/src/superlocalmemory/storage/_migration_internals.py +0 -638
  562. package/src/superlocalmemory/storage/_schema_version.py +0 -174
  563. package/src/superlocalmemory/storage/access_log.py +0 -170
  564. package/src/superlocalmemory/storage/admission_codec.py +0 -129
  565. package/src/superlocalmemory/storage/admission_journal.py +0 -843
  566. package/src/superlocalmemory/storage/agent_experience.py +0 -546
  567. package/src/superlocalmemory/storage/backup.py +0 -531
  568. package/src/superlocalmemory/storage/correction_cases.py +0 -670
  569. package/src/superlocalmemory/storage/database.py +0 -3180
  570. package/src/superlocalmemory/storage/deferred_writes.py +0 -209
  571. package/src/superlocalmemory/storage/embedding_codec.py +0 -200
  572. package/src/superlocalmemory/storage/embedding_migrator.py +0 -672
  573. package/src/superlocalmemory/storage/erasure_fence.py +0 -45
  574. package/src/superlocalmemory/storage/execution_learning.py +0 -285
  575. package/src/superlocalmemory/storage/external_evidence.py +0 -359
  576. package/src/superlocalmemory/storage/generation_fence.py +0 -63
  577. package/src/superlocalmemory/storage/lineage_retention.py +0 -236
  578. package/src/superlocalmemory/storage/logical_edges.py +0 -86
  579. package/src/superlocalmemory/storage/memory_write.py +0 -115
  580. package/src/superlocalmemory/storage/migration_runner.py +0 -895
  581. package/src/superlocalmemory/storage/migration_v33.py +0 -140
  582. package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +0 -67
  583. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +0 -107
  584. package/src/superlocalmemory/storage/migrations/M003_migration_log.py +0 -38
  585. package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +0 -46
  586. package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +0 -75
  587. package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +0 -75
  588. package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +0 -63
  589. package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +0 -94
  590. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +0 -80
  591. package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +0 -87
  592. package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +0 -72
  593. package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +0 -55
  594. package/src/superlocalmemory/storage/migrations/M014_v345_scale_ready.py +0 -45
  595. package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +0 -58
  596. package/src/superlocalmemory/storage/migrations/M016_add_scope_support.py +0 -120
  597. package/src/superlocalmemory/storage/migrations/M017_ccq_scope_column.py +0 -79
  598. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +0 -120
  599. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +0 -54
  600. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +0 -52
  601. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +0 -108
  602. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +0 -86
  603. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +0 -194
  604. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +0 -87
  605. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +0 -90
  606. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +0 -136
  607. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +0 -163
  608. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +0 -305
  609. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +0 -137
  610. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +0 -93
  611. package/src/superlocalmemory/storage/migrations/M031_dead_letter_operations.py +0 -80
  612. package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +0 -188
  613. package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +0 -148
  614. package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +0 -58
  615. package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +0 -113
  616. package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +0 -107
  617. package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +0 -162
  618. package/src/superlocalmemory/storage/migrations/M038_learning_feedback_channel.py +0 -77
  619. package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +0 -137
  620. package/src/superlocalmemory/storage/migrations/M040_agent_experience_receipts.py +0 -254
  621. package/src/superlocalmemory/storage/migrations/M041_external_evidence_receipts.py +0 -189
  622. package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +0 -245
  623. package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +0 -512
  624. package/src/superlocalmemory/storage/migrations/M044_play_carries_its_own_evidence.py +0 -127
  625. package/src/superlocalmemory/storage/migrations/M045_fact_outcome_score.py +0 -158
  626. package/src/superlocalmemory/storage/migrations/M046_prospective_memory_has_its_own_name.py +0 -620
  627. package/src/superlocalmemory/storage/migrations/M047_fisher_vectors_are_stored_like_every_other_vector.py +0 -306
  628. package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +0 -229
  629. package/src/superlocalmemory/storage/migrations/M049_a_schema_version_marker_is_one_row.py +0 -201
  630. package/src/superlocalmemory/storage/migrations/M050_execution_learning_v2.py +0 -70
  631. package/src/superlocalmemory/storage/migrations/__init__.py +0 -103
  632. package/src/superlocalmemory/storage/migrations.py +0 -333
  633. package/src/superlocalmemory/storage/models.py +0 -500
  634. package/src/superlocalmemory/storage/projection_outbox.py +0 -346
  635. package/src/superlocalmemory/storage/quantized_store.py +0 -280
  636. package/src/superlocalmemory/storage/read_connection.py +0 -115
  637. package/src/superlocalmemory/storage/retention_policy.py +0 -860
  638. package/src/superlocalmemory/storage/schema.py +0 -1108
  639. package/src/superlocalmemory/storage/schema_code_graph.py +0 -282
  640. package/src/superlocalmemory/storage/schema_v32.py +0 -382
  641. package/src/superlocalmemory/storage/schema_v3410.py +0 -159
  642. package/src/superlocalmemory/storage/schema_v3411.py +0 -149
  643. package/src/superlocalmemory/storage/schema_v343.py +0 -315
  644. package/src/superlocalmemory/storage/schema_v345.py +0 -109
  645. package/src/superlocalmemory/storage/schema_v347.py +0 -140
  646. package/src/superlocalmemory/storage/sqlite_vectors.py +0 -169
  647. package/src/superlocalmemory/storage/v2_migrator.py +0 -466
  648. package/src/superlocalmemory/storage/write_coordinator.py +0 -949
  649. package/src/superlocalmemory/storage/write_lock.py +0 -88
  650. package/src/superlocalmemory/summaries/__init__.py +0 -37
  651. package/src/superlocalmemory/summaries/base.py +0 -267
  652. package/src/superlocalmemory/summaries/daily_reflection.py +0 -340
  653. package/src/superlocalmemory/summaries/non_answer.py +0 -223
  654. package/src/superlocalmemory/summaries/project_work_log.py +0 -440
  655. package/src/superlocalmemory/summaries/session_summary.py +0 -311
  656. package/src/superlocalmemory/trust/__init__.py +0 -0
  657. package/src/superlocalmemory/trust/gate.py +0 -171
  658. package/src/superlocalmemory/trust/provenance.py +0 -124
  659. package/src/superlocalmemory/trust/scorer.py +0 -413
  660. package/src/superlocalmemory/trust/signals.py +0 -153
  661. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +0 -64
  662. package/src/superlocalmemory/ui/assets/slm-icon.svg +0 -36
  663. package/src/superlocalmemory/ui/css/brain.css +0 -409
  664. package/src/superlocalmemory/ui/css/design-system.css +0 -696
  665. package/src/superlocalmemory/ui/css/legacy-dashboard.css +0 -663
  666. package/src/superlocalmemory/ui/css/neural-glass.css +0 -1599
  667. package/src/superlocalmemory/ui/css/od-bridge.css +0 -158
  668. package/src/superlocalmemory/ui/favicon.svg +0 -36
  669. package/src/superlocalmemory/ui/index.html +0 -1638
  670. package/src/superlocalmemory/ui/js/agents.js +0 -192
  671. package/src/superlocalmemory/ui/js/auto-settings.js +0 -624
  672. package/src/superlocalmemory/ui/js/brain.js +0 -1400
  673. package/src/superlocalmemory/ui/js/clusters.js +0 -326
  674. package/src/superlocalmemory/ui/js/compliance.js +0 -307
  675. package/src/superlocalmemory/ui/js/core.js +0 -566
  676. package/src/superlocalmemory/ui/js/dashboard.js +0 -503
  677. package/src/superlocalmemory/ui/js/event-delegation.js +0 -113
  678. package/src/superlocalmemory/ui/js/events.js +0 -178
  679. package/src/superlocalmemory/ui/js/fact-detail.js +0 -142
  680. package/src/superlocalmemory/ui/js/feedback.js +0 -339
  681. package/src/superlocalmemory/ui/js/graph-event-bus.js +0 -83
  682. package/src/superlocalmemory/ui/js/graph-filters.js +0 -220
  683. package/src/superlocalmemory/ui/js/graph-ui.js +0 -214
  684. package/src/superlocalmemory/ui/js/ide-status.js +0 -115
  685. package/src/superlocalmemory/ui/js/init.js +0 -54
  686. package/src/superlocalmemory/ui/js/knowledge-graph.js +0 -945
  687. package/src/superlocalmemory/ui/js/lifecycle.js +0 -387
  688. package/src/superlocalmemory/ui/js/math-health.js +0 -114
  689. package/src/superlocalmemory/ui/js/memories.js +0 -394
  690. package/src/superlocalmemory/ui/js/memory-chat.js +0 -371
  691. package/src/superlocalmemory/ui/js/memory-timeline.js +0 -265
  692. package/src/superlocalmemory/ui/js/modal.js +0 -733
  693. package/src/superlocalmemory/ui/js/ng-entities.js +0 -298
  694. package/src/superlocalmemory/ui/js/ng-health.js +0 -208
  695. package/src/superlocalmemory/ui/js/ng-ingestion.js +0 -203
  696. package/src/superlocalmemory/ui/js/ng-mesh.js +0 -374
  697. package/src/superlocalmemory/ui/js/ng-shell.js +0 -524
  698. package/src/superlocalmemory/ui/js/ng-skills.js +0 -663
  699. package/src/superlocalmemory/ui/js/od-agents.js +0 -588
  700. package/src/superlocalmemory/ui/js/od-auth-gate.js +0 -257
  701. package/src/superlocalmemory/ui/js/od-backup.js +0 -878
  702. package/src/superlocalmemory/ui/js/od-boundedloops.js +0 -324
  703. package/src/superlocalmemory/ui/js/od-brain.js +0 -1095
  704. package/src/superlocalmemory/ui/js/od-compliance-ext.js +0 -301
  705. package/src/superlocalmemory/ui/js/od-components.js +0 -147
  706. package/src/superlocalmemory/ui/js/od-entities.js +0 -622
  707. package/src/superlocalmemory/ui/js/od-graph.js +0 -776
  708. package/src/superlocalmemory/ui/js/od-health.js +0 -579
  709. package/src/superlocalmemory/ui/js/od-mcp.js +0 -508
  710. package/src/superlocalmemory/ui/js/od-memories.js +0 -1499
  711. package/src/superlocalmemory/ui/js/od-mesh.js +0 -645
  712. package/src/superlocalmemory/ui/js/od-operations.js +0 -1268
  713. package/src/superlocalmemory/ui/js/od-ops-health.js +0 -417
  714. package/src/superlocalmemory/ui/js/od-optimize.js +0 -828
  715. package/src/superlocalmemory/ui/js/od-settings.js +0 -1275
  716. package/src/superlocalmemory/ui/js/od-shell.js +0 -819
  717. package/src/superlocalmemory/ui/js/od-skills.js +0 -600
  718. package/src/superlocalmemory/ui/js/od-team.js +0 -265
  719. package/src/superlocalmemory/ui/js/optimize.js +0 -191
  720. package/src/superlocalmemory/ui/js/profiles.js +0 -362
  721. package/src/superlocalmemory/ui/js/quick-actions.js +0 -334
  722. package/src/superlocalmemory/ui/js/recall-lab.js +0 -373
  723. package/src/superlocalmemory/ui/js/search.js +0 -86
  724. package/src/superlocalmemory/ui/js/settings.js +0 -556
  725. package/src/superlocalmemory/ui/js/timeline.js +0 -62
  726. package/src/superlocalmemory/ui/js/trust-dashboard.js +0 -225
  727. package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +0 -2018
  728. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  729. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  730. package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +0 -7
  731. package/src/superlocalmemory/ui/vendor/bootstrap.min.css +0 -6
  732. package/src/superlocalmemory/ui/vendor/d3.v7.min.js +0 -2
  733. package/src/superlocalmemory/ui/vendor/graphology-library.min.js +0 -2
  734. package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +0 -2
  735. package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +0 -8
  736. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
  737. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
  738. package/src/superlocalmemory/ui/vendor/sigma.min.js +0 -1
  739. package/src/superlocalmemory/vector/__init__.py +0 -9
  740. package/src/superlocalmemory/vector/lancedb_backend.py +0 -366
@@ -1,2204 +0,0 @@
1
- # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
- # Licensed under AGPL-3.0-or-later - see LICENSE file
3
- # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
-
5
- """SuperLocalMemory V3 — Configuration.
6
-
7
- Unified configuration with Mode A/B/C capability matrix.
8
- Clean — zero dead options, every config has a consumer.
9
-
10
- Part of Qualixar | Author: Varun Pratap Bhardwaj
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- import logging
16
- from dataclasses import asdict, dataclass, field
17
- from pathlib import Path
18
-
19
- from superlocalmemory.infra.data_root import DynamicStatePath, canonical_data_root
20
- from superlocalmemory.storage.models import Mode
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
-
25
- # ---------------------------------------------------------------------------
26
- # Canonical limits — single source of truth across all surfaces
27
- # ---------------------------------------------------------------------------
28
-
29
- #: Default number of results returned by recall across MCP, CLI, daemon, and
30
- #: engine. All surfaces bind their ``limit`` defaults to this constant so a
31
- #: single change is sufficient to keep the full stack in sync. Search is a
32
- #: retrieval operation too and binds here: asking the same question through
33
- #: ``recall`` and through ``search`` must not return differently sized answers.
34
- CANONICAL_RECALL_LIMIT: int = 20
35
-
36
- #: Default number of rows returned when listing the newest memories rather than
37
- #: answering a question — MCP ``list_recent`` and ``slm list``. Separate from
38
- #: the recall limit because the two can legitimately diverge; equal today.
39
- CANONICAL_LIST_LIMIT: int = 20
40
-
41
- #: Page size for the HTTP memory browser (``GET /api/memories``), which is a
42
- #: paged table rather than an answer. Deliberately larger than the list limit:
43
- #: a person scrolling a table wants a fuller page than an agent asking what
44
- #: happened recently. The ceiling on that endpoint is enforced separately.
45
- BROWSE_PAGE_SIZE: int = 50
46
-
47
-
48
- # ---------------------------------------------------------------------------
49
- # Default Paths
50
- # ---------------------------------------------------------------------------
51
-
52
- DEFAULT_BASE_DIR = DynamicStatePath()
53
- DEFAULT_DB_NAME = "memory.db"
54
- DEFAULT_PROFILES_FILE = "profiles.json"
55
- CURRENT_MODE_FILE = "current_mode"
56
- # Populated lazily in _get_mode_config_path() to avoid circular imports
57
- _MODE_CONFIG_NAMES: dict | None = None
58
-
59
-
60
- def _runtime_base_dir() -> Path:
61
- """Resolve the process namespace when a config root is not explicit."""
62
- return canonical_data_root()
63
-
64
-
65
- # ---------------------------------------------------------------------------
66
- # Embedding Config
67
- # ---------------------------------------------------------------------------
68
-
69
- @dataclass(frozen=True)
70
- class EmbeddingConfig:
71
- """Embedding model configuration per mode."""
72
-
73
- model_name: str = "nomic-ai/nomic-embed-text-v1.5"
74
- dimension: int = 768
75
- # Provider: "" = auto-detect, "sentence-transformers", "ollama", "cloud",
76
- # "openai" (V3.4.24: any OpenAI-compatible /v1/embeddings endpoint)
77
- provider: str = ""
78
- # Ollama settings (used when provider="ollama" or auto-detected)
79
- ollama_model: str = "nomic-embed-text"
80
- ollama_base_url: str = "http://localhost:11434"
81
- # Azure / cloud settings (Mode C only)
82
- api_endpoint: str = ""
83
- api_key: str = ""
84
- api_version: str = "2024-02-01"
85
- deployment_name: str = ""
86
-
87
- @property
88
- def is_cloud(self) -> bool:
89
- if self.provider == "openai":
90
- return False
91
- return bool(self.api_endpoint) or self.provider == "cloud"
92
-
93
- @property
94
- def is_ollama(self) -> bool:
95
- return self.provider == "ollama"
96
-
97
- @property
98
- def is_openai_compatible(self) -> bool:
99
- """V3.4.24: True when using a custom OpenAI-compatible endpoint."""
100
- return self.provider == "openai" and bool(self.api_endpoint)
101
-
102
-
103
- # ---------------------------------------------------------------------------
104
- # LLM Config
105
- # ---------------------------------------------------------------------------
106
-
107
- @dataclass(frozen=True)
108
- class LLMConfig:
109
- """LLM provider configuration per mode.
110
-
111
- Frozen (restored in 4.0.6). ``frozen=True`` also generates ``__hash__``;
112
- dropping it makes every LLMConfig unhashable, which breaks any set/dict-key
113
- or cache use at runtime without necessarily failing a test. Updates build a
114
- NEW instance via ``dataclasses.replace`` and assign it to the mutable
115
- SLMConfig — see ``v3_api.apply_settings_update`` — so immutability here
116
- costs nothing.
117
-
118
- All production code creates new instances via LLMConfig(...) or
119
- dataclasses.replace().
120
- """
121
-
122
- provider: str = "" # "" = no LLM, "ollama", "azure", "openai", "anthropic"
123
- model: str = "" # Model name/deployment
124
- api_key: str = ""
125
- api_base: str = ""
126
- temperature: float = 0.0 # Deterministic by default
127
- max_tokens: int = 4096
128
- timeout_seconds: float = 60.0
129
-
130
- @property
131
- def is_available(self) -> bool:
132
- return bool(self.provider)
133
-
134
-
135
- # ---------------------------------------------------------------------------
136
- # Channel Weights
137
- # ---------------------------------------------------------------------------
138
-
139
- @dataclass
140
- class ChannelWeights:
141
- """Retrieval channel weights — 5 channels, query-adaptive."""
142
-
143
- # Semantic should dominate for conversational retrieval (paraphrase matters most).
144
- semantic: float = 1.5
145
- bm25: float = 1.0
146
- entity_graph: float = 1.0
147
- temporal: float = 1.0
148
- spreading_activation: float = 1.0 # Phase 3: 5th channel (BC-08: default value)
149
- hopfield: float = 0.8 # Phase G: 6th channel (Hopfield associative memory)
150
-
151
- def as_dict(self) -> dict[str, float]:
152
- return {
153
- "semantic": self.semantic,
154
- "bm25": self.bm25,
155
- "entity_graph": self.entity_graph,
156
- "temporal": self.temporal,
157
- "spreading_activation": self.spreading_activation,
158
- "hopfield": self.hopfield,
159
- }
160
-
161
-
162
- # ---------------------------------------------------------------------------
163
- # Scope Weights
164
- # ---------------------------------------------------------------------------
165
-
166
- @dataclass
167
- class ScopeWeights:
168
- """RRF fusion weights for multi-scope retrieval.
169
-
170
- Personal scope has highest weight (most relevant to current profile).
171
- Shared scope has medium weight (team/group memories).
172
- Global scope has lowest weight (public/common knowledge).
173
- """
174
-
175
- personal: float = 1.0
176
- shared: float = 0.7
177
- global_: float = 0.5 # trailing underscore avoids Python keyword
178
-
179
- def __post_init__(self) -> None:
180
- for name in ("personal", "shared", "global_"):
181
- val = getattr(self, name)
182
- if val < 0:
183
- raise ValueError(f"ScopeWeights values must be non-negative, got {name}={val}")
184
-
185
- def as_dict(self) -> dict[str, float]:
186
- return {"personal": self.personal, "shared": self.shared, "global": self.global_}
187
-
188
-
189
- # ---------------------------------------------------------------------------
190
- # Scope Config (multi-scope memory behaviour defaults)
191
- # ---------------------------------------------------------------------------
192
-
193
- _VALID_SCOPES = ("personal", "shared", "global")
194
-
195
-
196
- @dataclass
197
- class ScopeConfig:
198
- """User-facing defaults for multi-scope (shared) memory.
199
-
200
- SHARED MEMORY IS OPT-IN — NOT a default feature (v3.6.15 product decision).
201
- The defaults below make a fresh / unconfigured install behave EXACTLY like
202
- 3.6.14: every write is ``personal`` and recall returns only this profile's
203
- own facts. Another profile's ``global``/``shared`` facts never leak into
204
- recall until the user explicitly turns sharing on.
205
-
206
- - ``default_scope='personal'`` → writes stay private by default;
207
- - ``recall_include_global=False`` → don't surface other profiles' global;
208
- - ``recall_include_shared=False`` → don't surface facts shared *to* me.
209
-
210
- Turning it on is a deliberate act, done either per-call (``--scope`` /
211
- ``--include-global`` / MCP args) or persistently by editing config.json /
212
- mode_a|b|c.json (the installer can also write the choice). The CLI/MCP
213
- boundary passes ``None`` ("not specified") so the engine resolves these
214
- config values as the effective default.
215
- """
216
-
217
- default_scope: str = "personal" # scope assigned to new memories
218
- recall_include_global: bool = False # surface scope='global' facts in recall
219
- recall_include_shared: bool = False # surface scope='shared' facts in recall
220
-
221
- def __post_init__(self) -> None:
222
- if self.default_scope not in _VALID_SCOPES:
223
- raise ValueError(
224
- f"default_scope must be one of {_VALID_SCOPES}, got {self.default_scope!r}"
225
- )
226
-
227
- def as_dict(self) -> dict:
228
- return {
229
- "default_scope": self.default_scope,
230
- "recall_include_global": self.recall_include_global,
231
- "recall_include_shared": self.recall_include_shared,
232
- }
233
-
234
-
235
- # ---------------------------------------------------------------------------
236
- # Encoding Config
237
- # ---------------------------------------------------------------------------
238
-
239
- @dataclass
240
- class EncodingConfig:
241
- """Configuration for the encoding (memory creation) pipeline."""
242
-
243
- # Fact extraction
244
- chunk_size: int = 10 # Conversation turns per extraction chunk
245
- max_facts_per_chunk: int = 10 # V3.3.11: increased from 5 to preserve more details
246
- min_fact_confidence: float = 0.3
247
-
248
- # Entity resolution
249
- entity_similarity_threshold: float = 0.85
250
- max_entity_candidates: int = 10
251
-
252
- # Graph construction
253
- semantic_edge_top_k: int = 5 # Top-K semantic edges per new fact
254
- temporal_edge_window_hours: int = 168 # 1 week
255
-
256
- # Consolidation
257
- consolidation_similarity_threshold: float = 0.85
258
- max_consolidation_candidates: int = 5
259
-
260
- # Entropy gate
261
- entropy_threshold: float = 0.95
262
-
263
- # Entity reflexion (Wave Q1) — Mode B/C self-review of extracted entities.
264
- # Fail-open; one extra bounded LLM call per chunk. Mode A is unaffected.
265
- enable_entity_reflexion: bool = True
266
- reflexion_max_facts: int = 8
267
-
268
-
269
- # ---------------------------------------------------------------------------
270
- # Retrieval Config
271
- # ---------------------------------------------------------------------------
272
-
273
- @dataclass
274
- class RetrievalConfig:
275
- """Configuration for the retrieval (recall) pipeline."""
276
-
277
- # Fusion
278
- rrf_k: int = 15 # RRF smoothing constant (k=15 for candidate pools of 50-200)
279
- top_k: int = 20 # Final results to return
280
-
281
- # Per-channel
282
- semantic_top_k: int = 50 # ANN pre-filter candidates
283
- bm25_top_k: int = 50
284
- entity_graph_max_hops: int = 3
285
- temporal_proximity_days: int = 30
286
-
287
- # Reranking (V3.3.2: ONNX backend enabled for all modes)
288
- # V3.4.2: Tested gte-reranker-modernbert-base (8K context) — REGRESSED
289
- # LoCoMo from 68.4% to 64.1%. Reverted to MiniLM-L-12-v2. The 512-token
290
- # limit is acceptable because SLM's multi-producer retrieval pre-filters
291
- # relevant facts before reranking. See bench-v342-locomo.md.
292
- use_cross_encoder: bool = True
293
- cross_encoder_model: str = "cross-encoder/ms-marco-MiniLM-L-12-v2"
294
- # "" = PyTorch (~500MB stable), "onnx" = ONNX (leaks on ARM64 CoreML),
295
- # "openai"/"remote" = v3.8.12 (issue #105) OpenAI-compatible /v1/rerank
296
- # endpoint (llama-server, TEI, Infinity, vLLM, Cohere-shaped services).
297
- cross_encoder_backend: str = ""
298
-
299
- # v3.8.12 (issue #105): remote reranker endpoint. The bundled default
300
- # cross-encoder (ms-marco-MiniLM-L-12-v2) is ENGLISH-ONLY, so non-English
301
- # deployments scored their own language with a model that cannot read it.
302
- # Pointing this at a multilingual reranker (bge-reranker-v2-m3, a Qwen
303
- # reranker, …) is the same escape hatch remote embeddings got in v3.4.24.
304
- #
305
- # This key was accepted-and-ignored before 3.8.12 (issue #103): it was not
306
- # a dataclass field, so ``SLMConfig.load`` filtered it out without a word.
307
- # It is now read, validated, and — when it disagrees with the backend —
308
- # reported as a loud configuration error instead of nothing at all.
309
- cross_encoder_endpoint: str = ""
310
- # Optional bearer token. SLM_CROSS_ENCODER_API_KEY takes precedence.
311
- # When persisted for backward compatibility, config.json is atomically
312
- # forced to owner-only mode 0600 by save().
313
- cross_encoder_api_key: str = ""
314
- # Per-request read budget for the remote reranker. Recall is interactive,
315
- # so this stays tight: a slow reranker degrades to fusion scores rather
316
- # than holding the recall open.
317
- cross_encoder_timeout_seconds: float = 15.0
318
-
319
- # v3.9.x (issue #112): plain-HTTP trust for private-LAN reranker endpoints.
320
- # When True (the default), numeric RFC1918/ULA/link-local addresses may use
321
- # plain HTTP — the same security model as the local reranker, where memory
322
- # text only crosses the loopback. Set to False in hardened deployments
323
- # (zero-trust networks, shared colocation) to require HTTPS for all
324
- # non-loopback hosts, including private-LAN addresses.
325
- #
326
- # THREAT MODEL: setting this True does NOT prevent a MITM attack by an
327
- # adversary on the same physical LAN (e.g. ARP spoofing). This flag means
328
- # "my LAN is under my control and I accept that residual risk." It is not
329
- # a claim that private-LAN traffic is cryptographically secure.
330
- trust_plain_http_lan: bool = True
331
-
332
- @property
333
- def is_remote_cross_encoder(self) -> bool:
334
- """True when reranking is served by a remote HTTP endpoint."""
335
- from superlocalmemory.retrieval.remote_reranker import (
336
- is_remote_cross_encoder_backend,
337
- )
338
-
339
- return (
340
- is_remote_cross_encoder_backend(self.cross_encoder_backend)
341
- and bool(self.cross_encoder_endpoint)
342
- )
343
-
344
- # Agentic (Mode C only)
345
- agentic_max_rounds: int = 3
346
- agentic_confidence_threshold: float = 0.3
347
-
348
- # v3.8.2: Client-driven agentic (the recall spine's global flag).
349
- # The agent hot path (CLI / MCP / plugins) is consumed by a frontier
350
- # LLM (Claude Code, Copilot, Codex, …) that reformulates multi-hop /
351
- # low-confidence queries far better than the local Ollama model. So the
352
- # hot path DELEGATES the agentic reformulation loop to that calling LLM:
353
- # it returns fast local retrieval (all six channels + reranker) plus the
354
- # confidence signals (no_confident_match / answer_confidence / abstained),
355
- # and never spends an internal LLM round unless a caller asks for it.
356
- # True (default) → hot path skips the internal agentic round
357
- # (equivalent to fast=True) and the client drives
358
- # refinement. Consistent ~1.5-2s recall, no LLM tail.
359
- # False → hot path always runs the internal agentic round
360
- # when a query looks multi-hop / low-confidence
361
- # (equivalent to fast=False) — for deployments with
362
- # no smart client in front of SLM.
363
- # This ONLY sets the default when a caller does not pass ``fast``
364
- # explicitly. The dashboard human path (search-all → cluster summary)
365
- # passes fast=False directly and always keeps internal synthesis.
366
- # Env kill-switch: SLM_HOT_PATH_INTERNAL_AGENTIC=1 forces internal-on.
367
- client_driven_agentic: bool = True
368
-
369
- # Spreading activation
370
- spreading_activation_decay: float = 0.7
371
- spreading_activation_threshold: float = 0.1
372
-
373
- # Hopfield (Phase G: 6th channel)
374
- hopfield_top_k: int = 50
375
-
376
- # Trust weighting — apply Bayesian trust scores to retrieval ranking.
377
- # When enabled, each fact's score is multiplied by a trust weight in [0.5, 1.5].
378
- # Low-trust facts are demoted; high-trust facts are promoted.
379
- # Default trust = 1.0 (no effect when no trust data exists).
380
- use_trust_weighting: bool = True
381
-
382
- # Ablation channel control for experiments.
383
- # List of channel names to SKIP during retrieval (e.g., ["bm25", "entity_graph"]).
384
- # Used by s19_runner for ablation experiments. Empty = all channels active.
385
- disabled_channels: list[str] = field(default_factory=list)
386
-
387
- # v3.6.6: Evidence floor — gate on per-channel scores, not fused/RRF score.
388
- # Nonsense queries earn 0.0 on every primary channel; real matches earn
389
- # semantic >= 0.85 or bm25 > 0. The discriminator is earned channel evidence.
390
- # Env kill-switch: SLM_RECALL_NO_FLOOR=1 disables without release.
391
- evidence_floor_enabled: bool = True
392
- min_semantic_evidence: float = 0.60 # Minimum cosine similarity to keep a result
393
-
394
- # v3.6.6: Recall output budget — protect consuming agents from 585KB responses.
395
- recall_per_fact_max_chars: int = 2400 # ~600 tokens; head 70% + tail 30%
396
- recall_total_max_chars: int = 12000 # ~3K tokens; stubs beyond this
397
-
398
- # Wave Q2b: attach a precomputed community summary as thematic context when
399
- # the top results cluster in one community. Read-only lookup, gated, and
400
- # fail-open — never a per-query LLM call. Kill-switch for tuning.
401
- enable_community_context: bool = True
402
-
403
- # Recency query strategy.
404
- # When False, queries matching present-activity phrases fall through to
405
- # factual routing — one-line rollback without touching strategy.py.
406
- enable_recency_strategy: bool = True
407
- # Strength of the recency prior applied during final-score fusion.
408
- # 0.0 = no recency bias (restores previous ranking exactly).
409
- # Consumed by a separate ranking stage; this field makes the knob
410
- # configurable without further edits to this class.
411
- recency_prior_strength: float = 0.5
412
- # A fact written moments ago has no embedding yet, so it scores zero on the
413
- # semantic channel — the same score a genuinely unrelated fact gets. Inside
414
- # this window that zero is read as "not computed yet", not as "unrelated".
415
- write_recency_floor_enabled: bool = True
416
- write_recency_floor_minutes: float = 60.0
417
-
418
-
419
- # ---------------------------------------------------------------------------
420
- # Math Config
421
- # ---------------------------------------------------------------------------
422
-
423
- @dataclass
424
- class MathConfig:
425
- """Configuration for mathematical layers."""
426
-
427
- # Fisher-Rao
428
- fisher_temperature: float = 15.0
429
- fisher_bayesian_update: bool = True
430
- # "simplified" = local Mahalanobis-like (fast, existing behaviour)
431
- # "full" = Atkinson-Mitchell geodesic from FisherRaoMetric class
432
- fisher_mode: str = "simplified"
433
-
434
- # Langevin
435
- langevin_dt: float = 0.005
436
- langevin_temperature: float = 0.3
437
- langevin_persist_positions: bool = True
438
- langevin_weight_range: tuple[float, float] = (0.0, 1.0)
439
-
440
- # Hopfield
441
-
442
- # Ebbinghaus-Langevin coupling in maintenance (Phase 5 — P1-ELC)
443
- # When True, the maintenance cycle computes the combined Ebbinghaus-Langevin
444
- # coupled state for each fact and writes back the lifecycle zone.
445
- # Defaults to False to preserve existing maintenance behaviour.
446
- ebbinghaus_langevin_coupling_enabled: bool = False
447
-
448
- # Sheaf (at encoding time, NOT retrieval)
449
- sheaf_at_encoding: bool = True
450
- sheaf_contradiction_threshold: float = 0.45
451
- # Max edges to check per fact during sheaf consistency.
452
- # At 18K+ edges, coboundary computation becomes O(N*dim^2) and hangs.
453
- # Facts with more edges than this skip sheaf check (still get contradiction
454
- # detection via consolidator UPDATE/SUPERSEDE path).
455
- sheaf_max_edges_per_check: int = 200
456
-
457
- # Rate-Distortion (production only, disabled for benchmarks)
458
-
459
-
460
- # ---------------------------------------------------------------------------
461
- # Store Config (v3.6.6)
462
- # ---------------------------------------------------------------------------
463
-
464
- @dataclass(frozen=True)
465
- class StoreConfig:
466
- """Configuration for the remember/store write path (v3.6.6).
467
-
468
- Ingest gate: protect the DB from oversized facts and prompt-template
469
- pollution. Defaults ON; env kill-switch SLM_INGEST_NO_GATE=1.
470
- """
471
-
472
- # Max chars for the FACT content stored in atomic_facts.content.
473
- # Content above this is clamped to head 70% + tail 30% + truncation marker.
474
- # The FULL original is preserved in the memories table row. Set high (24K
475
- # ≈ 6K tokens) so only pathological pastes are touched; normal dense
476
- # session-handoff memories (6-15K chars) are stored 100% intact.
477
- max_verbatim_chars: int = 24000
478
-
479
- # Hard upper bound in bytes. Content above this is rejected outright.
480
- # Nobody's "memory" is a megabyte (MCP: success=False, HTTP: 413).
481
- max_ingest_bytes: int = 1_048_576 # 1 MB
482
-
483
-
484
- # ---------------------------------------------------------------------------
485
- # Context Injection (v3.4.65)
486
- # ---------------------------------------------------------------------------
487
-
488
- @dataclass(frozen=True)
489
- class InjectionConfig:
490
- """Context-injection budgets & framing (v3.4.65).
491
-
492
- Single source of truth for what reaches the agent at session_init,
493
- prestage_context, and the recall hooks. Replaces scattered char caps.
494
-
495
- Budgets are in *estimated tokens* (chars/4 heuristic; see
496
- core/injection.estimate_tokens). Mode-aware: A=fast/cheap, C=rich.
497
- """
498
- enabled: bool = True
499
-
500
- # Mode-aware TOTAL budget (core block + recall), in estimated tokens.
501
- total_budget_tokens_a: int = 2000
502
- total_budget_tokens_b: int = 4000
503
- total_budget_tokens_c: int = 8000
504
-
505
- # Per-memory ceiling (estimated tokens). Whole-memory inclusion until
506
- # the total budget is hit; this only clamps a single oversized memory.
507
- per_memory_max_tokens: int = 600
508
-
509
- # Core Memory Block (Letta pattern).
510
- core_block_enabled: bool = True
511
- core_block_max_facts: int = 5
512
- core_block_max_tokens: int = 1000
513
- core_block_importance_min: float = 0.8
514
- core_block_min_access_count: int = 2
515
-
516
- # Lost-in-the-middle: place strongest at top & bottom edges.
517
- edge_ordering: bool = True
518
-
519
- # Trust framing. False (shipped) = cautious "reference only" wrapper.
520
- # True (Varun personal) = clean "memory context" framing.
521
- # redact_secrets ALWAYS runs regardless.
522
- trust_first_party: bool = False
523
-
524
- # prestage_context response byte cap (was hardcoded 16 KB).
525
- prestage_max_response_bytes: int = 64 * 1024
526
-
527
-
528
- # ---------------------------------------------------------------------------
529
- # Master Config
530
- # ---------------------------------------------------------------------------
531
-
532
- @dataclass(frozen=True)
533
- class ConsolidationConfig:
534
- """Configuration for sleep-time consolidation (Phase 5).
535
-
536
- Ships enabled by default. Users can disable via slm config.
537
- """
538
-
539
- enabled: bool = True
540
- step_count_trigger: int = 50 # Lightweight consolidation every N stores (L7)
541
- session_trigger: bool = True # Run on session end
542
- idle_timeout_seconds: int = 300 # 5 min inactivity
543
- scheduled_sessions: int = 5 # Full consolidation every N sessions
544
- core_memory_char_limit: int = 2000 # Total chars across all blocks
545
- block_char_limit: int = 500 # Per-block character limit
546
- compression_similarity: float = 0.85 # Dedup threshold for compression
547
- promotion_min_access: int = 3 # Min access count for promotion
548
- promotion_min_trust: float = 0.5 # Min trust for promotion
549
- decay_days_threshold: int = 30 # Edge decay after N days
550
-
551
-
552
- @dataclass(frozen=True)
553
- class ForgettingConfig:
554
- """Ebbinghaus forgetting configuration."""
555
-
556
- enabled: bool = True
557
- # Strength coefficients
558
- alpha: float = 2.0 # Access frequency weight (log scale)
559
- beta: float = 1.5 # Importance weight (PageRank)
560
- gamma: float = 1.0 # Confirmation count weight
561
- delta: float = 0.5 # Emotional salience weight
562
- # Strength bounds
563
- min_strength: float = 0.1 # Floor (prevents instant forgetting)
564
- max_strength: float = 100.0 # Ceiling (numerical stability)
565
- # Zone thresholds
566
- archive_threshold: float = 0.2 # Below this -> ARCHIVE
567
- forget_threshold: float = 0.05 # Below this -> FORGOTTEN
568
- # Spaced repetition
569
- learning_rate: float = 1.0 # eta in spaced repetition update
570
- # Coupling
571
- forgetting_drift_scale: float = 0.5 # How strongly forgetting affects Langevin drift
572
- # Trust-weighted forgetting (Paper 3, Section 5.5)
573
- trust_kappa: float = 2.0 # Sensitivity: lambda_eff = lambda * (1 + trust_kappa * (1 - tau))
574
- # Scheduler
575
- scheduler_interval_minutes: int = 30 # How often to recompute retentions
576
- # Immunity
577
- core_memory_immune: bool = True # Core Memory blocks never forget
578
-
579
-
580
- @dataclass(frozen=True)
581
- class HopfieldConfig:
582
- """Modern Continuous Hopfield Network configuration (Ramsauer et al., 2020).
583
-
584
- Energy: E(xi) = -log(sum_i exp(B * xi' * x_i)) + B/2 * ||xi||^2
585
- Update: xi_new = X' @ softmax(B * X @ xi)
586
- Beta: B = 1/sqrt(d) where d = dimension
587
- Storage capacity: O(e^{d/2}) -- exponential in dimension.
588
- """
589
-
590
- enabled: bool = True
591
- dimension: int = 768
592
- max_iterations: int = 1
593
- convergence_epsilon: float = 1e-6
594
- prefilter_threshold: int = 10_000
595
- # How many candidates the index hands to this stage. This stage decides final
596
- # membership, so a fact ranked past this number by the index never reaches it
597
- # and can never be returned — the size of this pool is a direct limit on which
598
- # memories are reachable at all.
599
- #
600
- #
601
- # Chosen by measurement against the 2 s recall ceiling, on a 517 MB store:
602
- # 150 -> p95 998 ms, 0 channel timeouts
603
- # 300 -> p95 1054 ms, 0
604
- # 500 -> p95 1140 ms, 0 <- here: 3.3x the reachability of 150 for +142 ms
605
- # 750 -> p95 1278 ms, 0
606
- # 1000 -> p95 1947-2737 ms, 2-25 timeouts
607
- #
608
- # 1000 is excluded twice over: it breaches the ceiling, and a channel that
609
- # misses its deadline contributes nothing to fusion — so whether it lands
610
- # varies with machine load and the same question stops returning the same
611
- # answer. 500 keeps ~860 ms of headroom, which is several times the observed
612
- # run-to-run variance, and stays clear of the region where the cost curve
613
- # turned non-linear for reasons that were never explained.
614
- prefilter_candidates: int = 500
615
- skip_threshold: int = 100_000
616
- cache_ttl_seconds: float = 60.0
617
-
618
-
619
- @dataclass(frozen=True)
620
- class ReaperConfig:
621
- """Process health & stale reaper configuration (Phase H0).
622
-
623
- Prevents zombie SLM processes from exhausting RAM.
624
- """
625
-
626
- enabled: bool = True
627
- heartbeat_interval_seconds: int = 60
628
- orphan_age_threshold_hours: float = 4.0
629
- pid_file_path: str = ""
630
- graceful_timeout_seconds: float = 5.0
631
-
632
-
633
- @dataclass(frozen=True)
634
- class PolarQuantConfig:
635
- """PolarQuant embedding quantization configuration.
636
-
637
- Random orthogonal rotation + recursive polar + scalar quantization.
638
- Reference: TurboQuant (ICLR 2026), PolarQuant (arXiv 2502.02617).
639
- """
640
-
641
- dimension: int = 768
642
- rotation_matrix_path: str = "" # empty = ~/.superlocalmemory/polar_rotation.npy
643
- seed: int = 42 # reproducible rotation matrix
644
- codebook_method: str = "turbo" # "turbo" (default) or "polar_legacy"
645
-
646
-
647
- @dataclass(frozen=True)
648
- class QJLConfig:
649
- """QJL 1-bit residual correction configuration.
650
-
651
- Random projection + sign-bit quantization for asymmetric IP estimation.
652
- Reference: QJL (AAAI 2025, arXiv 2406.03482).
653
- """
654
-
655
- projection_dim: int = 128
656
- seed: int = 43 # separate from PolarQuant
657
-
658
-
659
- @dataclass(frozen=True)
660
- class QuantizationConfig:
661
- """Memory-aware embedding quantization (EAP + LP2E).
662
-
663
- Couples Ebbinghaus retention to embedding precision.
664
- """
665
-
666
- enabled: bool = True
667
- polar: PolarQuantConfig = field(default_factory=PolarQuantConfig)
668
- qjl: QJLConfig = field(default_factory=QJLConfig)
669
- default_bit_width: int = 32
670
- eap_enabled: bool = True
671
- keep_float32_backup: bool = True
672
- auto_compact_interval_hours: int = 6
673
- polar_search_penalty: float = 0.97 # V3.3.8: 0.95→0.97, TurboQuant has lower MSE
674
-
675
-
676
- @dataclass(frozen=True)
677
- class CCQConfig:
678
- """Cognitive Consolidation Quantization configuration (Phase E).
679
-
680
- Ships enabled by default. CCQ runs as Step 7 of the consolidation cycle.
681
- Biological analogy: sleep-time hippocampal-neocortical transfer.
682
- """
683
-
684
- enabled: bool = True
685
-
686
- # Candidate identification
687
- retention_threshold: float = 0.5
688
- max_candidates_per_run: int = 200
689
-
690
- # Clustering
691
- min_entity_overlap: int = 2
692
- temporal_window_days: int = 7
693
- min_cluster_size: int = 3
694
- max_cluster_size: int = 20
695
-
696
- # Gist extraction
697
- use_llm_gist: bool = True
698
- max_gist_chars: int = 500
699
- min_entity_coverage: float = 0.5
700
-
701
- # Embedding compression
702
- target_bit_width: int = 2
703
- compress_embeddings: bool = True
704
-
705
- # Scheduling
706
- store_count_trigger: int = 100
707
- run_on_session_end: bool = True
708
-
709
- # Safety
710
- core_memory_immune: bool = True
711
-
712
-
713
- @dataclass(frozen=True)
714
- class SAGQConfig:
715
- """Spreading Activation-Guided Quantization configuration.
716
-
717
- Centrality formula:
718
- centrality(i) = w_pagerank * pr_norm + w_degree * deg_norm + w_sa_freq * sa_freq_norm
719
-
720
- SAGQ precision:
721
- sagq_bw = b_min + (b_max - b_min) * centrality, snapped to valid_bit_widths
722
-
723
- Combined precision (with Phase A EAP):
724
- final_bw = max(eap_bw, sagq_bw)
725
- """
726
-
727
- enabled: bool = True
728
-
729
- # Centrality weights (MUST sum to 1.0 -- validated in __post_init__)
730
- w_pagerank: float = 0.5 # PageRank structural importance
731
- w_degree: float = 0.3 # Degree centrality (connection count)
732
- w_sa_freq: float = 0.2 # Spreading activation frequency (7-day window)
733
-
734
- # Bit-width range
735
- b_min: int = 2 # Minimum bit-width (most aggressive quantization)
736
- b_max: int = 32 # Maximum bit-width (full float32 precision)
737
-
738
- # Valid bit-widths (snapping targets) -- must be sorted ascending
739
- valid_bit_widths: tuple[int, ...] = (2, 4, 8, 32)
740
-
741
- # SA frequency window (days to look back in activation_cache)
742
- sa_frequency_window_days: int = 7
743
-
744
- # Scheduler
745
- scheduler_interval_hours: float = 6.0 # How often to run combined scheduler
746
-
747
- def __post_init__(self) -> None:
748
- weight_sum = self.w_pagerank + self.w_degree + self.w_sa_freq
749
- if abs(weight_sum - 1.0) > 1e-6:
750
- raise ValueError(
751
- f"SAGQConfig centrality weights must sum to 1.0, got {weight_sum:.6f}"
752
- )
753
- if not self.valid_bit_widths:
754
- raise ValueError("SAGQConfig.valid_bit_widths must not be empty")
755
- if self.b_min < 1:
756
- raise ValueError(f"SAGQConfig.b_min must be >= 1, got {self.b_min}")
757
- if self.b_max < self.b_min:
758
- raise ValueError(
759
- f"SAGQConfig.b_max ({self.b_max}) must be >= b_min ({self.b_min})"
760
- )
761
-
762
-
763
- @dataclass(frozen=True)
764
- class ParameterizationConfig:
765
- """Soft prompt parameterization configuration (Phase F: The Learning Brain).
766
-
767
- Controls pattern extraction, prompt generation, injection, and lifecycle.
768
- Ships enabled by default. Pure text soft prompts — no LoRA, no weights.
769
- """
770
-
771
- enabled: bool = True
772
-
773
- # Pattern extraction
774
- min_confidence: float = 0.7 # Minimum pattern confidence [0.3, 1.0]
775
- min_evidence: int = 5 # Minimum evidence count for behavioral/workflow
776
- cross_project_boost: float = 1.2 # 20% confidence boost for cross-project patterns
777
-
778
- # Prompt generation
779
- max_prompt_tokens: int = 500 # Token budget for soft prompts
780
- max_memory_tokens: int = 1500 # Token budget for regular memories
781
- categories_enabled: tuple[str, ...] = (
782
- "identity", "tech_preference", "communication_style",
783
- "workflow_pattern", "project_context", "topic_interest",
784
- "decision_history", "avoidance",
785
- )
786
-
787
- # Lifecycle
788
- refresh_interval_hours: float = 24.0 # Min hours between parameterization runs
789
- effectiveness_tracking: bool = True # Track prompt effectiveness via feedback
790
-
791
-
792
- @dataclass(frozen=True)
793
- class TemporalValidatorConfig:
794
- """Configuration for temporal intelligence (Phase 4).
795
-
796
- Ships enabled by default. Users can disable via slm config.
797
- """
798
-
799
- enabled: bool = True
800
- mode: str = "a" # "a" (sheaf), "b"/"c" (LLM)
801
-
802
- # Sheaf contradiction threshold
803
- contradiction_threshold: float = 0.45 # Mode A threshold (768d)
804
-
805
- # P5-INT-01: superseded facts are DEMOTED in recall, not hidden. A fact
806
- # marked system_expired_at keeps its channel evidence but its per-channel
807
- # score is multiplied by this factor, so current facts rank above it while
808
- # nothing valid silently vanishes (Mem0-2026 non-destructive design;
809
- # retrieval-time recency resolves conflicts). 0.0 restores the old hide
810
- # behaviour; 1.0 disables demotion.
811
- superseded_demotion_factor: float = 0.25
812
-
813
- # LLM pre-filter threshold (lower to catch more candidates)
814
- llm_prefilter_threshold: float = 0.30
815
-
816
- # Max LLM checks per new fact (cost control)
817
- max_llm_checks: int = 5
818
-
819
- # Trust penalty for expired facts
820
- expiration_trust_penalty: float = -0.2
821
-
822
- # Include expired facts in historical queries
823
- include_expired_in_history: bool = True
824
-
825
- # Event-time (valid_until) demotion factor, applied like
826
- # superseded_demotion_factor but for facts whose stated validity window has
827
- # elapsed. Default matches the prior hardcoded constant (0.5).
828
- event_time_demotion_factor: float = 0.5
829
-
830
-
831
- @dataclass(frozen=True)
832
- class EvolutionConfig:
833
- """Configuration for Skill Evolution Engine (v3.4.10).
834
-
835
- OFF by default — opt in via `slm setup` (interactive) or
836
- `slm config set evolution.enabled true` (CLI). Enabling makes
837
- background LLM calls; the enable flow surfaces a cost advisory.
838
-
839
- Backend auto-detection priority:
840
- 1. `claude` CLI available → spawn `claude --model haiku` (ECC pattern, free)
841
- 2. Ollama running → use Ollama (free, local)
842
- 3. API key set → use Anthropic/OpenAI API (paid)
843
- 4. Nothing → dashboard-only (show candidates, manual evolution)
844
-
845
- Model selection (v3.7.9): each pipeline step is configurable and
846
- defaults to the lowest-cost capable model for the active backend
847
- (Claude→haiku, Ollama→local). Leave a field empty ("") for "auto".
848
- The blind verifier is kept on a *different* model from the generator
849
- so it can't grade its own homework; see
850
- ``evolution.model_selection.resolve_evolution_models``.
851
- """
852
-
853
- enabled: bool = False # OFF by default, opt-in
854
- backend: str = "auto" # auto, claude, ollama, anthropic, openai
855
- max_evolutions_per_cycle: int = 3 # Budget cap per consolidation
856
- # Empty string == "auto" (resolve cheapest capable model at runtime).
857
- # Accepts short aliases ("haiku"/"sonnet") or allow-listed model ids.
858
- mutation_model: str = "" # generator (quality-sensitive)
859
- verify_model: str = "" # blind verifier (must differ from generator)
860
- confirm_model: str = "" # cheap yes/no gate
861
-
862
-
863
- @dataclass(frozen=True)
864
- class AutoInvokeConfig:
865
- """Configuration for the Auto-Invoke Engine (Phase 2).
866
-
867
- Ships enabled by default. Users can disable via slm config.
868
-
869
- References:
870
- - SYNAPSE: FOK gating (fok_threshold = 0.12)
871
- - ACT-R: base-level activation (act_r_decay = 0.5)
872
- - Zep/Hindsight: multi-signal ranking consensus
873
- """
874
-
875
- enabled: bool = True
876
- profile_id: str = "default"
877
-
878
- # Scoring weights (4-signal default) -- must sum to 1.0
879
- weights: dict = field(default_factory=lambda: {
880
- "similarity": 0.40,
881
- "recency": 0.25,
882
- "frequency": 0.20,
883
- "trust": 0.15,
884
- })
885
-
886
- # ACT-R mode (3-signal alternative) -- must sum to 1.0
887
- use_act_r: bool = False
888
- act_r_weights: dict = field(default_factory=lambda: {
889
- "similarity": 0.40,
890
- "base_level": 0.35,
891
- "trust": 0.25,
892
- })
893
- act_r_decay: float = 0.5 # Power-law decay exponent
894
-
895
- # FOK gating (Feeling-of-Knowing)
896
- fok_threshold: float = 0.12 # SYNAPSE minimum score gate
897
-
898
- # Retrieval limits
899
- max_memories_injected: int = 10
900
- candidate_multiplier: int = 3 # candidates = limit * multiplier
901
-
902
- # Mode A degradation weights -- must sum to 1.0
903
- mode_a_weights: dict = field(default_factory=lambda: {
904
- "similarity": 0.00,
905
- "recency": 0.40,
906
- "frequency": 0.35,
907
- "trust": 0.25,
908
- })
909
-
910
- # Behavioral
911
- include_archived: bool = False
912
- relevance_threshold: float = 0.3 # Legacy compat with AutoRecall
913
-
914
-
915
- # ---------------------------------------------------------------------------
916
- # Deployment Config (v3.8.0)
917
- # ---------------------------------------------------------------------------
918
-
919
- _VALID_DEPLOYMENT_MODES = ("personal", "enterprise")
920
-
921
-
922
- @dataclass(frozen=True)
923
- class DeploymentConfig:
924
- """Deployment mode configuration.
925
-
926
- Personal (default, safe defaults):
927
- Single-user install. No login required, no PII redaction, no
928
- retention scheduler. Behaviour is identical to pre-3.8.0 installs.
929
-
930
- Enterprise:
931
- Multi-user / team / company install. Login required, PII redacted,
932
- retention scheduler active, audit enabled.
933
-
934
- SAFE DEFAULTS = Personal — an existing config.toml with no [deployment]
935
- section continues to behave exactly as before. Adding a [deployment]
936
- section is opt-in; removing it reverts to Personal automatically.
937
- """
938
-
939
- mode: str = "personal" # "personal" | "enterprise"
940
- require_login: bool = False
941
- pii_redaction: bool = False
942
- retention_enabled: bool = False
943
- audit: bool = True
944
-
945
- def __post_init__(self) -> None:
946
- if self.mode not in _VALID_DEPLOYMENT_MODES:
947
- raise ValueError(
948
- f"DeploymentConfig.mode must be one of {_VALID_DEPLOYMENT_MODES!r}, "
949
- f"got {self.mode!r}"
950
- )
951
-
952
- @property
953
- def is_personal(self) -> bool:
954
- """True when operating in personal (single-user) mode."""
955
- return self.mode == "personal"
956
-
957
- @property
958
- def is_enterprise(self) -> bool:
959
- """True when operating in enterprise (team/company) mode."""
960
- return self.mode == "enterprise"
961
-
962
- def as_dict(self) -> dict:
963
- return {
964
- "mode": self.mode,
965
- "require_login": self.require_login,
966
- "pii_redaction": self.pii_redaction,
967
- "retention_enabled": self.retention_enabled,
968
- "audit": self.audit,
969
- }
970
-
971
-
972
- #: Canonical Personal preset — all permissive defaults.
973
- DEPLOYMENT_PERSONAL = DeploymentConfig(
974
- mode="personal",
975
- require_login=False,
976
- pii_redaction=False,
977
- retention_enabled=False,
978
- audit=True,
979
- )
980
-
981
- #: Canonical Enterprise preset — all enforcement defaults.
982
- DEPLOYMENT_ENTERPRISE = DeploymentConfig(
983
- mode="enterprise",
984
- require_login=True,
985
- pii_redaction=True,
986
- retention_enabled=True,
987
- audit=True,
988
- )
989
-
990
-
991
- def load_deployment_config(
992
- config_toml_path: Path | None = None,
993
- ) -> DeploymentConfig:
994
- """Parse [deployment] from config.toml with fail-closed invalid-state handling.
995
-
996
- config.toml is the installer-written performance config (separate from the
997
- daemon's config.json managed by SLMConfig). This function reads ONLY the
998
- [deployment] section. All other sections are ignored.
999
-
1000
- Args:
1001
- config_toml_path: Explicit path to config.toml. When None, resolved to
1002
- ``~/.superlocalmemory/config.toml`` via the canonical data root.
1003
-
1004
- Returns:
1005
- DeploymentConfig — Personal only when the file or deployment section is
1006
- absent. A present but invalid deployment configuration resolves to the
1007
- enterprise preset so startup controls cannot silently downgrade.
1008
- """
1009
- if config_toml_path is None:
1010
- try:
1011
- config_toml_path = _runtime_base_dir() / "config.toml"
1012
- except Exception:
1013
- return DEPLOYMENT_PERSONAL
1014
-
1015
- if not config_toml_path.exists():
1016
- return DEPLOYMENT_PERSONAL
1017
-
1018
- try:
1019
- import tomllib as _tomllib
1020
- raw = config_toml_path.read_text(encoding="utf-8")
1021
- data = _tomllib.loads(raw)
1022
- except Exception as exc:
1023
- logger.warning(
1024
- "load_deployment_config: failed to parse %s: %s", config_toml_path, exc
1025
- )
1026
- return DEPLOYMENT_ENTERPRISE
1027
-
1028
- if "deployment" not in data:
1029
- # No [deployment] section — personal defaults, no behaviour change.
1030
- return DEPLOYMENT_PERSONAL
1031
- dep = data["deployment"]
1032
- if not isinstance(dep, dict):
1033
- logger.warning(
1034
- "load_deployment_config: [deployment] in %s is not a table — "
1035
- "failing closed to enterprise",
1036
- config_toml_path,
1037
- )
1038
- return DEPLOYMENT_ENTERPRISE
1039
-
1040
- raw_mode = str(dep.get("mode", "")).lower()
1041
- if raw_mode not in _VALID_DEPLOYMENT_MODES:
1042
- logger.warning(
1043
- "load_deployment_config: unknown mode %r in %s — failing closed to enterprise",
1044
- raw_mode, config_toml_path,
1045
- )
1046
- return DEPLOYMENT_ENTERPRISE
1047
-
1048
- # Use the preset for the mode as the base so omitted keys inherit
1049
- # sensible values (enterprise → require_login=True etc.).
1050
- base = DEPLOYMENT_ENTERPRISE if raw_mode == "enterprise" else DEPLOYMENT_PERSONAL
1051
-
1052
- def _strict_bool(name: str, default: bool) -> bool:
1053
- value = dep.get(name, default)
1054
- if not isinstance(value, bool):
1055
- raise TypeError(f"{name} must be a TOML boolean")
1056
- return value
1057
-
1058
- try:
1059
- return DeploymentConfig(
1060
- mode=raw_mode,
1061
- require_login=_strict_bool("require_login", base.require_login),
1062
- pii_redaction=_strict_bool("pii_redaction", base.pii_redaction),
1063
- retention_enabled=_strict_bool(
1064
- "retention_enabled", base.retention_enabled
1065
- ),
1066
- audit=_strict_bool("audit", base.audit),
1067
- )
1068
- except (ValueError, TypeError) as exc:
1069
- logger.warning(
1070
- "load_deployment_config: invalid values in [deployment] in %s: %s — "
1071
- "failing closed to enterprise",
1072
- config_toml_path, exc,
1073
- )
1074
- return DEPLOYMENT_ENTERPRISE
1075
-
1076
-
1077
- # ---------------------------------------------------------------------------
1078
- # Health Config (v3.6.9 BUG-A)
1079
- # ---------------------------------------------------------------------------
1080
-
1081
- @dataclass
1082
- class HealthConfig:
1083
- """Health-monitor tuning knobs.
1084
-
1085
- All values have safe defaults so an empty ``"health": {}`` JSON section
1086
- silently inherits them. The ``global_rss_budget_mb`` default is computed
1087
- at runtime (40% of physical RAM, floor 2500 MB) so low-RAM boxes keep the
1088
- old behaviour while large machines are never accidentally throttled.
1089
- """
1090
- global_rss_budget_mb: int = 0 # 0 = compute at runtime (40% RAM, floor 2500)
1091
- heartbeat_timeout_sec: int = 60
1092
- health_check_interval_sec: int = 15
1093
- enable_structured_logging: bool = True
1094
-
1095
-
1096
- # ---------------------------------------------------------------------------
1097
- # Graph Pruning Config (Workstream G — #84)
1098
- # ---------------------------------------------------------------------------
1099
-
1100
- @dataclass
1101
- class GraphPruningConfig:
1102
- """Graph thinning parameters. Exposed via dashboard + CLI.
1103
-
1104
- Defaults reproduce the previous hard-coded behaviour so existing
1105
- deployments see zero behavioural change after upgrade.
1106
- """
1107
-
1108
- #: Maximum in- or out-degree per node. Maps to the legacy module-level
1109
- #: ``_MAX_DEGREE_PER_NODE = 100`` in graph_pruner.py.
1110
- max_degree_per_node: int = 100
1111
- #: Discard edges with weight strictly below this floor.
1112
- #: 0.0 = no floor (current behaviour, always-safe default).
1113
- min_edge_weight: float = 0.0
1114
- #: Master kill-switch for all graph pruning. ``False`` skips the entire
1115
- #: prune step (useful for debugging graph bloat).
1116
- enabled: bool = True
1117
-
1118
-
1119
- # ---------------------------------------------------------------------------
1120
- # Master Config
1121
- # ---------------------------------------------------------------------------
1122
-
1123
- @dataclass
1124
- class SLMConfig:
1125
- """Master configuration for SuperLocalMemory V3.
1126
-
1127
- Create via SLMConfig.for_mode(Mode.A) for mode-specific defaults.
1128
- """
1129
-
1130
- mode: Mode = Mode.A
1131
- base_dir: Path = field(default_factory=_runtime_base_dir)
1132
- db_path: Path | None = None # Computed from base_dir if None
1133
- active_profile: str = "default"
1134
-
1135
- embedding: EmbeddingConfig = field(default_factory=EmbeddingConfig)
1136
- llm: LLMConfig = field(default_factory=LLMConfig)
1137
- channel_weights: ChannelWeights = field(default_factory=ChannelWeights)
1138
- scope_weights: ScopeWeights = field(default_factory=ScopeWeights)
1139
- scope: ScopeConfig = field(default_factory=ScopeConfig)
1140
- encoding: EncodingConfig = field(default_factory=EncodingConfig)
1141
- retrieval: RetrievalConfig = field(default_factory=RetrievalConfig)
1142
- math: MathConfig = field(default_factory=MathConfig)
1143
- temporal_validator: TemporalValidatorConfig = field(
1144
- default_factory=TemporalValidatorConfig,
1145
- )
1146
- auto_invoke: AutoInvokeConfig = field(default_factory=AutoInvokeConfig)
1147
- consolidation: ConsolidationConfig = field(
1148
- default_factory=ConsolidationConfig,
1149
- )
1150
- forgetting: ForgettingConfig = field(default_factory=ForgettingConfig)
1151
- hopfield: HopfieldConfig = field(default_factory=HopfieldConfig)
1152
- reaper: ReaperConfig = field(default_factory=ReaperConfig)
1153
- quantization: QuantizationConfig = field(default_factory=QuantizationConfig)
1154
- sagq: SAGQConfig = field(default_factory=SAGQConfig)
1155
- ccq: CCQConfig = field(default_factory=CCQConfig)
1156
- parameterization: ParameterizationConfig = field(
1157
- default_factory=ParameterizationConfig,
1158
- )
1159
- injection: InjectionConfig = field(default_factory=InjectionConfig)
1160
- store: StoreConfig = field(default_factory=StoreConfig)
1161
- # v3.5.0: scaling backends — "sqlite" / "cozo" / "auto" / "lancedb" / "sqlite-vec" / "auto".
1162
- graph_backend: str = "auto" # "auto" = cozo if pycozo installed, else sqlite
1163
- vector_backend: str = "auto" # "auto" = lancedb if installed, else sqlite-vec
1164
- # Scale Engine is installed separately from activation. Existing roots
1165
- # stay on SQLite until a staged parity check promotes these projections.
1166
- scale_engine_state: str = "local_core" # local_core | prepared | verified | promoted
1167
- # v3.8.5: auto-promote Cozo+LanceDB when a DB grows past the scale at which
1168
- # they actually help. Auto-promotion is background, uses the same staged
1169
- # parity gate as the manual path, and falls back to SQLite on any failure.
1170
- #
1171
- # 100,000 (was 1,000,000, set by Varun 2026-08-22). The old value gated every
1172
- # store anyone actually has -- the author's is 140,000 edges -- so the
1173
- # projection was built for nobody and, being unexercised, was wrong: it
1174
- # carried 1,257 memories the store may not return and its graph search
1175
- # disagreed with SQLite on every query. A threshold no real store crosses is
1176
- # not a safety margin, it is a feature nobody tests.
1177
- #
1178
- # What promotion now changes is narrow and measured. The graph WALK does not
1179
- # move: SQLite loads 130k edges in 141 ms against CozoDB's 252 ms, so the
1180
- # adjacency read stays where it is fastest (retrieval/graph_adjacency.py
1181
- # holds the seam for the day that flips). What promotion switches on is the
1182
- # LanceDB vector index, which is within noise of sqlite-vec at this size
1183
- # (18.3 ms against 18.7 ms over 5,324 vectors) and is the one built to keep
1184
- # going as the vector count grows. So crossing this line is close to
1185
- # latency-neutral today and buys the projection real exercise.
1186
- scale_auto_promote_enabled: bool = True
1187
- scale_auto_promote_min_edges: int = 100_000
1188
- evolution: EvolutionConfig = field(default_factory=EvolutionConfig)
1189
- health: HealthConfig = field(default_factory=HealthConfig)
1190
- # v3.8.4-G: Graph thinning parameters (#84)
1191
- graph_pruning: GraphPruningConfig = field(default_factory=GraphPruningConfig)
1192
-
1193
- # v3.4.3: Daemon configuration
1194
- daemon_idle_timeout: int = 0 # 0 = 24/7 (no auto-kill). >0 = seconds before auto-kill.
1195
- daemon_port: int = 8765 # Primary daemon port
1196
- daemon_legacy_port: int = 8767 # Backward-compat redirect port
1197
- daemon_enable_legacy_port: bool = True # Set False to disable 8767 redirect
1198
-
1199
- # v4: auto-close orphaned application sessions during maintenance.
1200
- # A session is stale when it has had no new atomic_facts for this many
1201
- # hours. close_session is only invoked by the MCP tool otherwise, so
1202
- # without this pass temporal summaries never form for abandoned sessions.
1203
- session_idle_close_hours: float = 24.0
1204
- session_idle_close_max_per_pass: int = 50
1205
-
1206
- # v3.4.3: Entity compilation
1207
- entity_compilation_enabled: bool = True
1208
- entity_compilation_retrieval_boost: float = 1.0 # 1.0 = disabled. >1.0 = boost score.
1209
-
1210
- # v3.4.3: Mesh
1211
- mesh_enabled: bool = True
1212
-
1213
- # Deployment policy overlay. Personal installs remain unchanged; the
1214
- # daemon upgrades this to True before engine initialization when the
1215
- # enterprise deployment preset requests PII redaction.
1216
- pii_redaction: bool = False
1217
-
1218
- # GDPR / compliance: data-retention window in days.
1219
- # Default 90 days (owner decision: "configurable through the UI, and
1220
- # 90 days by default"). Units: days. Value of 0 means no automatic
1221
- # expiry. Consumed by the backup/erasure obligation tracker and any
1222
- # future retention scheduler. Configurable via config.json (key
1223
- # "retention_window_days") or the UI settings panel.
1224
- # NOTE: this field is intentionally separate from
1225
- # DeploymentConfig.retention_enabled — the enabled flag gates the
1226
- # scheduler, while this window controls the duration.
1227
- retention_window_days: int = 90
1228
-
1229
- def __post_init__(self) -> None:
1230
- if self.db_path is None:
1231
- self.db_path = self.base_dir / DEFAULT_DB_NAME
1232
-
1233
- @classmethod
1234
- def load(cls, config_path: Path | None = None) -> SLMConfig:
1235
- """Load config from JSON file. Returns default Mode A if file doesn't exist."""
1236
- # V3.7: process namespace selection is authoritative. A persisted
1237
- # base_dir may bootstrap a legacy custom root only when no environment
1238
- # alias selected the process namespace.
1239
- try:
1240
- from superlocalmemory.infra.data_root import (
1241
- canonical_data_root,
1242
- environment_data_root,
1243
- )
1244
- _environment_base = environment_data_root()
1245
- _runtime_base = canonical_data_root()
1246
- except Exception:
1247
- _environment_base = None
1248
- _runtime_base = DEFAULT_BASE_DIR
1249
- path = config_path or (_runtime_base / "config.json")
1250
- if not path.exists():
1251
- return cls.for_mode(Mode.A, base_dir=_runtime_base)
1252
-
1253
- # DASH-V4 (3.7.9): the ``current_mode`` file is the single source of
1254
- # truth for the ACTIVE mode. If config.json drifted from it (a writer
1255
- # bypassed switch_mode and reset the active config), load the
1256
- # authoritative per-mode file so daemon/CLI/dashboard all run the user's
1257
- # chosen mode — with its settings — instead of a stale one. Scoped to
1258
- # config.json so explicit per-mode loads (switch_mode) are untouched.
1259
- # Fail-open: any error falls through to the normal load below.
1260
- if path.name == "config.json":
1261
- try:
1262
- import json as _json
1263
- _disk_mode = str(
1264
- _json.loads(path.read_text()).get("mode", "")
1265
- ).lower()
1266
- _active_mode = cls.read_current_mode(path.parent)
1267
- if _disk_mode and _active_mode and _disk_mode != _active_mode:
1268
- _mode_path = cls._mode_config_path(
1269
- path.parent, Mode(_active_mode)
1270
- )
1271
- if _mode_path.exists():
1272
- logger.warning(
1273
- "config.json mode=%s disagrees with current_mode=%s;"
1274
- " loading authoritative %s",
1275
- _disk_mode, _active_mode, _mode_path.name,
1276
- )
1277
- return cls.load(_mode_path)
1278
- except Exception:
1279
- pass # fail-open — fall through to the normal load below
1280
-
1281
- import json
1282
- try:
1283
- data = json.loads(path.read_text())
1284
- except (json.JSONDecodeError, OSError) as exc:
1285
- # An already-corrupt/truncated config.json must NOT brick every `slm`
1286
- # call — degrade to the Mode-A default and warn rather than raise.
1287
- logger.warning(
1288
- "config.json unreadable/corrupt (%s) — using Mode A default", exc
1289
- )
1290
- return cls.for_mode(Mode.A, base_dir=_runtime_base)
1291
- mode = Mode(data.get("mode", "a"))
1292
- llm_data = data.get("llm", {})
1293
- emb_data = data.get("embedding", {})
1294
- # V3.5.9: read base_dir before constructing config so for_mode() builds
1295
- # db_path from the user's directory, not DEFAULT_BASE_DIR.
1296
- if _environment_base is not None:
1297
- raw_base_dir = _environment_base
1298
- else:
1299
- try:
1300
- raw_base_dir = canonical_data_root(
1301
- configured_base_dir=data.get("base_dir", _runtime_base),
1302
- )
1303
- except Exception:
1304
- raw_base_dir = _runtime_base
1305
- config = cls.for_mode(
1306
- mode,
1307
- llm_provider=llm_data.get("provider", ""),
1308
- llm_model=llm_data.get("model", ""),
1309
- llm_api_key=llm_data.get("api_key", ""),
1310
- llm_api_base=llm_data.get("base_url", ""),
1311
- embedding_provider=emb_data.get("provider", ""),
1312
- embedding_endpoint=emb_data.get("api_endpoint", ""),
1313
- embedding_key=emb_data.get("api_key", ""),
1314
- embedding_deployment=emb_data.get("deployment_name", ""),
1315
- embedding_model_name=emb_data.get("model_name", ""),
1316
- embedding_dimension=int(emb_data.get("dimension", 0) or 0),
1317
- base_dir=raw_base_dir,
1318
- )
1319
- config.active_profile = data.get("active_profile", "default")
1320
- config.graph_backend = data.get("graph_backend", "auto")
1321
- config.vector_backend = data.get("vector_backend", "auto")
1322
- state = data.get("scale_engine_state", "local_core")
1323
- config.scale_engine_state = (
1324
- state if state in {"local_core", "prepared", "verified", "promoted"}
1325
- else "local_core"
1326
- )
1327
- config.scale_auto_promote_enabled = bool(
1328
- data.get("scale_auto_promote_enabled", True)
1329
- )
1330
- try:
1331
- config.scale_auto_promote_min_edges = int(
1332
- data.get("scale_auto_promote_min_edges", 100_000)
1333
- )
1334
- except (TypeError, ValueError):
1335
- config.scale_auto_promote_min_edges = 100_000
1336
-
1337
- # V3.3 config fields (additive — defaults work if missing from JSON)
1338
- fg = data.get("forgetting", {})
1339
- if fg:
1340
- config.forgetting = ForgettingConfig(**{
1341
- k: v for k, v in fg.items()
1342
- if k in ForgettingConfig.__dataclass_fields__
1343
- })
1344
-
1345
- # quantization: nested PolarQuantConfig + QJLConfig are reconstructed
1346
- # from their serialized dicts; scalar fields are passed through directly.
1347
- # Unknown subkeys are filtered out for forward-compat safety.
1348
- qt_raw = data.get("quantization", {})
1349
- if qt_raw:
1350
- try:
1351
- polar_raw = qt_raw.get("polar", {})
1352
- qjl_raw = qt_raw.get("qjl", {})
1353
- polar = PolarQuantConfig(**{
1354
- k: v for k, v in polar_raw.items()
1355
- if k in PolarQuantConfig.__dataclass_fields__
1356
- })
1357
- qjl = QJLConfig(**{
1358
- k: v for k, v in qjl_raw.items()
1359
- if k in QJLConfig.__dataclass_fields__
1360
- })
1361
- qt_scalars = {
1362
- k: v for k, v in qt_raw.items()
1363
- if k in QuantizationConfig.__dataclass_fields__
1364
- and k not in ("polar", "qjl")
1365
- }
1366
- config.quantization = QuantizationConfig(polar=polar, qjl=qjl, **qt_scalars)
1367
- except (TypeError, ValueError) as exc:
1368
- logger.warning(
1369
- "Ignoring invalid quantization config (%s) — using defaults", exc
1370
- )
1371
-
1372
- # sagq: valid_bit_widths is serialized as a list by dataclasses.asdict()
1373
- # and must be coerced back to a tuple to satisfy the field annotation and
1374
- # preserve identity through a round-trip.
1375
- sq_raw = data.get("sagq", {})
1376
- if sq_raw:
1377
- try:
1378
- sq_kwargs = {
1379
- k: v for k, v in sq_raw.items()
1380
- if k in SAGQConfig.__dataclass_fields__
1381
- }
1382
- if "valid_bit_widths" in sq_kwargs:
1383
- sq_kwargs["valid_bit_widths"] = tuple(sq_kwargs["valid_bit_widths"])
1384
- config.sagq = SAGQConfig(**sq_kwargs)
1385
- except (TypeError, ValueError) as exc:
1386
- logger.warning(
1387
- "Ignoring invalid sagq config (%s) — using defaults", exc
1388
- )
1389
-
1390
- # auto_invoke: dict fields (weights, act_r_weights, mode_a_weights) are
1391
- # preserved as-is by asdict(); no special reconstruction required.
1392
- ai_raw = data.get("auto_invoke", {})
1393
- if ai_raw:
1394
- try:
1395
- config.auto_invoke = AutoInvokeConfig(**{
1396
- k: v for k, v in ai_raw.items()
1397
- if k in AutoInvokeConfig.__dataclass_fields__
1398
- })
1399
- except (TypeError, ValueError) as exc:
1400
- logger.warning(
1401
- "Ignoring invalid auto_invoke config (%s) — using defaults", exc
1402
- )
1403
-
1404
- rt = data.get("retrieval", {})
1405
- if rt:
1406
- # V3.3.2 migration: add ONNX cross-encoder backend field.
1407
- # Pre-3.3.2 configs lacked cross_encoder_backend. Add it,
1408
- # but NEVER override an explicit use_cross_encoder setting.
1409
- # The user's explicit choice always wins.
1410
- if "cross_encoder_backend" not in rt:
1411
- rt.setdefault("cross_encoder_model", "cross-encoder/ms-marco-MiniLM-L-12-v2")
1412
- rt["cross_encoder_backend"] = "" # V3.3.18: PyTorch (ONNX CoreML leaks on ARM64)
1413
- # Only auto-enable if user didn't explicitly set the field
1414
- rt.setdefault("use_cross_encoder", True)
1415
- config.retrieval = RetrievalConfig(**{
1416
- k: v for k, v in rt.items()
1417
- if k in RetrievalConfig.__dataclass_fields__
1418
- })
1419
-
1420
- # 4.1.0 (#124): restore the two sections save() now writes. This runs
1421
- # AFTER for_mode() has applied its presets, so a value someone chose
1422
- # beats the mode default -- the same ordering retrieval already relies
1423
- # on. A malformed section falls back to the dataclass defaults rather
1424
- # than raising, matching quantization and sagq above: a corrupt config
1425
- # file must not make `slm` unrunnable.
1426
- mth = data.get("math")
1427
- if isinstance(mth, dict) and mth:
1428
- try:
1429
- fields = {
1430
- k: v for k, v in mth.items()
1431
- if k in MathConfig.__dataclass_fields__
1432
- }
1433
- # JSON has no tuple. Written as a list, it must come back a
1434
- # tuple or every reader that unpacks a pair gets a list and the
1435
- # difference surfaces somewhere far from here.
1436
- rng = fields.get("langevin_weight_range")
1437
- if isinstance(rng, list):
1438
- fields["langevin_weight_range"] = tuple(rng)
1439
- config.math = MathConfig(**fields)
1440
- except (TypeError, ValueError) as exc:
1441
- logger.warning(
1442
- "Ignoring invalid math config (%s) — using defaults", exc
1443
- )
1444
-
1445
- cw = data.get("channel_weights")
1446
- if isinstance(cw, dict) and cw:
1447
- try:
1448
- config.channel_weights = ChannelWeights(**{
1449
- k: v for k, v in cw.items()
1450
- if k in ChannelWeights.__dataclass_fields__
1451
- })
1452
- except (TypeError, ValueError) as exc:
1453
- logger.warning(
1454
- "Ignoring invalid channel_weights config (%s) — using defaults",
1455
- exc,
1456
- )
1457
-
1458
- # V3.4.3 config fields (additive — missing keys get dataclass defaults)
1459
- config.daemon_idle_timeout = data.get("daemon_idle_timeout", 0)
1460
- config.daemon_port = data.get("daemon_port", 8765)
1461
- config.session_idle_close_hours = float(
1462
- data.get("session_idle_close_hours", 24.0) or 24.0
1463
- )
1464
- config.session_idle_close_max_per_pass = int(
1465
- data.get("session_idle_close_max_per_pass", 50) or 50
1466
- )
1467
- config.daemon_legacy_port = data.get("daemon_legacy_port", 8767)
1468
- config.daemon_enable_legacy_port = data.get("daemon_enable_legacy_port", True)
1469
- config.entity_compilation_enabled = data.get("entity_compilation_enabled", True)
1470
- config.entity_compilation_retrieval_boost = data.get(
1471
- "entity_compilation_retrieval_boost", 1.0,
1472
- )
1473
- config.mesh_enabled = data.get("mesh_enabled", True)
1474
-
1475
- # v4.0.6: GDPR retention window (additive — defaults to 90 if absent)
1476
- try:
1477
- config.retention_window_days = int(data.get("retention_window_days", 90))
1478
- except (TypeError, ValueError):
1479
- config.retention_window_days = 90
1480
-
1481
- # V3.4.10: Evolution config
1482
- evo = data.get("evolution", {})
1483
- if evo:
1484
- config.evolution = EvolutionConfig(**{
1485
- k: v for k, v in evo.items()
1486
- if k in EvolutionConfig.__dataclass_fields__
1487
- })
1488
-
1489
- # V3.6.9: Health monitor config (BUG-A — previously silently ignored)
1490
- hlth = data.get("health", {})
1491
- if hlth:
1492
- config.health = HealthConfig(**{
1493
- k: v for k, v in hlth.items()
1494
- if k in HealthConfig.__dataclass_fields__
1495
- })
1496
-
1497
- # v3.8.4-G: Graph pruning config (#84) — additive, no migration needed.
1498
- # Old configs without this section silently fall back to GraphPruningConfig()
1499
- # defaults, which reproduce the existing _MAX_DEGREE_PER_NODE=100 behaviour.
1500
- gp_raw = data.get("graph_pruning", {})
1501
- if gp_raw:
1502
- try:
1503
- gp_kwargs = {
1504
- k: v for k, v in gp_raw.items()
1505
- if k in GraphPruningConfig.__dataclass_fields__
1506
- }
1507
- gp = GraphPruningConfig(**gp_kwargs)
1508
- # Validate and clamp out-of-range values
1509
- max_deg = gp.max_degree_per_node if gp.max_degree_per_node >= 1 else 100
1510
- min_w = max(0.0, min(1.0, gp.min_edge_weight))
1511
- config.graph_pruning = GraphPruningConfig(
1512
- max_degree_per_node=max_deg,
1513
- min_edge_weight=min_w,
1514
- enabled=gp.enabled,
1515
- )
1516
- except (ValueError, TypeError) as exc:
1517
- logger.warning(
1518
- "graph_pruning config invalid (%s) — using defaults", exc
1519
- )
1520
- config.graph_pruning = GraphPruningConfig()
1521
-
1522
- # V3.4.65: Injection config (additive — defaults if missing from JSON)
1523
- inj = data.get("injection", {}) or {}
1524
- config.injection = InjectionConfig(
1525
- enabled=bool(inj.get("enabled", True)),
1526
- total_budget_tokens_a=int(inj.get("total_budget_tokens_a", 2000)),
1527
- total_budget_tokens_b=int(inj.get("total_budget_tokens_b", 4000)),
1528
- total_budget_tokens_c=int(inj.get("total_budget_tokens_c", 8000)),
1529
- per_memory_max_tokens=int(inj.get("per_memory_max_tokens", 600)),
1530
- core_block_enabled=bool(inj.get("core_block_enabled", True)),
1531
- core_block_max_facts=int(inj.get("core_block_max_facts", 5)),
1532
- core_block_max_tokens=int(inj.get("core_block_max_tokens", 1000)),
1533
- core_block_importance_min=float(inj.get("core_block_importance_min", 0.8)),
1534
- core_block_min_access_count=int(inj.get("core_block_min_access_count", 2)),
1535
- edge_ordering=bool(inj.get("edge_ordering", True)),
1536
- trust_first_party=bool(inj.get("trust_first_party", False)),
1537
- prestage_max_response_bytes=int(inj.get("prestage_max_response_bytes", 64 * 1024)),
1538
- )
1539
-
1540
- # Multi-scope memory: scope weights
1541
- sw = data.get("scope_weights", {})
1542
- if sw:
1543
- # v3.6.15: a malformed value (e.g. negative weight) must NOT brick
1544
- # every `slm` command via an uncaught ValueError out of load().
1545
- # Fall back to defaults and warn — the config is recoverable.
1546
- try:
1547
- config.scope_weights = ScopeWeights(**{
1548
- k: v for k, v in sw.items()
1549
- if k in ScopeWeights.__dataclass_fields__
1550
- })
1551
- except (ValueError, TypeError) as exc:
1552
- logger.warning(
1553
- "Ignoring invalid scope_weights in config (%s) — using defaults", exc
1554
- )
1555
-
1556
- # Multi-scope memory: behaviour defaults (default scope + recall visibility)
1557
- sc = data.get("scope", {})
1558
- if sc:
1559
- # Same guard: a typo'd default_scope must not crash the whole CLI.
1560
- try:
1561
- config.scope = ScopeConfig(**{
1562
- k: v for k, v in sc.items()
1563
- if k in ScopeConfig.__dataclass_fields__
1564
- })
1565
- except (ValueError, TypeError) as exc:
1566
- logger.warning(
1567
- "Ignoring invalid scope config (%s) — using shared-off defaults", exc
1568
- )
1569
-
1570
- # Preserve the raw loaded dict so save() can return every key that was
1571
- # present in the file, including unknown forward-compat keys and nested
1572
- # sections whose subkeys this version does not model. The attribute is
1573
- # intentionally private (leading underscore) and is not a declared field,
1574
- # so dataclasses.asdict() and dataclasses.replace() ignore it — callers
1575
- # that clone a config via replace() will lose unknown-key preservation,
1576
- # which is acceptable: the primary contract is load→save round-trip.
1577
- config._raw_preserved: dict = dict(data) # type: ignore[attr-defined]
1578
- return config
1579
-
1580
- def save(
1581
- self,
1582
- config_path: Path | None = None,
1583
- *,
1584
- mode_change: bool = False,
1585
- ) -> None:
1586
- """Save config to JSON file.
1587
-
1588
- v3.4.34: mode protection. If the existing config.json has a mode
1589
- that differs from ``self.mode`` and the caller did NOT pass
1590
- ``mode_change=True``, the EXISTING mode is preserved. This
1591
- prevents accidental mode resets when code creates a fresh
1592
- ``SLMConfig()`` (defaults to Mode A) and calls ``save()`` to
1593
- persist an unrelated field change.
1594
-
1595
- Callers that intentionally switch mode (``slm mode b``, the MCP
1596
- ``set_mode`` tool, the dashboard PUT ``/api/v3/mode``) MUST pass
1597
- ``mode_change=True``.
1598
- """
1599
- import json
1600
- path = config_path or (self.base_dir / "config.json")
1601
- path.parent.mkdir(parents=True, exist_ok=True)
1602
- # Read existing config to preserve V3.3 fields not in this save
1603
- existing = {}
1604
- if path.exists():
1605
- try:
1606
- existing = json.loads(path.read_text())
1607
- except (json.JSONDecodeError, OSError):
1608
- pass
1609
-
1610
- # v3.4.34: mode protection — preserve user's mode unless explicitly changing
1611
- effective_mode = self.mode.value
1612
- existing_mode = existing.get("mode")
1613
- if existing_mode and existing_mode != effective_mode and not mode_change:
1614
- logger.warning(
1615
- "SLMConfig.save(): mode change blocked (%s → %s). "
1616
- "Pass mode_change=True to override. Preserving '%s'.",
1617
- existing_mode, effective_mode, existing_mode,
1618
- )
1619
- effective_mode = existing_mode
1620
-
1621
- data = {
1622
- "mode": effective_mode,
1623
- "active_profile": self.active_profile,
1624
- "graph_backend": self.graph_backend,
1625
- "vector_backend": self.vector_backend,
1626
- "scale_engine_state": self.scale_engine_state,
1627
- "scale_auto_promote_enabled": self.scale_auto_promote_enabled,
1628
- "scale_auto_promote_min_edges": self.scale_auto_promote_min_edges,
1629
- "base_dir": str(self.base_dir), # V3.5.9: persist so load() can restore custom paths
1630
- "llm": {
1631
- "provider": self.llm.provider,
1632
- "model": self.llm.model,
1633
- "api_key": self.llm.api_key,
1634
- "base_url": self.llm.api_base,
1635
- },
1636
- "embedding": {
1637
- "model_name": self.embedding.model_name,
1638
- "dimension": self.embedding.dimension,
1639
- "provider": self.embedding.provider,
1640
- "api_endpoint": self.embedding.api_endpoint,
1641
- "api_key": self.embedding.api_key,
1642
- "deployment_name": self.embedding.deployment_name,
1643
- },
1644
- # Persist the complete retrieval contract. Saving only the
1645
- # cross-encoder subset silently reset channel limits, evidence
1646
- # floors, and agentic settings on the next mode/provider change.
1647
- "retrieval": asdict(self.retrieval),
1648
- }
1649
-
1650
- # V3.4.11: Persist evolution config (C-CONFIGSAVE fix)
1651
- data["evolution"] = {
1652
- "enabled": self.evolution.enabled,
1653
- "backend": self.evolution.backend,
1654
- "max_evolutions_per_cycle": self.evolution.max_evolutions_per_cycle,
1655
- "mutation_model": self.evolution.mutation_model,
1656
- "verify_model": self.evolution.verify_model,
1657
- "confirm_model": self.evolution.confirm_model,
1658
- }
1659
-
1660
- # V3.4.65: Persist injection config
1661
- data["injection"] = {
1662
- "enabled": self.injection.enabled,
1663
- "total_budget_tokens_a": self.injection.total_budget_tokens_a,
1664
- "total_budget_tokens_b": self.injection.total_budget_tokens_b,
1665
- "total_budget_tokens_c": self.injection.total_budget_tokens_c,
1666
- "per_memory_max_tokens": self.injection.per_memory_max_tokens,
1667
- "core_block_enabled": self.injection.core_block_enabled,
1668
- "core_block_max_facts": self.injection.core_block_max_facts,
1669
- "core_block_max_tokens": self.injection.core_block_max_tokens,
1670
- "core_block_importance_min": self.injection.core_block_importance_min,
1671
- "core_block_min_access_count": self.injection.core_block_min_access_count,
1672
- "edge_ordering": self.injection.edge_ordering,
1673
- "trust_first_party": self.injection.trust_first_party,
1674
- "prestage_max_response_bytes": self.injection.prestage_max_response_bytes,
1675
- }
1676
-
1677
- # Multi-scope memory: scope weights
1678
- data["scope_weights"] = {
1679
- "personal": self.scope_weights.personal,
1680
- "shared": self.scope_weights.shared,
1681
- "global_": self.scope_weights.global_,
1682
- }
1683
-
1684
- # Multi-scope memory: behaviour defaults
1685
- data["scope"] = self.scope.as_dict()
1686
-
1687
- # v3.8.4-G: Persist graph pruning config (#84).
1688
- # Always written — additive key, never overwrites unrelated sections.
1689
- data["graph_pruning"] = {
1690
- "max_degree_per_node": self.graph_pruning.max_degree_per_node,
1691
- "min_edge_weight": self.graph_pruning.min_edge_weight,
1692
- "enabled": self.graph_pruning.enabled,
1693
- }
1694
-
1695
- # Daemon, entity-compilation, and mesh scalar settings were loaded by
1696
- # load() but omitted from earlier versions of this method. Write them
1697
- # explicitly so a load-then-save is lossless for these fields.
1698
- data["daemon_idle_timeout"] = self.daemon_idle_timeout
1699
- data["daemon_port"] = self.daemon_port
1700
- data["session_idle_close_hours"] = self.session_idle_close_hours
1701
- data["session_idle_close_max_per_pass"] = self.session_idle_close_max_per_pass
1702
- data["daemon_legacy_port"] = self.daemon_legacy_port
1703
- data["daemon_enable_legacy_port"] = self.daemon_enable_legacy_port
1704
- data["entity_compilation_enabled"] = self.entity_compilation_enabled
1705
- data["entity_compilation_retrieval_boost"] = self.entity_compilation_retrieval_boost
1706
- data["mesh_enabled"] = self.mesh_enabled
1707
- # v4.0.6: GDPR retention window — always written so load→save is lossless.
1708
- data["retention_window_days"] = self.retention_window_days
1709
-
1710
- # Typed in-memory config sections. Preserve any on-disk subkeys this
1711
- # version does not model (forward-compat or externally-tuned fields such
1712
- # as forgetting.half_life_days), then overlay the current in-memory
1713
- # dataclass values so a programmatic mutation still survives a
1714
- # save/load cycle while unmodeled subkeys are never dropped.
1715
- # dataclasses.asdict() recurses into nested dataclasses
1716
- # (quantization.polar, quantization.qjl).
1717
- #
1718
- # embedding_signature: has no typed in-memory model — it is an opaque blob
1719
- # written by external tooling (see below).
1720
- # ``math`` and ``channel_weights`` join this loop as of 4.1.0. Both are
1721
- # read by engine wiring -- ``math.sheaf_contradiction_threshold`` by the
1722
- # consistency checker, ``channel_weights`` as the retrieval base weights
1723
- # -- and neither was ever written here or restored in ``load()``. Tuning
1724
- # either one, by hand or by switching mode, survived until the next
1725
- # restart and then silently reverted to the dataclass default, with
1726
- # nothing said. Reported as #124.
1727
- for _section, _obj in (
1728
- ("forgetting", self.forgetting),
1729
- ("quantization", self.quantization),
1730
- ("sagq", self.sagq),
1731
- ("auto_invoke", self.auto_invoke),
1732
- ("math", self.math),
1733
- ("channel_weights", self.channel_weights),
1734
- ):
1735
- _base = existing.get(_section)
1736
- _merged = dict(_base) if isinstance(_base, dict) else {}
1737
- _merged.update(asdict(_obj))
1738
- data[_section] = _merged
1739
-
1740
- # Merge any keys from the last load() that are not covered by the
1741
- # explicit serialization above. This includes unknown forward-compat
1742
- # top-level keys and nested sections whose subkeys this version does
1743
- # not model (e.g. a "health" dict that carries vendor-extension fields).
1744
- # Explicitly serialized keys always take precedence; preserved keys are
1745
- # only written when the slot is vacant in the output dict.
1746
- _raw = getattr(self, "_raw_preserved", None)
1747
- if _raw:
1748
- for _k, _v in _raw.items():
1749
- if _k not in data:
1750
- data[_k] = _v
1751
-
1752
- # embedding_signature has no typed in-memory model — it is an opaque blob
1753
- # written by external tooling. Preserve the on-disk value verbatim so a
1754
- # load→save cycle does not erase externally written metadata.
1755
- if "embedding_signature" in existing:
1756
- data["embedding_signature"] = existing["embedding_signature"]
1757
-
1758
- # Atomic write: a crash mid-write must NOT leave a truncated/corrupt
1759
- # config.json (which would make every subsequent `slm` call fail to load).
1760
- import os as _os
1761
- import tempfile as _tempfile
1762
-
1763
- _fd, _tmp_name = _tempfile.mkstemp(
1764
- prefix=f".{path.name}.", suffix=".tmp", dir=path.parent,
1765
- )
1766
- _tmp = Path(_tmp_name)
1767
- try:
1768
- with _os.fdopen(_fd, "w", encoding="utf-8") as _handle:
1769
- json.dump(data, _handle, indent=2)
1770
- _handle.write("\n")
1771
- _handle.flush()
1772
- _os.fsync(_handle.fileno())
1773
- _os.chmod(_tmp, 0o600)
1774
- _os.replace(_tmp, path)
1775
- _os.chmod(path, 0o600)
1776
- except BaseException:
1777
- try:
1778
- _tmp.unlink(missing_ok=True)
1779
- except OSError:
1780
- pass
1781
- raise
1782
-
1783
- @staticmethod
1784
- def provider_presets() -> dict[str, dict[str, str]]:
1785
- """Provider presets for setup wizard."""
1786
- return {
1787
- "openai": {
1788
- "base_url": "https://api.openai.com/v1",
1789
- "model": "gpt-4.1-mini",
1790
- "embedding_model": "text-embedding-3-large",
1791
- "env_key": "OPENAI_API_KEY",
1792
- },
1793
- "anthropic": {
1794
- "base_url": "https://api.anthropic.com",
1795
- "model": "claude-sonnet-4-6",
1796
- "embedding_model": "",
1797
- "env_key": "ANTHROPIC_API_KEY",
1798
- },
1799
- "ollama": {
1800
- "base_url": "http://localhost:11434",
1801
- "model": "llama3.2",
1802
- "embedding_model": "nomic-embed-text",
1803
- "env_key": "",
1804
- },
1805
- "openrouter": {
1806
- "base_url": "https://openrouter.ai/api/v1",
1807
- "model": "openai/gpt-4.1-mini",
1808
- "embedding_model": "",
1809
- "env_key": "OPENROUTER_API_KEY",
1810
- },
1811
- }
1812
-
1813
- @classmethod
1814
- def default(cls) -> SLMConfig:
1815
- """Create default Mode A configuration."""
1816
- return cls.for_mode(Mode.A)
1817
-
1818
- @classmethod
1819
- def for_mode(
1820
- cls,
1821
- mode: Mode,
1822
- base_dir: Path | None = None,
1823
- *,
1824
- llm_provider: str = "",
1825
- llm_model: str = "",
1826
- llm_api_key: str = "",
1827
- llm_api_base: str = "",
1828
- embedding_provider: str = "",
1829
- embedding_endpoint: str = "",
1830
- embedding_key: str = "",
1831
- embedding_deployment: str = "",
1832
- embedding_model_name: str = "",
1833
- embedding_dimension: int = 0,
1834
- ) -> SLMConfig:
1835
- """Create config with mode-appropriate defaults."""
1836
- # resolve base dir via slm_home() at call time when base_dir is not explicit.
1837
- if base_dir is None:
1838
- try:
1839
- from superlocalmemory.cli._lazy_init import slm_home as _slm_home
1840
- _base = _slm_home()
1841
- except Exception:
1842
- _base = DEFAULT_BASE_DIR
1843
- else:
1844
- _base = base_dir
1845
-
1846
- if mode == Mode.A:
1847
- # V3.4.24: If user chose "openai" provider, honour their custom
1848
- # endpoint/model/dimension. Otherwise use local defaults.
1849
- _a_provider = embedding_provider or "sentence-transformers"
1850
- if _a_provider == "openai" and embedding_endpoint:
1851
- _a_emb = EmbeddingConfig(
1852
- model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
1853
- dimension=embedding_dimension or 768,
1854
- provider="openai",
1855
- api_endpoint=embedding_endpoint,
1856
- api_key=embedding_key,
1857
- )
1858
- else:
1859
- _a_emb = EmbeddingConfig(
1860
- model_name="nomic-ai/nomic-embed-text-v1.5",
1861
- dimension=768,
1862
- provider=_a_provider,
1863
- )
1864
- return cls(
1865
- mode=mode,
1866
- base_dir=_base,
1867
- embedding=_a_emb,
1868
- llm=LLMConfig(), # No LLM
1869
- # Mode A's promise is that nothing puts a model on any path, and
1870
- # a subsystem should refuse because the mode says so, not because
1871
- # it happened to be handed llm=None. Three of the four places
1872
- # that build the consolidator pass only a database, so today the
1873
- # guard holds by accident of wiring; this makes it hold by
1874
- # configuration, which is the thing a reader can check.
1875
- ccq=CCQConfig(use_llm_gist=False),
1876
- temporal_validator=TemporalValidatorConfig(mode="a"),
1877
- retrieval=RetrievalConfig(
1878
- # V3.3.2: ONNX cross-encoder enabled for all modes (~200MB)
1879
- use_cross_encoder=True,
1880
- # V3.3.19: Enable 1 round of rule-based query decomposition.
1881
- # The enhanced _heuristic_expand generates entity+action
1882
- # sub-queries that dramatically improve multi-hop retrieval.
1883
- agentic_max_rounds=1,
1884
- ),
1885
- math=MathConfig(
1886
- sheaf_contradiction_threshold=0.45, # 768d threshold
1887
- ),
1888
- )
1889
-
1890
- if mode == Mode.B:
1891
- # V3.4.24: If user chose "openai" provider with a custom endpoint
1892
- # (e.g. local vLLM, LiteLLM, Ollama /v1), honour it.
1893
- _b_provider = embedding_provider or "ollama"
1894
- if _b_provider == "openai" and embedding_endpoint:
1895
- _b_emb = EmbeddingConfig(
1896
- model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
1897
- dimension=embedding_dimension or 768,
1898
- provider="openai",
1899
- api_endpoint=embedding_endpoint,
1900
- api_key=embedding_key,
1901
- )
1902
- else:
1903
- _b_emb = EmbeddingConfig(
1904
- model_name="nomic-ai/nomic-embed-text-v1.5",
1905
- dimension=768,
1906
- provider=_b_provider,
1907
- )
1908
- return cls(
1909
- mode=mode,
1910
- base_dir=_base,
1911
- embedding=_b_emb,
1912
- llm=LLMConfig(
1913
- provider=llm_provider or "ollama",
1914
- model=llm_model or "llama3.2",
1915
- api_base=llm_api_base or "http://localhost:11434",
1916
- api_key=llm_api_key or "",
1917
- ),
1918
- temporal_validator=TemporalValidatorConfig(mode="b"),
1919
- retrieval=RetrievalConfig(
1920
- # V3.3.2: ONNX cross-encoder enabled for all modes (~200MB)
1921
- use_cross_encoder=True,
1922
- ),
1923
- )
1924
-
1925
- # Mode C — FULL POWER, UNRESTRICTED
1926
- # Don't carry over local-only providers (ollama) to cloud mode
1927
- c_provider = llm_provider if llm_provider not in ("ollama", "") else "openrouter"
1928
- c_model = llm_model if llm_provider not in ("ollama", "") else "anthropic/claude-sonnet-4"
1929
- # V3.4.24: If user chose "openai" provider, honour it in Mode C too.
1930
- _c_emb_provider = embedding_provider or ""
1931
- if _c_emb_provider == "openai" and embedding_endpoint:
1932
- _c_emb = EmbeddingConfig(
1933
- model_name=embedding_model_name or "text-embedding-3-large",
1934
- dimension=embedding_dimension or 3072,
1935
- provider="openai",
1936
- api_endpoint=embedding_endpoint,
1937
- api_key=embedding_key,
1938
- )
1939
- elif embedding_endpoint:
1940
- _c_emb = EmbeddingConfig(
1941
- model_name=embedding_model_name or "text-embedding-3-large",
1942
- dimension=embedding_dimension or 3072,
1943
- provider=_c_emb_provider,
1944
- api_endpoint=embedding_endpoint,
1945
- api_key=embedding_key,
1946
- deployment_name=embedding_deployment,
1947
- )
1948
- else:
1949
- # Mode C upgrades LLM capability; it must not silently require a
1950
- # second paid embedding provider. A 3072-dim cloud embedding
1951
- # contract without an endpoint/key was auto-routed to the local
1952
- # 768-dim Ollama embedder, which made ingestion fail at vector
1953
- # materialization. Cloud embeddings remain an explicit opt-in.
1954
- _c_emb = EmbeddingConfig(
1955
- # Honour an on-disk embedding model when one was configured (the
1956
- # load() path passes it through); default to the local nomic
1957
- # model so Mode C never silently requires a paid cloud embedder.
1958
- model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
1959
- dimension=embedding_dimension or 768,
1960
- )
1961
- return cls(
1962
- mode=mode,
1963
- base_dir=_base,
1964
- embedding=_c_emb,
1965
- llm=LLMConfig(
1966
- provider=c_provider,
1967
- model=c_model,
1968
- api_key=llm_api_key,
1969
- api_base=llm_api_base,
1970
- ),
1971
- temporal_validator=TemporalValidatorConfig(mode="c"),
1972
- channel_weights=ChannelWeights(
1973
- semantic=1.5,
1974
- bm25=1.2,
1975
- entity_graph=1.3,
1976
- temporal=1.0,
1977
- spreading_activation=1.2, # Phase 3: SA boost in Mode C
1978
- hopfield=1.0, # Phase G: Hopfield in Mode C
1979
- ),
1980
- retrieval=RetrievalConfig(
1981
- use_cross_encoder=True,
1982
- semantic_top_k=80,
1983
- agentic_max_rounds=2, # EverMemOS 2-round
1984
- ),
1985
- math=MathConfig(
1986
- sheaf_contradiction_threshold=0.65, # Higher for 3072d embeddings
1987
- ),
1988
- )
1989
-
1990
- # ------------------------------------------------------------------
1991
- # 3-Mode config system (v3.4.54)
1992
- # ------------------------------------------------------------------
1993
-
1994
- @staticmethod
1995
- def _mode_config_path(base_dir: Path, mode: "Mode") -> Path:
1996
- """Return the per-mode config file path."""
1997
- from superlocalmemory.storage.models import Mode as _M
1998
- _names = {
1999
- _M.A: "mode_a.json",
2000
- _M.B: "mode_b.json",
2001
- _M.C: "mode_c.json",
2002
- }
2003
- return base_dir / _names.get(mode, "mode_a.json")
2004
-
2005
- @staticmethod
2006
- def read_current_mode(base_dir: Path | None = None) -> str:
2007
- """Read the current mode from the ``current_mode`` file.
2008
-
2009
- Returns ``\"b\"`` (the default) if the file doesn't exist.
2010
- """
2011
- # resolve via slm_home() at call time when base_dir is not explicit.
2012
- if base_dir is None:
2013
- try:
2014
- from superlocalmemory.cli._lazy_init import slm_home as _slm_home
2015
- _base = _slm_home()
2016
- except Exception:
2017
- _base = DEFAULT_BASE_DIR
2018
- else:
2019
- _base = base_dir
2020
- _f = _base / CURRENT_MODE_FILE
2021
- try:
2022
- return _f.read_text(encoding="utf-8").strip().lower() or "b"
2023
- except (OSError, FileNotFoundError):
2024
- return "b"
2025
-
2026
- @staticmethod
2027
- def write_current_mode(mode: str, base_dir: Path | None = None) -> None:
2028
- """Write the current mode letter to ``current_mode``."""
2029
- # resolve via slm_home() at call time when base_dir is not explicit.
2030
- if base_dir is None:
2031
- try:
2032
- from superlocalmemory.cli._lazy_init import slm_home as _slm_home
2033
- _base = _slm_home()
2034
- except Exception:
2035
- _base = DEFAULT_BASE_DIR
2036
- else:
2037
- _base = base_dir
2038
- _base.mkdir(parents=True, exist_ok=True)
2039
- (_base / CURRENT_MODE_FILE).write_text(
2040
- mode.lower().strip(), encoding="utf-8",
2041
- )
2042
-
2043
- @classmethod
2044
- def switch_mode(
2045
- cls,
2046
- new_mode: str,
2047
- base_dir: Path | None = None,
2048
- ) -> "SLMConfig":
2049
- """Switch to a different mode, preserving ALL non-mode settings.
2050
-
2051
- v3.4.58 fix: Only ``config.mode`` changes. The previous implementation
2052
- called ``for_mode()`` (which resets embedding/LLM/retrieval to mode
2053
- defaults) whenever the target mode's per-mode file didn't exist.
2054
- This silently clobbered custom embeddings, endpoints, and LLM config.
2055
-
2056
- Correct behavior:
2057
- - Load the current (old) config from config.json.
2058
- - Save it to mode_{old}.json for the 3-mode system.
2059
- - If mode_{new}.json exists, load it (user had a prior config for that mode).
2060
- - If mode_{new}.json does NOT exist: start from the OLD config, change
2061
- only the mode field. This preserves embedding, retrieval, forgetting, etc.
2062
- - Exception: if user has NO LLM provider AND is switching to B/C, populate
2063
- sensible LLM defaults so the daemon doesn't start dead.
2064
- - Write the result to config.json (backward compat) and current_mode.
2065
-
2066
- Returns the new active config.
2067
- """
2068
- import copy
2069
- import dataclasses
2070
-
2071
- from superlocalmemory.storage.models import Mode as _M
2072
- # resolve via slm_home() at call time when base_dir is not explicit.
2073
- if base_dir is None:
2074
- try:
2075
- from superlocalmemory.cli._lazy_init import slm_home as _slm_home
2076
- _base = _slm_home()
2077
- except Exception:
2078
- _base = DEFAULT_BASE_DIR
2079
- else:
2080
- _base = base_dir
2081
- _base.mkdir(parents=True, exist_ok=True)
2082
-
2083
- old_config = cls.load(_base / "config.json")
2084
- old_mode = old_config.mode.value.lower()
2085
- new_mode_val = _M(new_mode.lower())
2086
-
2087
- # 1. Save current config to its per-mode file (preserve customizations)
2088
- if old_mode != new_mode.lower():
2089
- old_path = cls._mode_config_path(_base, old_config.mode)
2090
- old_config.save(old_path)
2091
-
2092
- # 2. Determine new config:
2093
- # a) mode_{new}.json exists → user had prior config for this mode, use it
2094
- # b) otherwise → copy old config, change only mode
2095
- new_path = cls._mode_config_path(_base, new_mode_val)
2096
- if new_path.exists():
2097
- try:
2098
- new_config = cls.load(new_path)
2099
- if new_config.mode != new_mode_val:
2100
- # Corrupt/mismatched mode file — rebuild preserving old values
2101
- new_config = copy.copy(old_config)
2102
- new_config.mode = new_mode_val
2103
- except Exception:
2104
- new_config = copy.copy(old_config)
2105
- new_config.mode = new_mode_val
2106
- else:
2107
- # First time switching to this mode: start from old config, change mode only.
2108
- # Use dataclasses.replace() to produce a new frozen-compatible object.
2109
- new_config = dataclasses.replace(old_config, mode=new_mode_val)
2110
-
2111
- # 3. LLM default population — ONLY if user has no provider AND switching to B/C.
2112
- # Never overwrites an existing provider.
2113
- if new_mode_val in (_M.B, _M.C) and not new_config.llm.provider:
2114
- if new_mode_val == _M.B:
2115
- new_config = dataclasses.replace(
2116
- new_config,
2117
- llm=LLMConfig(
2118
- provider="ollama",
2119
- model="llama3.2",
2120
- api_base="http://localhost:11434",
2121
- ),
2122
- )
2123
- else: # Mode C
2124
- new_config = dataclasses.replace(
2125
- new_config,
2126
- llm=LLMConfig(
2127
- provider="openrouter",
2128
- model="anthropic/claude-sonnet-4",
2129
- ),
2130
- )
2131
-
2132
- # 4. Save as active config.json (backward compat)
2133
- new_config.save(_base / "config.json", mode_change=True)
2134
-
2135
- # 5. Write current_mode
2136
- cls.write_current_mode(new_mode, _base)
2137
-
2138
- return new_config
2139
-
2140
- @classmethod
2141
- def migrate_to_3mode(cls, base_dir: Path | None = None) -> bool:
2142
- """One-time migration: config.json → 3-mode system.
2143
-
2144
- Called on daemon boot. Idempotent — if ``current_mode`` already
2145
- exists, this is a no-op. Returns True if migration was performed.
2146
- """
2147
- # resolve via slm_home() at call time when base_dir is not explicit.
2148
- if base_dir is None:
2149
- try:
2150
- from superlocalmemory.cli._lazy_init import slm_home as _slm_home
2151
- _base = _slm_home()
2152
- except Exception:
2153
- _base = DEFAULT_BASE_DIR
2154
- else:
2155
- _base = base_dir
2156
- _current = _base / CURRENT_MODE_FILE
2157
- if _current.exists():
2158
- return False # already migrated
2159
-
2160
- legacy = _base / "config.json"
2161
- if not legacy.exists():
2162
- # No config at all — write defaults and current_mode
2163
- from superlocalmemory.storage.models import Mode as _M
2164
- _def = cls.for_mode(_M.B, base_dir=_base)
2165
- _def.save(legacy)
2166
- cls.write_current_mode("b", _base)
2167
- for _m in (_M.A, _M.B, _M.C):
2168
- _mp = cls._mode_config_path(_base, _m)
2169
- if not _mp.exists():
2170
- try:
2171
- _mc = cls.for_mode(_m, base_dir=_base)
2172
- _mc.save(_mp)
2173
- except Exception:
2174
- pass
2175
- return True
2176
-
2177
- # Migrate existing config.json → mode_{current}.json
2178
- try:
2179
- config = cls.load(legacy)
2180
- current = config.mode.value.lower()
2181
- cls.write_current_mode(current, _base)
2182
-
2183
- # Save current config to its mode file
2184
- mode_path = cls._mode_config_path(_base, config.mode)
2185
- config.save(mode_path)
2186
-
2187
- # Generate other mode files from defaults
2188
- from superlocalmemory.storage.models import Mode as _M
2189
- for _m in (_M.A, _M.B, _M.C):
2190
- _mp = cls._mode_config_path(_base, _m)
2191
- if not _mp.exists():
2192
- try:
2193
- _mc = cls.for_mode(_m, base_dir=_base)
2194
- _mc.save(_mp)
2195
- except Exception:
2196
- pass
2197
-
2198
- logger.info(
2199
- "3-mode config system migrated: config.json → mode_%s.json. "
2200
- "All three mode configs generated.", current,
2201
- )
2202
- return True
2203
- except Exception:
2204
- return False