superlocalmemory 3.7.8 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +129 -0
  4. package/README.md +205 -123
  5. package/package.json +12 -3
  6. package/plugin/.claude-plugin/plugin.json +2 -3
  7. package/plugin/CLAUDE.md +8 -8
  8. package/plugin/agents/slm-governance-advisor.md +80 -0
  9. package/plugin/agents/slm-loop-runner.md +71 -0
  10. package/plugin/agents/slm-memory-advisor.md +10 -5
  11. package/plugin/agents/slm-optimize-advisor.md +9 -3
  12. package/plugin/commands/slm-loop.md +31 -0
  13. package/plugin/hooks/hooks.json +79 -0
  14. package/plugin/requirements.txt +1 -1
  15. package/plugin/scripts/slm-launch +46 -7
  16. package/plugin/settings.json +9 -0
  17. package/plugin/skills/slm-cache/SKILL.md +9 -1
  18. package/plugin/skills/slm-compress/SKILL.md +8 -1
  19. package/plugin/skills/slm-governance/SKILL.md +248 -0
  20. package/plugin/skills/slm-graph/SKILL.md +17 -3
  21. package/plugin/skills/slm-loop/SKILL.md +99 -0
  22. package/plugin/skills/slm-mesh/SKILL.md +282 -0
  23. package/plugin/skills/slm-profile/SKILL.md +148 -0
  24. package/plugin/skills/slm-recall/SKILL.md +46 -10
  25. package/plugin/skills/slm-remember/SKILL.md +48 -1
  26. package/plugin/skills/slm-scope/SKILL.md +176 -0
  27. package/plugin/skills/slm-session/SKILL.md +24 -1
  28. package/plugin/skills/slm-status/SKILL.md +18 -1
  29. package/plugin-src/rules/AGENTS.md +57 -18
  30. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  31. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  32. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  33. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  34. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  35. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  36. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  37. package/pyproject.toml +2 -1
  38. package/scripts/postinstall/validation.js +2 -0
  39. package/scripts/postinstall-interactive.js +74 -2
  40. package/src/superlocalmemory/__init__.py +1 -1
  41. package/src/superlocalmemory/access/__init__.py +3 -0
  42. package/src/superlocalmemory/access/rbac.py +477 -0
  43. package/src/superlocalmemory/cli/commands.py +228 -17
  44. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  45. package/src/superlocalmemory/cli/daemon.py +7 -0
  46. package/src/superlocalmemory/cli/loop_cmd.py +187 -0
  47. package/src/superlocalmemory/cli/main.py +49 -8
  48. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  49. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  50. package/src/superlocalmemory/cli/pending_store.py +49 -13
  51. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  52. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  53. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  54. package/src/superlocalmemory/cli/version_banner.py +17 -3
  55. package/src/superlocalmemory/compliance/audit.py +6 -0
  56. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  57. package/src/superlocalmemory/compliance/retention.py +176 -45
  58. package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
  59. package/src/superlocalmemory/core/community_summary.py +267 -0
  60. package/src/superlocalmemory/core/config.py +216 -3
  61. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  62. package/src/superlocalmemory/core/context_cache.py +61 -18
  63. package/src/superlocalmemory/core/embedding_worker.py +21 -7
  64. package/src/superlocalmemory/core/embeddings.py +131 -46
  65. package/src/superlocalmemory/core/engine.py +41 -22
  66. package/src/superlocalmemory/core/engine_ingestion.py +359 -43
  67. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  68. package/src/superlocalmemory/core/entity_community.py +178 -0
  69. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  70. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  71. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  72. package/src/superlocalmemory/core/injection.py +12 -7
  73. package/src/superlocalmemory/core/key_expander.py +138 -0
  74. package/src/superlocalmemory/core/maintenance.py +23 -0
  75. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  76. package/src/superlocalmemory/core/modes.py +1 -1
  77. package/src/superlocalmemory/core/mutations.py +2 -2
  78. package/src/superlocalmemory/core/pii.py +105 -0
  79. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  80. package/src/superlocalmemory/core/recall_pipeline.py +7 -3
  81. package/src/superlocalmemory/core/recall_worker.py +20 -6
  82. package/src/superlocalmemory/core/scale_engine.py +60 -1
  83. package/src/superlocalmemory/core/security_primitives.py +40 -2
  84. package/src/superlocalmemory/core/store_pipeline.py +186 -29
  85. package/src/superlocalmemory/core/worker_pool.py +21 -6
  86. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  87. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  88. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  89. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  90. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  91. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  92. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  93. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  94. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  95. package/src/superlocalmemory/evolution/triggers.py +22 -13
  96. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  97. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  98. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  99. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  100. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  101. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  102. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  103. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  104. package/src/superlocalmemory/hooks/portable_kit.py +148 -3
  105. package/src/superlocalmemory/infra/backup.py +12 -1
  106. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  107. package/src/superlocalmemory/infra/data_root.py +43 -4
  108. package/src/superlocalmemory/infra/event_bus.py +107 -24
  109. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  110. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  111. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  112. package/src/superlocalmemory/learning/cross_project.py +28 -19
  113. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  114. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  115. package/src/superlocalmemory/learning/reward.py +50 -0
  116. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  117. package/src/superlocalmemory/learning/source_quality.py +523 -1
  118. package/src/superlocalmemory/loops/__init__.py +56 -0
  119. package/src/superlocalmemory/loops/budget.py +58 -0
  120. package/src/superlocalmemory/loops/engine.py +164 -0
  121. package/src/superlocalmemory/loops/ledger.py +263 -0
  122. package/src/superlocalmemory/loops/models.py +152 -0
  123. package/src/superlocalmemory/loops/rules.py +52 -0
  124. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  125. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  126. package/src/superlocalmemory/mcp/profiles.py +103 -0
  127. package/src/superlocalmemory/mcp/server.py +32 -79
  128. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  129. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  130. package/src/superlocalmemory/mcp/tools_core.py +12 -4
  131. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  132. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  133. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  134. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  135. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  136. package/src/superlocalmemory/mesh/broker.py +237 -129
  137. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  138. package/src/superlocalmemory/optimize/NOTICE +1 -6
  139. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  140. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  141. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  142. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  143. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  144. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  145. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  146. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  147. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  148. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  149. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  150. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  151. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  152. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  153. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  154. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  155. package/src/superlocalmemory/retrieval/engine.py +221 -47
  156. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  157. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  158. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  159. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  160. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  161. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  162. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  163. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  164. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  165. package/src/superlocalmemory/server/api.py +4 -4
  166. package/src/superlocalmemory/server/config_file.py +90 -0
  167. package/src/superlocalmemory/server/origin.py +50 -0
  168. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  169. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  170. package/src/superlocalmemory/server/recall_health.py +24 -3
  171. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  172. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  173. package/src/superlocalmemory/server/routes/agents.py +128 -38
  174. package/src/superlocalmemory/server/routes/backup.py +317 -70
  175. package/src/superlocalmemory/server/routes/behavioral.py +349 -71
  176. package/src/superlocalmemory/server/routes/brain.py +69 -12
  177. package/src/superlocalmemory/server/routes/chat.py +10 -5
  178. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  179. package/src/superlocalmemory/server/routes/config_api.py +438 -0
  180. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  181. package/src/superlocalmemory/server/routes/entity.py +108 -26
  182. package/src/superlocalmemory/server/routes/events.py +24 -8
  183. package/src/superlocalmemory/server/routes/evolution.py +189 -68
  184. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  185. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  186. package/src/superlocalmemory/server/routes/insights.py +3 -3
  187. package/src/superlocalmemory/server/routes/learning.py +289 -118
  188. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  189. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  190. package/src/superlocalmemory/server/routes/memories.py +182 -57
  191. package/src/superlocalmemory/server/routes/mesh.py +200 -31
  192. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  193. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  194. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  195. package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
  196. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  197. package/src/superlocalmemory/server/routes/stats.py +103 -158
  198. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  199. package/src/superlocalmemory/server/routes/token.py +3 -13
  200. package/src/superlocalmemory/server/routes/v3_api.py +247 -89
  201. package/src/superlocalmemory/server/routes/ws.py +5 -2
  202. package/src/superlocalmemory/server/security_middleware.py +12 -5
  203. package/src/superlocalmemory/server/ui.py +20 -5
  204. package/src/superlocalmemory/server/unified_daemon.py +827 -72
  205. package/src/superlocalmemory/server/write_identity.py +38 -8
  206. package/src/superlocalmemory/storage/database.py +265 -53
  207. package/src/superlocalmemory/storage/migration_runner.py +132 -1
  208. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  209. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  210. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  211. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  212. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  213. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  214. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  215. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  216. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  217. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  218. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  219. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  220. package/src/superlocalmemory/storage/models.py +4 -0
  221. package/src/superlocalmemory/storage/schema.py +136 -1
  222. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  223. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  224. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  225. package/src/superlocalmemory/trust/gate.py +49 -8
  226. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  227. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  228. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  229. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  230. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  231. package/src/superlocalmemory/ui/favicon.svg +35 -4
  232. package/src/superlocalmemory/ui/index.html +303 -173
  233. package/src/superlocalmemory/ui/js/brain.js +5 -20
  234. package/src/superlocalmemory/ui/js/core.js +100 -41
  235. package/src/superlocalmemory/ui/js/dashboard.js +403 -65
  236. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  237. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  238. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  239. package/src/superlocalmemory/ui/js/memories.js +15 -4
  240. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  241. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  242. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  243. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  244. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  245. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  246. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  247. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  248. package/src/superlocalmemory/ui/js/od-backup.js +871 -0
  249. package/src/superlocalmemory/ui/js/od-brain.js +816 -0
  250. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  251. package/src/superlocalmemory/ui/js/od-graph.js +600 -0
  252. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  253. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  254. package/src/superlocalmemory/ui/js/od-memories.js +929 -0
  255. package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
  256. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  257. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  258. package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
  259. package/src/superlocalmemory/ui/js/od-shell.js +809 -0
  260. package/src/superlocalmemory/ui/js/od-skills.js +600 -0
  261. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  262. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  263. package/src/superlocalmemory/ui/js/settings.js +17 -3
  264. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  265. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  266. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  267. package/plugin-src/.mcp.json +0 -12
  268. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  269. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  270. package/plugin-src/hooks/.gitkeep +0 -0
  271. package/plugin-src/hooks/hooks.json +0 -23
  272. package/plugin-src/manifest.json +0 -25
  273. package/plugin-src/requirements.txt +0 -1
  274. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  275. package/plugin-src/scripts/ensure-venv.bat +0 -122
  276. package/plugin-src/scripts/ensure-venv.sh +0 -105
  277. package/plugin-src/scripts/slm-launch +0 -23
  278. package/plugin-src/scripts/slm-launch.bat +0 -23
  279. package/plugin-src/settings.json +0 -16
  280. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -17,8 +17,6 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
