superlocalmemory 2.8.6 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +1 -1
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.sh} +0 -0
@@ -0,0 +1,170 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3 — Bridge Discovery + Spreading Activation.
6
+
7
+ Connects disconnected retrieval results via intermediate facts.
8
+ Combines AriadneMem's 5-step bridging with Hindsight's TEMPR activation.
9
+
10
+ Algorithm:
11
+ 1. Sort seed results chronologically
12
+ 2. For consecutive pairs, check entity overlap + temporal proximity
13
+ 3. If disconnected: try entity/keyword/proper-noun bridge strategies
14
+ 4. Add bridge facts with inferred edges
15
+ 5. Spreading activation from seeds through graph
16
+
17
+ Parameters:
18
+ - max_depth=3 hops
19
+ - node_budget=8-25
20
+ - time_window=1-168 hours
21
+ - decay=0.7 per hop
22
+ - typed mu: entity=1.2, causal=1.3, semantic=0.8, temporal=0.9
23
+
24
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
25
+ License: MIT
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+ import re
31
+ from typing import TYPE_CHECKING
32
+
33
+ if TYPE_CHECKING:
34
+ from superlocalmemory.storage.database import DatabaseManager
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # Spreading activation parameters (Hindsight TEMPR)
39
+ _DECAY: float = 0.7
40
+ _TYPED_MU: dict[str, float] = {
41
+ "entity": 1.2,
42
+ "causal": 1.3,
43
+ "semantic": 0.8,
44
+ "temporal": 0.9,
45
+ "supersedes": 0.0,
46
+ }
47
+ _MAX_DEPTH: int = 4
48
+ _NODE_BUDGET: int = 50
49
+
50
+
51
+ class BridgeDiscovery:
52
+ """Connect disconnected retrieval results via graph paths.
53
+
54
+ Usage::
55
+ bridge = BridgeDiscovery(db)
56
+ expanded = bridge.discover(seed_fact_ids, profile_id)
57
+ """
58
+
59
+ def __init__(self, db: DatabaseManager) -> None:
60
+ self._db = db
61
+
62
+ def discover(
63
+ self,
64
+ seed_ids: list[str],
65
+ profile_id: str,
66
+ max_bridges: int = 10,
67
+ ) -> list[tuple[str, float]]:
68
+ """Find bridge facts connecting seed results.
69
+
70
+ Args:
71
+ seed_ids: Fact IDs from initial retrieval.
72
+ profile_id: Scope to this profile.
73
+ max_bridges: Maximum bridge facts to return.
74
+
75
+ Returns:
76
+ List of (fact_id, bridge_score) for discovered bridges.
77
+ """
78
+ if len(seed_ids) < 2:
79
+ return []
80
+
81
+ bridges: list[tuple[str, float]] = []
82
+ seen = set(seed_ids)
83
+
84
+ # Check consecutive pairs for entity overlap
85
+ for i in range(len(seed_ids) - 1):
86
+ fact_a = self._db.get_fact(seed_ids[i])
87
+ fact_b = self._db.get_fact(seed_ids[i + 1])
88
+ if not fact_a or not fact_b:
89
+ continue
90
+
91
+ entities_a = set(fact_a.canonical_entities)
92
+ entities_b = set(fact_b.canonical_entities)
93
+
94
+ # If they share entities, no bridge needed
95
+ if entities_a & entities_b:
96
+ continue
97
+
98
+ # Strategy 1: Entity bridge (union minus intersection)
99
+ bridge_entities = (entities_a | entities_b) - (entities_a & entities_b)
100
+ for eid in bridge_entities:
101
+ entity_facts = self._db.get_facts_by_entity(eid, profile_id)
102
+ for f in entity_facts[:5]:
103
+ if f.fact_id not in seen:
104
+ seen.add(f.fact_id)
105
+ bridges.append((f.fact_id, 0.7))
106
+
107
+ if len(bridges) >= max_bridges:
108
+ break
109
+
110
+ bridges.sort(key=lambda x: x[1], reverse=True)
111
+ return bridges[:max_bridges]
112
+
113
+ def spreading_activation(
114
+ self,
115
+ seed_ids: list[str],
116
+ profile_id: str,
117
+ max_depth: int = _MAX_DEPTH,
118
+ budget: int = _NODE_BUDGET,
119
+ ) -> list[tuple[str, float]]:
120
+ """Spreading activation from seed facts through the graph.
121
+
122
+ At each hop, activation decays by _DECAY and is modulated by
123
+ edge type via _TYPED_MU.
124
+
125
+ Args:
126
+ seed_ids: Starting fact IDs.
127
+ profile_id: Scope.
128
+ max_depth: Maximum hops.
129
+ budget: Maximum nodes to return.
130
+
131
+ Returns:
132
+ List of (fact_id, activation_score) for activated facts.
133
+ """
134
+ activations: dict[str, float] = {fid: 1.0 for fid in seed_ids}
135
+ frontier = list(seed_ids)
136
+
137
+ for depth in range(max_depth):
138
+ next_frontier: list[str] = []
139
+ for fid in frontier:
140
+ current_activation = activations.get(fid, 0.0)
141
+ if current_activation < 0.01:
142
+ continue
143
+
144
+ edges = self._db.get_edges_for_node(fid, profile_id)
145
+ for edge in edges:
146
+ other_id = (
147
+ edge.target_id
148
+ if edge.source_id == fid
149
+ else edge.source_id
150
+ )
151
+ mu = _TYPED_MU.get(edge.edge_type.value, 0.8)
152
+ propagated = current_activation * _DECAY * mu
153
+
154
+ if propagated > activations.get(other_id, 0.0):
155
+ activations[other_id] = propagated
156
+ if other_id not in seed_ids:
157
+ next_frontier.append(other_id)
158
+
159
+ frontier = next_frontier
160
+ if not frontier:
161
+ break
162
+
163
+ # Return non-seed nodes with activation
164
+ results = [
165
+ (fid, score)
166
+ for fid, score in activations.items()
167
+ if fid not in set(seed_ids) and score > 0.01
168
+ ]
169
+ results.sort(key=lambda x: x[1], reverse=True)
170
+ return results[:budget]
@@ -0,0 +1,390 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3 — Retrieval Engine (4-Channel Orchestrator).
6
+
7
+ 4 channels -> single RRF fusion -> optional cross-encoder rerank.
8
+ Replaces V1's broken 10-channel triple-re-fusion pipeline.
9
+
10
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
11
+ License: MIT
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import math
17
+ import re
18
+ import time
19
+ from typing import TYPE_CHECKING, Any, Protocol
20
+
21
+ from superlocalmemory.core.config import ChannelWeights, RetrievalConfig
22
+ from superlocalmemory.retrieval.fusion import FusionResult, weighted_rrf
23
+ from superlocalmemory.retrieval.strategy import QueryStrategy, QueryStrategyClassifier
24
+ from superlocalmemory.storage.models import (
25
+ AtomicFact, Mode, RecallResponse, RetrievalResult,
26
+ )
27
+
28
+ if TYPE_CHECKING:
29
+ from superlocalmemory.retrieval.bm25_channel import BM25Channel
30
+ from superlocalmemory.retrieval.entity_channel import EntityGraphChannel
31
+ from superlocalmemory.retrieval.semantic_channel import SemanticChannel
32
+ from superlocalmemory.retrieval.temporal_channel import TemporalChannel
33
+ from superlocalmemory.storage.database import DatabaseManager
34
+ from superlocalmemory.trust.scorer import TrustScorer
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ class CrossEncoderProtocol(Protocol):
40
+ """Duck-typed cross-encoder interface."""
41
+ def rerank(self, query: str, candidates: list[tuple[str, str]]) -> list[tuple[str, float]]: ...
42
+
43
+
44
+ class EmbeddingProvider(Protocol):
45
+ """Duck-typed embedding provider."""
46
+ def embed(self, text: str) -> list[float]: ...
47
+
48
+
49
+ class RetrievalEngine:
50
+ """4-channel retrieval: semantic + BM25 + entity_graph + temporal.
51
+
52
+ Usage::
53
+ engine = RetrievalEngine(db, config, channels, embedder)
54
+ response = engine.recall("What did Alice do?", "default", Mode.A)
55
+ """
56
+
57
+ def __init__(
58
+ self, db: DatabaseManager, config: RetrievalConfig,
59
+ channels: dict[str, Any],
60
+ embedder: EmbeddingProvider | None = None,
61
+ reranker: CrossEncoderProtocol | None = None,
62
+ strategy: QueryStrategyClassifier | None = None,
63
+ base_weights: ChannelWeights | None = None,
64
+ profile_channel: Any | None = None,
65
+ bridge_discovery: Any | None = None,
66
+ trust_scorer: TrustScorer | None = None,
67
+ ) -> None:
68
+ self._db = db
69
+ self._config = config
70
+ self._semantic: SemanticChannel | None = channels.get("semantic")
71
+ self._bm25: BM25Channel | None = channels.get("bm25")
72
+ self._entity: EntityGraphChannel | None = channels.get("entity_graph")
73
+ self._temporal: TemporalChannel | None = channels.get("temporal")
74
+ self._embedder = embedder
75
+ self._reranker = reranker
76
+ self._strategy = strategy or QueryStrategyClassifier()
77
+ self._base_weights = (base_weights or ChannelWeights()).as_dict()
78
+ self._profile_channel = profile_channel
79
+ self._bridge = bridge_discovery
80
+ self._trust_scorer = trust_scorer
81
+
82
+ def recall(
83
+ self, query: str, profile_id: str,
84
+ mode: Mode = Mode.A, limit: int = 20,
85
+ ) -> RecallResponse:
86
+ """Full retrieval pipeline: strategy -> channels -> RRF -> rerank."""
87
+ t0 = time.monotonic()
88
+
89
+ # 1. Classify query, get adaptive weights
90
+ strat = self._strategy.classify(query, self._base_weights)
91
+
92
+ # Profile shortcut (runs before channel search)
93
+ if self._profile_channel is not None:
94
+ try:
95
+ profile_hits = self._profile_channel.search(
96
+ query, profile_id, top_k=10,
97
+ )
98
+ if profile_hits:
99
+ strat.weights["profile"] = 2.0
100
+ except Exception as exc:
101
+ logger.warning("Profile channel: %s", exc)
102
+ profile_hits = []
103
+ else:
104
+ profile_hits = []
105
+
106
+ # Dynamic top-k for aggregation queries
107
+ effective_limit = 50 if strat.query_type == "aggregation" else limit
108
+
109
+ # 3. Run 4 channels
110
+ ch_results = self._run_channels(query, profile_id, strat)
111
+ if profile_hits:
112
+ ch_results["profile"] = profile_hits
113
+ total = sum(len(v) for v in ch_results.values())
114
+
115
+ # 3. Single-pass RRF fusion
116
+ fused = weighted_rrf(ch_results, strat.weights, k=self._config.rrf_k)
117
+
118
+ # Bridge discovery for multi-hop queries
119
+ if self._bridge is not None and strat.query_type == "multi_hop":
120
+ try:
121
+ seed_ids = [fr.fact_id for fr in fused[:10]]
122
+ bridges = self._bridge.discover(seed_ids, profile_id, max_bridges=10)
123
+ spread = self._bridge.spreading_activation(seed_ids, profile_id)
124
+ extra = bridges + spread
125
+ for fid, score in extra:
126
+ if not any(fr.fact_id == fid for fr in fused):
127
+ fused.append(FusionResult(
128
+ fact_id=fid, fused_score=score * 0.8,
129
+ channel_ranks={}, channel_scores={},
130
+ ))
131
+ except Exception as exc:
132
+ logger.warning("Bridge discovery: %s", exc)
133
+
134
+ # Scene expansion
135
+ if fused:
136
+ try:
137
+ expanded_ids: set[str] = set()
138
+ for fr in fused[:20]:
139
+ scenes = self._db.get_scenes_for_fact(fr.fact_id, profile_id)
140
+ for scene in scenes[:2]:
141
+ for sfid in scene.fact_ids:
142
+ if not any(f.fact_id == sfid for f in fused) and sfid not in expanded_ids:
143
+ expanded_ids.add(sfid)
144
+ fused.append(FusionResult(
145
+ fact_id=sfid, fused_score=fr.fused_score * 0.8,
146
+ channel_ranks={}, channel_scores={},
147
+ ))
148
+ except Exception as exc:
149
+ logger.warning("Scene expansion: %s", exc)
150
+
151
+ # 4. Load facts for rerank pool
152
+ pool = min(len(fused), max(effective_limit * 3, 30))
153
+ top = fused[:pool]
154
+ facts = self._load_facts(top, profile_id)
155
+
156
+ # 5. Cross-encoder rerank (optional)
157
+ # Bug 4 fix: reduced alpha for multi-hop/temporal to preserve diversity
158
+ if self._reranker is not None and facts:
159
+ ce_alpha = 0.5 if strat.query_type in ("multi_hop", "temporal") else 0.75
160
+ top = self._apply_reranker(query, top, facts, alpha=ce_alpha)
161
+
162
+ # 6. Build response
163
+ results = self._build_results(top[:effective_limit], facts, strat)
164
+ ms = (time.monotonic() - t0) * 1000.0
165
+ return RecallResponse(
166
+ query=query, mode=mode, results=results,
167
+ query_type=strat.query_type, channel_weights=strat.weights,
168
+ total_candidates=total, retrieval_time_ms=ms,
169
+ )
170
+
171
+ # -- Channel execution --------------------------------------------------
172
+
173
+ def _run_channels(
174
+ self, query: str, profile_id: str, strat: QueryStrategy,
175
+ ) -> dict[str, list[tuple[str, float]]]:
176
+ """Run active retrieval channels. Respects disabled_channels config for ablation."""
177
+ out: dict[str, list[tuple[str, float]]] = {}
178
+ # Skip channels listed in disabled_channels (ablation support)
179
+ disabled = set(self._config.disabled_channels)
180
+
181
+ if self._semantic is not None and self._embedder is not None and "semantic" not in disabled:
182
+ try:
183
+ q_emb = self._embedder.embed(query)
184
+ r = self._semantic.search(q_emb, profile_id, self._config.semantic_top_k)
185
+ if r:
186
+ out["semantic"] = r
187
+ except Exception as exc:
188
+ logger.warning("Semantic channel: %s", exc)
189
+
190
+ if self._bm25 is not None and "bm25" not in disabled:
191
+ try:
192
+ r = self._bm25.search(query, profile_id, self._config.bm25_top_k)
193
+ if r:
194
+ out["bm25"] = r
195
+ except Exception as exc:
196
+ logger.warning("BM25 channel: %s", exc)
197
+
198
+ if self._entity is not None and "entity_graph" not in disabled:
199
+ try:
200
+ r = self._entity.search(query, profile_id, top_k=self._config.bm25_top_k)
201
+ if r:
202
+ out["entity_graph"] = r
203
+ except Exception as exc:
204
+ logger.warning("Entity channel: %s", exc)
205
+
206
+ if self._temporal is not None and "temporal" not in disabled:
207
+ try:
208
+ r = self._temporal.search(query, profile_id, top_k=self._config.bm25_top_k)
209
+ if r:
210
+ out["temporal"] = r
211
+ except Exception as exc:
212
+ logger.warning("Temporal channel: %s", exc)
213
+
214
+ return out
215
+
216
+ # -- Fact loading -------------------------------------------------------
217
+
218
+ def _load_facts(
219
+ self, fused: list[FusionResult], profile_id: str,
220
+ ) -> dict[str, AtomicFact]:
221
+ needed = {fr.fact_id for fr in fused}
222
+ if not needed:
223
+ return {}
224
+ all_facts = self._db.get_all_facts(profile_id)
225
+ return {f.fact_id: f for f in all_facts if f.fact_id in needed}
226
+
227
+ # -- Cross-encoder rerank -----------------------------------------------
228
+
229
+ @staticmethod
230
+ def _sigmoid(x: float) -> float:
231
+ """Numerically stable sigmoid."""
232
+ x = max(-500.0, min(500.0, x))
233
+ return 1.0 / (1.0 + math.exp(-x))
234
+
235
+ def _apply_reranker(
236
+ self, query: str, fused: list[FusionResult],
237
+ fact_map: dict[str, AtomicFact],
238
+ alpha: float = 0.75,
239
+ ) -> list[FusionResult]:
240
+ """Rerank with blended CE + RRF scores (Bug 1 fix).
241
+
242
+ Blended: alpha * sigmoid(CE_score) + (1 - alpha) * rrf_score.
243
+ Speaker tags stripped before scoring (Bug 3 fix).
244
+ """
245
+ # Bug 2 fix: score ALL candidates, not just top_k
246
+ candidates = [
247
+ (fact_map[fr.fact_id], fr.fused_score)
248
+ for fr in fused if fr.fact_id in fact_map
249
+ ]
250
+ if not candidates:
251
+ return fused
252
+
253
+ # Bug 3 fix: strip speaker tags from content before CE scoring
254
+ clean_candidates: list[tuple[AtomicFact, float]] = []
255
+ for fact, score in candidates:
256
+ cleaned_content = re.sub(r'^\[[A-Za-z]+\]:\s*', '', fact.content)
257
+ clean_fact = AtomicFact(
258
+ fact_id=fact.fact_id, memory_id=fact.memory_id,
259
+ profile_id=fact.profile_id, content=cleaned_content,
260
+ fact_type=fact.fact_type, entities=fact.entities,
261
+ canonical_entities=fact.canonical_entities,
262
+ observation_date=fact.observation_date,
263
+ referenced_date=fact.referenced_date,
264
+ confidence=fact.confidence, importance=fact.importance,
265
+ evidence_count=fact.evidence_count,
266
+ access_count=fact.access_count,
267
+ embedding=fact.embedding, created_at=fact.created_at,
268
+ )
269
+ clean_candidates.append((clean_fact, score))
270
+
271
+ try:
272
+ scored = self._reranker.rerank( # type: ignore[union-attr]
273
+ query, clean_candidates, top_k=len(clean_candidates),
274
+ )
275
+ except Exception as exc:
276
+ logger.warning("Cross-encoder rerank failed: %s", exc)
277
+ return fused
278
+
279
+ score_map = {fact.fact_id: score for fact, score in scored}
280
+
281
+ updated = [
282
+ FusionResult(
283
+ fact_id=fr.fact_id,
284
+ fused_score=(
285
+ alpha * self._sigmoid(score_map.get(fr.fact_id, 0.0))
286
+ + (1.0 - alpha) * fr.fused_score
287
+ ),
288
+ channel_ranks=fr.channel_ranks,
289
+ channel_scores=fr.channel_scores,
290
+ )
291
+ for fr in fused
292
+ ]
293
+ updated.sort(key=lambda r: r.fused_score, reverse=True)
294
+ return updated
295
+
296
+ # -- Agentic adapter -----------------------------------
297
+
298
+ def recall_facts(
299
+ self, query: str, profile_id: str,
300
+ top_k: int = 20, skip_agentic: bool = True,
301
+ ) -> list[tuple[AtomicFact, float]]:
302
+ """Simplified recall returning (fact, score) tuples.
303
+
304
+ Used by AgenticRetriever for round-2 re-retrieval.
305
+ skip_agentic is always True here to prevent infinite recursion.
306
+ """
307
+ response = self.recall(query, profile_id, limit=top_k)
308
+ return [(r.fact, r.score) for r in response.results]
309
+
310
+ # -- Trust weighting ----------------------------------------------------
311
+
312
+ def _get_trust_weight(self, fact: AtomicFact, profile_id: str) -> tuple[float, float]:
313
+ """Look up Bayesian trust score and convert to a multiplicative weight.
314
+
315
+ Returns (trust_weight, raw_trust_score).
316
+ trust_weight is clamped to [0.5, 1.5]:
317
+ - trust=0.0 -> weight=0.5 (demote untrusted facts)
318
+ - trust=0.5 -> weight=1.0 (neutral, default prior)
319
+ - trust=1.0 -> weight=1.5 (promote highly trusted facts)
320
+ If trust scoring is disabled or unavailable, returns (1.0, 0.5).
321
+ """
322
+ if not self._config.use_trust_weighting or self._trust_scorer is None:
323
+ return 1.0, 0.5
324
+
325
+ try:
326
+ raw = self._trust_scorer.get_fact_trust(fact.fact_id, profile_id)
327
+ except Exception:
328
+ return 1.0, 0.5
329
+
330
+ # Linear map: trust 0.0->0.5, 0.5->1.0, 1.0->1.5
331
+ weight = 0.5 + raw # raw in [0, 1] -> weight in [0.5, 1.5]
332
+ return weight, raw
333
+
334
+ # -- Response building --------------------------------------------------
335
+
336
+ def _build_results(
337
+ self, fused: list[FusionResult], fact_map: dict[str, AtomicFact],
338
+ strat: QueryStrategy,
339
+ ) -> list[RetrievalResult]:
340
+ from datetime import UTC, datetime
341
+ now = datetime.now(UTC)
342
+ results: list[RetrievalResult] = []
343
+ profile_id = next(
344
+ (f.profile_id for f in fact_map.values()), "default",
345
+ )
346
+ for fr in fused:
347
+ fact = fact_map.get(fr.fact_id)
348
+ if fact is None:
349
+ continue
350
+ evidence = [
351
+ f"{ch}(rank={rk}, score={fr.channel_scores.get(ch, 0.0):.4f})"
352
+ for ch, rk in sorted(fr.channel_ranks.items(), key=lambda x: x[1])
353
+ if rk < 1000
354
+ ]
355
+ # Recency boost: recent facts get up to 1.1x, old facts 0.9x
356
+ age_days = 0.0
357
+ if fact.created_at:
358
+ try:
359
+ created = datetime.fromisoformat(fact.created_at.replace("Z", "+00:00"))
360
+ age_days = max(0.0, (now - created).total_seconds() / 86400.0)
361
+ except (ValueError, TypeError):
362
+ pass
363
+ recency = max(0.1, 1.0 - age_days / 365.0)
364
+ recency_boost = 1.0 + 0.2 * (recency - 0.5)
365
+
366
+ # Content quality: penalize short/low-info facts that rank high
367
+ # due to BM25 name-matching (greetings like "Hey Caroline!" score high
368
+ # on BM25 but have zero retrieval value)
369
+ content_len = len(fact.content.strip())
370
+ if content_len < 25:
371
+ quality = 0.1
372
+ elif content_len < 50:
373
+ quality = 0.5
374
+ elif content_len < 80:
375
+ quality = 0.8
376
+ else:
377
+ quality = 1.0
378
+
379
+ # Trust weighting: Bayesian trust modulates final ranking
380
+ trust_weight, raw_trust = self._get_trust_weight(fact, profile_id)
381
+
382
+ boosted_score = fr.fused_score * recency_boost * quality * trust_weight
383
+ confidence = min(1.0, boosted_score * 10.0) * fact.confidence
384
+ results.append(RetrievalResult(
385
+ fact=fact, score=boosted_score,
386
+ channel_scores=fr.channel_scores,
387
+ confidence=confidence, evidence_chain=evidence,
388
+ trust_score=raw_trust,
389
+ ))
390
+ return results