superlocalmemory 2.8.5 → 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 (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.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