17
17
  from __future__ import annotations
18
18
 
19
19
  import logging
20
- import sqlite3
21
- import threading
22
20
  from pathlib import Path
23
21
  from typing import TYPE_CHECKING, Any
24
22
 
@@ -54,7 +52,7 @@ class BackendOrchestrator:
54
52
  """Central coordinator for multi-backend architecture.
55
53
 
56
54
  Lifecycle:
57
- on_daemon_start() → migrate backends → ready
55
+ on_daemon_start() → initialize bounded backend state → ready
58
56
  sync_new_fact() → called from store_pipeline after SQLite write
59
57
  health_check() → returns status of all backends
60
58
  """
@@ -73,7 +71,7 @@ class BackendOrchestrator:
73
71
  # ------------------------------------------------------------------
74
72
 
75
73
  def on_daemon_start(self) -> None:
76
- """Called once on daemon startup. Order matters (F-11: rebalance before migration)."""
74
+ """Initialize bounded backend state without delaying daemon readiness."""
77
75
  logger.info("BackendOrchestrator: daemon starting")
78
76
 
79
77
  # 1. Apply schema (if not already applied)
@@ -84,18 +82,13 @@ class BackendOrchestrator:
84
82
  try:
85
83
  from superlocalmemory.core.tier_manager import evaluate_tiers
86
84
  self._tiers = evaluate_tiers
87
- logger.info("BackendOrchestrator: TierManager initialized")
85
+ logger.info("BackendOrchestrator: tier evaluator registered")
88
86
  except Exception as exc:
89
87
  logger.warning("TierManager init failed (non-fatal): %s", exc)
90
88
 
91
- # 3. Run initial tier rebalance FIRST (F-11: before migration)
92
- try:
93
- from superlocalmemory.core.tier_manager import evaluate_tiers as rebalance
94
- result = rebalance(self._db)
95
- logger.info("BackendOrchestrator: initial rebalance — %s",
96
- result.get("total_evaluated", "?"))
97
- except Exception as exc:
98
- logger.warning("Initial rebalance failed (non-fatal): %s", exc)
89
+ # Full-database tier evaluation belongs to MaintenanceScheduler. Running
90
+ # it here blocks FastAPI lifespan readiness on mature upgrade databases
91
+ # and occurs before optional projection backends are fully registered.
99
92
 
100
93
  self._recover_interrupted_scale_promotion()
101
94
 
@@ -109,12 +102,12 @@ class BackendOrchestrator:
109
102
  )
110
103
  return
111
104
 
112
- # 4. Initialize CozoDB if available
105
+ # 3. Initialize CozoDB if available
113
106
  cozo_available = self._detect_cozo()
114
107
  if cozo_available:
115
108
  self._init_cozo()
116
109
 
117
- # 5. Initialize LanceDB if available
110
+ # 4. Initialize LanceDB if available
118
111
  lancedb_available = self._detect_lancedb()
119
112
  if lancedb_available:
120
113
  self._init_lancedb()
@@ -141,13 +134,19 @@ class BackendOrchestrator:
141
134
  try:
142
135
  from superlocalmemory.core.scale_engine import ScaleEngineManager
143
136
 
144
- result = ScaleEngineManager(self._config, profile_id="default").recover_interrupted_promotion()
137
+ result = ScaleEngineManager(
138
+ self._config,
139
+ profile_id="default",
140
+ ).recover_interrupted_promotion()
145
141
  if result:
146
142
  logger.warning("Scale Engine promotion recovery: %s", result)
147
143
  except Exception as exc:
148
144
  # A scale projection is derived data. Startup must keep serving
149
145
  # canonical SQLite even if optional recovery itself is unhealthy.
150
- logger.error("Scale Engine recovery requires repair; Local Core remains active: %s", exc)
146
+ logger.error(
147
+ "Scale Engine recovery requires repair; Local Core remains active: %s",
148
+ exc,
149
+ )
151
150
 
152
151
  # ------------------------------------------------------------------
153
152
  # Incremental Sync (F-04: called from store_pipeline)
@@ -366,47 +365,11 @@ class BackendOrchestrator:
366
365
  logger.warning("LanceDB init failed: %s", exc)
367
366
  self._lancedb = None
368
367
 
369
- # ------------------------------------------------------------------
370
- # Internal: Migration
371
- # ------------------------------------------------------------------
372
-
373
- def _migrate_cozo(self) -> None:
374
- self._update_status("cozo", "migrating")
375
-
376
- def _run():
377
- conn = sqlite3.connect(str(self._data_dir / "memory.db"))
378
- conn.execute("PRAGMA journal_mode=WAL")
379
- conn.execute("PRAGMA query_only=ON") # F-07: read-only in migration thread
380
- try:
381
- count = self._cozo.bulk_import_from_sqlite(conn)
382
- self._update_status("cozo", "active", count)
383
- logger.info("CozoDB migration complete: %d edges", count)
384
- except Exception as exc:
385
- logger.error("CozoDB migration failed: %s", exc)
386
- self._update_status("cozo", "failed", error=str(exc))
387
- finally:
388
- conn.close()
389
-
390
- threading.Thread(target=_run, daemon=True).start()
391
-
392
- def _migrate_lancedb(self) -> None:
393
- self._update_status("lancedb", "migrating")
394
-
395
- def _run():
396
- conn = sqlite3.connect(str(self._data_dir / "memory.db"))
397
- conn.execute("PRAGMA journal_mode=WAL")
398
- conn.execute("PRAGMA query_only=ON")
399
- try:
400
- count = self._lancedb.bulk_import_from_sqlite(conn)
401
- self._update_status("lancedb", "active", count)
402
- logger.info("LanceDB migration complete: %d vectors", count)
403
- except Exception as exc:
404
- logger.error("LanceDB migration failed: %s", exc)
405
- self._update_status("lancedb", "failed", error=str(exc))
406
- finally:
407
- conn.close()
408
-
409
- threading.Thread(target=_run, daemon=True).start()
368
+ # v3.7.9 (scale MEDIUM-2): _migrate_cozo/_migrate_lancedb were dead code —
369
+ # never called anywhere — and bypassed the staged prepare→verify→promote
370
+ # safety envelope (no fingerprint, no parity, no backup). Removed so a future
371
+ # caller cannot re-import outside the lifecycle. Emergency re-imports must go
372
+ # through the Scale Engine lifecycle.
410
373
 
411
374
  # ------------------------------------------------------------------
412
375
  # Internal: Status
@@ -439,7 +402,8 @@ class BackendOrchestrator:
439
402
  def _apply_schema_v345(self) -> None:
440
403
  try:
441
404
  from superlocalmemory.storage.schema_v345 import (
442
- apply_migration, schema_version_applied,
405
+ apply_migration,
406
+ schema_version_applied,
443
407
  )
444
408
  # #47 fix: use raw_connection() — DatabaseManager has no `.conn`,
445
409
  # so the old code raised AttributeError that was silently swallowed,
@@ -0,0 +1,267 @@
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
+ """Community summaries (Wave Q2) — one synthesized report per entity community.
6
+
7
+ Rides on the entity-community backbone (core.entity_community). For each
8
+ community it gathers the member entities' facts, EXCLUDES superseded facts
9
+ (bi-temporal — market CRIT-3), then produces:
10
+
11
+ - a keyword-dense signal (always, Mode A, zero-LLM), and
12
+ - a summary: Mode B/C LLM synthesis via the shared core.Summarizer
13
+ (which itself falls back to a heuristic), else the Mode A keyword-dense
14
+ line. Fail-open — a summarizer error never breaks generation.
15
+
16
+ Surfacing is on-device-safe: summaries are PRECOMPUTED here in the background
17
+ and later matched to a query as a single thematic-context block (Q2b) — never
18
+ a GraphRAG-style per-query LLM fan-out (market CRIT-1). member_fact_ids gives
19
+ drill-down back to the source atoms (Q3).
20
+
21
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
22
+ License: AGPL-3.0-or-later
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import logging
29
+ from collections import Counter, defaultdict
30
+ from typing import Any
31
+
32
+ from superlocalmemory.core.entity_community import EntityCommunityBuilder
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ _STOPWORDS = frozenset({
37
+ "the", "a", "an", "is", "was", "were", "are", "be", "been", "being",
38
+ "have", "has", "had", "do", "does", "did", "will", "would", "could",
39
+ "should", "may", "might", "shall", "can", "to", "of", "in", "for", "on",
40
+ "with", "at", "by", "from", "as", "into", "through", "and", "but", "or",
41
+ "not", "no", "this", "that", "these", "those", "it", "its", "they",
42
+ "them", "their", "he", "she", "his", "her", "we", "our", "you", "your",
43
+ "i", "my", "me", "his", "was", "who", "what", "when", "where", "how",
44
+ })
45
+
46
+
47
+ class CommunitySummaryBuilder:
48
+ """Generate + persist one summary per entity community (background)."""
49
+
50
+ def __init__(
51
+ self,
52
+ db: Any,
53
+ summarizer: Any = None,
54
+ max_communities: int = 50,
55
+ min_facts: int = 2,
56
+ max_facts_per_community: int = 30,
57
+ max_keywords: int = 8,
58
+ summary_max_chars: int = 512,
59
+ ) -> None:
60
+ self._db = db
61
+ self._summarizer = summarizer
62
+ self._max_communities = max(1, int(max_communities))
63
+ self._min_facts = max(1, int(min_facts))
64
+ self._max_facts = max(1, int(max_facts_per_community))
65
+ self._max_keywords = max(1, int(max_keywords))
66
+ self._summary_max_chars = max(64, int(summary_max_chars))
67
+
68
+ # ------------------------------------------------------------------
69
+ # Generation
70
+ # ------------------------------------------------------------------
71
+
72
+ def compute_and_store(self, profile_id: str) -> dict[str, int]:
73
+ communities = EntityCommunityBuilder(self._db).get_communities(profile_id)
74
+ try:
75
+ self._db.execute(
76
+ "DELETE FROM community_summaries WHERE profile_id = ?",
77
+ (profile_id,),
78
+ )
79
+ except Exception as exc:
80
+ logger.debug("community_summaries clear failed: %s", exc)
81
+ if not communities:
82
+ return {"summaries_written": 0, "communities": 0}
83
+
84
+ entity_to_cid: dict[str, int] = {
85
+ e: cid for cid, ents in communities.items() for e in ents
86
+ }
87
+ cid_facts = self._gather_facts(profile_id, entity_to_cid)
88
+ name_map = self._entity_names(profile_id, communities)
89
+
90
+ written = 0
91
+ ordered = sorted(
92
+ cid_facts.items(), key=lambda kv: len(kv[1]), reverse=True,
93
+ )
94
+ for cid, facts in ordered:
95
+ if written >= self._max_communities:
96
+ break
97
+ seen: set[str] = set()
98
+ vf: list[tuple[str, str]] = []
99
+ for fid, content in facts:
100
+ if fid in seen:
101
+ continue
102
+ seen.add(fid)
103
+ vf.append((fid, content))
104
+ if len(vf) < self._min_facts:
105
+ continue
106
+
107
+ fact_ids = [fid for fid, _ in vf]
108
+ contents = [c for _, c in vf][: self._max_facts]
109
+ entity_ids = list(communities.get(cid, []))
110
+ entity_names = [name_map.get(e, e) for e in entity_ids]
111
+ keywords = self._keywords(contents)
112
+ summary = self._summary(contents, entity_names, keywords)
113
+
114
+ try:
115
+ self._db.execute(
116
+ "INSERT OR REPLACE INTO community_summaries "
117
+ "(profile_id, community_id, summary, keywords, "
118
+ " entity_ids_json, fact_ids_json, fact_count, computed_at) "
119
+ "VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))",
120
+ (
121
+ profile_id, cid, summary, keywords,
122
+ json.dumps(entity_ids), json.dumps(fact_ids),
123
+ len(fact_ids),
124
+ ),
125
+ )
126
+ written += 1
127
+ except Exception as exc:
128
+ logger.debug("community_summaries write failed (%s): %s", cid, exc)
129
+
130
+ return {"summaries_written": written, "communities": len(communities)}
131
+
132
+ # ------------------------------------------------------------------
133
+ # Read API
134
+ # ------------------------------------------------------------------
135
+
136
+ def get_summaries(self, profile_id: str) -> list[dict]:
137
+ try:
138
+ rows = self._db.execute(
139
+ "SELECT * FROM community_summaries WHERE profile_id = ? "
140
+ "ORDER BY fact_count DESC",
141
+ (profile_id,),
142
+ )
143
+ except Exception as exc:
144
+ logger.debug("get_summaries failed: %s", exc)
145
+ return []
146
+ return [dict(r) for r in rows]
147
+
148
+ def get_summary(self, profile_id: str, community_id: int) -> dict | None:
149
+ try:
150
+ rows = self._db.execute(
151
+ "SELECT * FROM community_summaries "
152
+ "WHERE profile_id = ? AND community_id = ?",
153
+ (profile_id, int(community_id)),
154
+ )
155
+ except Exception as exc:
156
+ logger.debug("get_summary failed: %s", exc)
157
+ return None
158
+ return dict(rows[0]) if rows else None
159
+
160
+ # ------------------------------------------------------------------
161
+ # Internal
162
+ # ------------------------------------------------------------------
163
+
164
+ def _gather_facts(
165
+ self, profile_id: str, entity_to_cid: dict[str, int],
166
+ ) -> dict[int, list[tuple[str, str]]]:
167
+ """One scan → {community_id -> [(fact_id, content)]}, superseded dropped."""
168
+ try:
169
+ rows = self._db.execute(
170
+ "SELECT fact_id, canonical_entities_json, content "
171
+ "FROM atomic_facts WHERE profile_id = ?",
172
+ (profile_id,),
173
+ )
174
+ except Exception as exc:
175
+ logger.debug("community fact scan failed: %s", exc)
176
+ return {}
177
+
178
+ cid_facts: dict[int, list[tuple[str, str]]] = defaultdict(list)
179
+ for row in rows:
180
+ d = dict(row)
181
+ raw = d.get("canonical_entities_json")
182
+ if not raw:
183
+ continue
184
+ try:
185
+ ents = json.loads(raw)
186
+ except (ValueError, TypeError):
187
+ continue
188
+ if not isinstance(ents, list):
189
+ continue
190
+ cids = {
191
+ entity_to_cid[str(e).strip()]
192
+ for e in ents
193
+ if str(e).strip() in entity_to_cid
194
+ }
195
+ if not cids:
196
+ continue
197
+ fid = str(d["fact_id"])
198
+ content = d.get("content") or ""
199
+ for cid in cids:
200
+ cid_facts[cid].append((fid, content))
201
+
202
+ all_fids = list({fid for lst in cid_facts.values() for fid, _ in lst})
203
+ invalid: set[str] = set()
204
+ if all_fids:
205
+ try:
206
+ invalid = self._db.get_invalidated_fact_ids(all_fids, profile_id)
207
+ except Exception as exc:
208
+ logger.debug("invalidated-fact lookup failed: %s", exc)
209
+ if invalid:
210
+ cid_facts = {
211
+ cid: [(fid, c) for fid, c in lst if fid not in invalid]
212
+ for cid, lst in cid_facts.items()
213
+ }
214
+ return cid_facts
215
+
216
+ def _entity_names(
217
+ self, profile_id: str, communities: dict[int, list[str]],
218
+ ) -> dict[str, str]:
219
+ all_eids = list({e for ents in communities.values() for e in ents})
220
+ name_map: dict[str, str] = {}
221
+ chunk = 900
222
+ for start in range(0, len(all_eids), chunk):
223
+ batch = all_eids[start:start + chunk]
224
+ ph = ",".join("?" for _ in batch)
225
+ try:
226
+ rows = self._db.execute(
227
+ "SELECT entity_id, canonical_name FROM canonical_entities "
228
+ f"WHERE profile_id = ? AND entity_id IN ({ph})",
229
+ (profile_id, *batch),
230
+ )
231
+ except Exception as exc:
232
+ logger.debug("entity-name lookup failed: %s", exc)
233
+ continue
234
+ for r in rows:
235
+ d = dict(r)
236
+ name_map[str(d["entity_id"])] = str(d.get("canonical_name") or "")
237
+ return name_map
238
+
239
+ def _keywords(self, contents: list[str]) -> str:
240
+ tokens: list[str] = []
241
+ for text in contents:
242
+ for word in text.lower().split():
243
+ w = word.strip(".,;:!?\"'()[]{}")
244
+ if len(w) > 2 and w not in _STOPWORDS:
245
+ tokens.append(w)
246
+ top = [w for w, _ in Counter(tokens).most_common(self._max_keywords)]
247
+ return ", ".join(top)
248
+
249
+ def _summary(
250
+ self, contents: list[str], entity_names: list[str], keywords: str,
251
+ ) -> str:
252
+ if self._summarizer is not None:
253
+ try:
254
+ text = self._summarizer.summarize_cluster(
255
+ [{"content": c} for c in contents],
256
+ )
257
+ if text and text.strip():
258
+ return text.strip()[: self._summary_max_chars]
259
+ except Exception as exc:
260
+ logger.debug("community summarizer failed (fail-open): %s", exc)
261
+ # Mode A keyword-dense fallback.
262
+ names = [n for n in entity_names if n]
263
+ topic = ", ".join(names[:6]) if names else "related memories"
264
+ base = f"Topics: {topic}."
265
+ if keywords:
266
+ base += f" Key terms: {keywords}."
267
+ return base[: self._summary_max_chars]