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,316 @@
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
+ """LLM backbone — unified interface for LLM providers.
6
+
7
+ Supports OpenAI, Anthropic, Azure OpenAI, and Ollama via raw HTTP (httpx).
8
+ Falls back gracefully when no API key is configured — Mode A still works.
9
+
10
+ Providers:
11
+ - ``"ollama"``: Local Ollama (OpenAI-compatible, no auth needed).
12
+ - ``"openai"``: OpenAI API (GPT-4o, etc.).
13
+ - ``"anthropic"``: Anthropic API (Claude, etc.).
14
+ - ``"azure"``: Azure OpenAI (via AI Foundry deployment).
15
+
16
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
17
+ License: MIT
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ import os
24
+ import socket
25
+ import time
26
+ from dataclasses import dataclass
27
+ from typing import Any
28
+
29
+ import httpx
30
+
31
+ from superlocalmemory.core.config import LLMConfig
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Constants
37
+ # ---------------------------------------------------------------------------
38
+
39
+ _OPENAI_URL = "https://api.openai.com/v1/chat/completions"
40
+ _OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
41
+ _ANTHROPIC_URL = "https://api.anthropic.com/v1/messages"
42
+ _ANTHROPIC_API_VERSION = "2023-06-01"
43
+ _AZURE_API_VERSION = "2024-12-01-preview"
44
+ _OLLAMA_DEFAULT_BASE = "http://localhost:11434"
45
+
46
+ _ENV_KEYS: dict[str, str] = {
47
+ "openai": "OPENAI_API_KEY",
48
+ "anthropic": "ANTHROPIC_API_KEY",
49
+ "azure": "AZURE_OPENAI_API_KEY",
50
+ "ollama": "OLLAMA_HOST",
51
+ "openrouter": "OPENROUTER_API_KEY",
52
+ }
53
+
54
+ _SUPPORTED_PROVIDERS = frozenset({"openai", "anthropic", "azure", "ollama", "openrouter"})
55
+
56
+ _MAX_RETRIES = 3
57
+ _RETRY_BASE_DELAY = 1.0 # seconds, doubles each retry
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Exceptions
62
+ # ---------------------------------------------------------------------------
63
+
64
+ class LLMUnavailableError(Exception):
65
+ """Raised when no API key is available for the configured provider."""
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Response dataclass
70
+ # ---------------------------------------------------------------------------
71
+
72
+ @dataclass(frozen=True)
73
+ class LLMResponse:
74
+ """Immutable container for a single LLM generation result."""
75
+
76
+ text: str
77
+ model: str
78
+ tokens_used: int
79
+ latency_ms: float
80
+
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # Main class
84
+ # ---------------------------------------------------------------------------
85
+
86
+ class LLMBackbone:
87
+ """Unified LLM interface driven by LLMConfig.
88
+
89
+ All HTTP via httpx — no provider SDKs needed.
90
+ Includes retry logic (3 attempts, exponential backoff) and
91
+ socket-level timeout as a hard backstop for SSL hangs (S15 lesson).
92
+ """
93
+
94
+ def __init__(self, config: LLMConfig) -> None:
95
+ if config.provider and config.provider not in _SUPPORTED_PROVIDERS:
96
+ raise ValueError(
97
+ f"Unsupported provider '{config.provider}'. "
98
+ f"Choose from {sorted(_SUPPORTED_PROVIDERS)}."
99
+ )
100
+ self._provider = config.provider
101
+ self._model = config.model
102
+ self._timeout = config.timeout_seconds
103
+ self._default_temperature = config.temperature
104
+ self._default_max_tokens = config.max_tokens
105
+
106
+ # Resolve API key: config > environment variable.
107
+ if self._provider == "ollama":
108
+ self._api_key = ""
109
+ host = config.api_base or os.environ.get(
110
+ "OLLAMA_HOST", _OLLAMA_DEFAULT_BASE,
111
+ )
112
+ self._base_url = f"{host.rstrip('/')}/v1/chat/completions"
113
+ elif self._provider == "openrouter":
114
+ self._api_key = config.api_key or os.environ.get(
115
+ _ENV_KEYS.get(self._provider, ""), "",
116
+ )
117
+ self._base_url = config.api_base or _OPENROUTER_URL
118
+ elif self._provider:
119
+ self._api_key = config.api_key or os.environ.get(
120
+ _ENV_KEYS.get(self._provider, ""), "",
121
+ )
122
+ self._base_url = config.api_base
123
+ else:
124
+ self._api_key = ""
125
+ self._base_url = ""
126
+
127
+ # -- Properties ---------------------------------------------------------
128
+
129
+ def is_available(self) -> bool:
130
+ """True when the provider is ready for requests."""
131
+ if not self._provider:
132
+ return False
133
+ if self._provider == "ollama":
134
+ return True
135
+ return bool(self._api_key)
136
+
137
+ @property
138
+ def provider(self) -> str:
139
+ return self._provider
140
+
141
+ @property
142
+ def model(self) -> str:
143
+ return self._model
144
+
145
+ # -- Core generation ----------------------------------------------------
146
+
147
+ def generate(
148
+ self,
149
+ prompt: str,
150
+ system: str = "",
151
+ temperature: float | None = None,
152
+ max_tokens: int | None = None,
153
+ ) -> str:
154
+ """Send prompt to the LLM and return generated text.
155
+
156
+ Returns empty string on content-filter errors (Azure 400)
157
+ instead of crashing — lets callers continue gracefully.
158
+ Retries up to 3 times with exponential backoff on transient errors.
159
+ """
160
+ if not self.is_available():
161
+ raise LLMUnavailableError(
162
+ f"No API key for provider '{self._provider}'. "
163
+ f"Set {_ENV_KEYS.get(self._provider, 'API_KEY')} or pass api_key=."
164
+ )
165
+
166
+ temp = temperature if temperature is not None else self._default_temperature
167
+ tokens = max_tokens if max_tokens is not None else self._default_max_tokens
168
+ url, headers, payload = self._build_request(prompt, system, tokens, temp)
169
+
170
+ last_error: Exception | None = None
171
+ for attempt in range(_MAX_RETRIES):
172
+ try:
173
+ response = self._send(url, headers, payload)
174
+ return self._extract_text(response)
175
+ except httpx.HTTPStatusError as exc:
176
+ # Azure content filter returns 400 — not retryable.
177
+ if exc.response.status_code == 400:
178
+ logger.warning("Content filter or bad request (400). Returning empty.")
179
+ return ""
180
+ last_error = exc
181
+ except (httpx.TimeoutException, httpx.ConnectError) as exc:
182
+ last_error = exc
183
+
184
+ if attempt < _MAX_RETRIES - 1:
185
+ delay = _RETRY_BASE_DELAY * (2 ** attempt)
186
+ logger.info("Retry %d/%d after %.1fs", attempt + 1, _MAX_RETRIES, delay)
187
+ time.sleep(delay)
188
+
189
+ logger.error("All %d retries exhausted: %s", _MAX_RETRIES, last_error)
190
+ return ""
191
+
192
+ # -- HTTP transport -----------------------------------------------------
193
+
194
+ def _send(self, url: str, headers: dict, payload: dict) -> dict:
195
+ """Execute HTTP POST with socket-level SSL backstop."""
196
+ old_default = socket.getdefaulttimeout()
197
+ socket.setdefaulttimeout(self._timeout + 30)
198
+ try:
199
+ timeout = httpx.Timeout(
200
+ connect=10.0,
201
+ read=self._timeout,
202
+ write=10.0,
203
+ pool=10.0,
204
+ )
205
+ with httpx.Client(timeout=timeout) as client:
206
+ resp = client.post(url, headers=headers, json=payload)
207
+ resp.raise_for_status()
208
+ return resp.json()
209
+ finally:
210
+ socket.setdefaulttimeout(old_default)
211
+
212
+ # -- Request builders ---------------------------------------------------
213
+
214
+ def _build_request(
215
+ self, prompt: str, system: str, max_tokens: int, temperature: float,
216
+ ) -> tuple[str, dict[str, str], dict]:
217
+ """Build provider-specific (url, headers, payload)."""
218
+ builders = {
219
+ "ollama": self._build_ollama,
220
+ "anthropic": self._build_anthropic,
221
+ "azure": self._build_azure,
222
+ }
223
+ builder = builders.get(self._provider, self._build_openai)
224
+ return builder(prompt, system, max_tokens, temperature)
225
+
226
+ def _build_openai(
227
+ self, prompt: str, system: str, max_tokens: int, temperature: float,
228
+ ) -> tuple[str, dict[str, str], dict]:
229
+ messages = self._make_messages(system, prompt)
230
+ headers = {
231
+ "Authorization": f"Bearer {self._api_key}",
232
+ "Content-Type": "application/json",
233
+ }
234
+ payload = {
235
+ "model": self._model,
236
+ "messages": messages,
237
+ "max_tokens": max_tokens,
238
+ "temperature": temperature,
239
+ }
240
+ url = self._base_url or _OPENAI_URL
241
+ return url, headers, payload
242
+
243
+ def _build_ollama(
244
+ self, prompt: str, system: str, max_tokens: int, temperature: float,
245
+ ) -> tuple[str, dict[str, str], dict]:
246
+ messages = self._make_messages(system, prompt)
247
+ headers = {"Content-Type": "application/json"}
248
+ payload = {
249
+ "model": self._model,
250
+ "messages": messages,
251
+ "max_tokens": max_tokens,
252
+ "temperature": temperature,
253
+ }
254
+ return self._base_url, headers, payload
255
+
256
+ def _build_anthropic(
257
+ self, prompt: str, system: str, max_tokens: int, temperature: float,
258
+ ) -> tuple[str, dict[str, str], dict]:
259
+ headers = {
260
+ "x-api-key": self._api_key,
261
+ "anthropic-version": _ANTHROPIC_API_VERSION,
262
+ "Content-Type": "application/json",
263
+ }
264
+ payload: dict[str, Any] = {
265
+ "model": self._model,
266
+ "max_tokens": max_tokens,
267
+ "temperature": temperature,
268
+ "messages": [{"role": "user", "content": prompt}],
269
+ }
270
+ if system:
271
+ payload["system"] = system
272
+ return _ANTHROPIC_URL, headers, payload
273
+
274
+ def _build_azure(
275
+ self, prompt: str, system: str, max_tokens: int, temperature: float,
276
+ ) -> tuple[str, dict[str, str], dict]:
277
+ if not self._base_url:
278
+ raise ValueError("Azure provider requires api_base URL.")
279
+ url = (
280
+ f"{self._base_url.rstrip('/')}/openai/deployments/"
281
+ f"{self._model}/chat/completions"
282
+ f"?api-version={_AZURE_API_VERSION}"
283
+ )
284
+ messages = self._make_messages(system, prompt)
285
+ headers = {"api-key": self._api_key, "Content-Type": "application/json"}
286
+ payload: dict[str, Any] = {"messages": messages}
287
+ if "gpt-5" in self._model.lower():
288
+ payload["max_completion_tokens"] = max(max_tokens, 200)
289
+ payload["reasoning_effort"] = "none"
290
+ if temperature > 0:
291
+ payload["temperature"] = temperature
292
+ else:
293
+ payload["max_tokens"] = max_tokens
294
+ payload["temperature"] = temperature
295
+ return url, headers, payload
296
+
297
+ # -- Response parsing ---------------------------------------------------
298
+
299
+ def _extract_text(self, data: dict) -> str:
300
+ """Extract text from provider-specific JSON response."""
301
+ if self._provider == "anthropic":
302
+ return data.get("content", [{}])[0].get("text", "").strip()
303
+ # OpenAI / Azure / Ollama share response format.
304
+ choices = data.get("choices", [{}])
305
+ return choices[0].get("message", {}).get("content", "").strip()
306
+
307
+ # -- Helpers ------------------------------------------------------------
308
+
309
+ @staticmethod
310
+ def _make_messages(system: str, prompt: str) -> list[dict[str, str]]:
311
+ """Build messages array with optional system message."""
312
+ messages: list[dict[str, str]] = []
313
+ if system:
314
+ messages.append({"role": "system", "content": system})
315
+ messages.append({"role": "user", "content": prompt})
316
+ return messages
File without changes
@@ -0,0 +1,356 @@
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
+ """Fisher-Rao geodesic metric with Bayesian variance update.
6
+
7
+ Information-geometric similarity on the statistical manifold of diagonal
8
+ Gaussians. Two embeddings with the same mean but different uncertainty
9
+ have zero cosine distance but nonzero Fisher distance --- this is the
10
+ core insight that differentiates Fisher-Rao from cosine similarity.
11
+
12
+ Geodesic distance (Atkinson & Mitchell 1981, Pinele et al. 2020):
13
+
14
+ Univariate component i:
15
+ delta_i = ((mu1_i - mu2_i)^2 + 2*(sigma1_i - sigma2_i)^2)
16
+ / (4 * sigma1_i * sigma2_i)
17
+ d_i = sqrt(2) * arccosh(1 + delta_i)
18
+
19
+ Diagonal multivariate (product-manifold decomposition):
20
+ d_FR(p, q) = sqrt( sum_i d_i^2 )
21
+
22
+ Bayesian variance update (NEW in Innovation Wave 4):
23
+
24
+ V1 bug: query always received UNIFORM variance, so Fisher degenerated
25
+ to a monotonic transform of cosine. FIX: every fact maintains its own
26
+ variance vector that narrows on each observation.
27
+
28
+ new_var_i = 1 / (1/old_var_i + 1/obs_var_i)
29
+
30
+ Multiple consistent observations reduce variance, increasing confidence.
31
+ This gives Fisher-Rao a genuine ranking advantage over cosine: well-
32
+ confirmed memories have tighter distributions and score higher when the
33
+ query matches.
34
+
35
+ References:
36
+ Rao C R (1945). Information and the accuracy attainable in the
37
+ estimation of statistical parameters. Bull. Calcutta Math. Soc.
38
+ Atkinson C & Mitchell A (1981). Rao's distance measure.
39
+ Sankhya: The Indian Journal of Statistics, Series A.
40
+ Pinele J, Strapasson J E & Costa S I R (2020). The Fisher-Rao
41
+ distance between multivariate normal distributions: special
42
+ cases, bounds and applications. Entropy 22(4):404.
43
+
44
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
45
+ License: MIT
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ import math
51
+
52
+ import numpy as np
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Constants
56
+ # ---------------------------------------------------------------------------
57
+
58
+ _VARIANCE_FLOOR: float = 0.05 # Minimum per-dimension variance (40x dynamic range)
59
+ _VARIANCE_CEIL: float = 2.0 # Maximum per-dimension variance (initial uncertainty)
60
+ _DEFAULT_TEMPERATURE: float = 15.0
61
+ _SMALL_DELTA_THRESHOLD: float = 1e-7
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Public API
66
+ # ---------------------------------------------------------------------------
67
+
68
+ class FisherRaoMetric:
69
+ """Fisher-Rao geodesic metric with Bayesian variance tracking.
70
+
71
+ Each fact is modelled as a diagonal Gaussian N(mu, diag(sigma^2)).
72
+ Distance is the Fisher-Rao geodesic on this statistical manifold.
73
+ Variance starts high (uncertain) and NARROWS on repeated access via
74
+ Bayesian update, giving well-confirmed memories a ranking advantage.
75
+
76
+ Attributes:
77
+ temperature: Softmax scaling for similarity conversion.
78
+ """
79
+
80
+ __slots__ = ("temperature",)
81
+
82
+ def __init__(self, temperature: float = _DEFAULT_TEMPERATURE) -> None:
83
+ if temperature <= 0:
84
+ raise ValueError(f"temperature must be positive, got {temperature}")
85
+ self.temperature = temperature
86
+
87
+ # ------------------------------------------------------------------
88
+ # Parameter derivation
89
+ # ------------------------------------------------------------------
90
+
91
+ def compute_params(
92
+ self,
93
+ embedding: list[float],
94
+ ) -> tuple[list[float], list[float]]:
95
+ """Derive Fisher parameters (mean, variance) from a raw embedding.
96
+
97
+ Mean is the L2-normalised embedding. Variance is content-derived
98
+ and *heterogeneous*: dimensions with strong signal get low variance,
99
+ weak-signal dimensions get high variance.
100
+
101
+ Mapping: ``var_i = CEIL - (CEIL - FLOOR) * |normed_i| / max_abs``
102
+ Strong signal (large |normed_i|) -> low variance (high confidence).
103
+
104
+ Args:
105
+ embedding: Raw embedding vector, any dimensionality.
106
+
107
+ Returns:
108
+ (mean, variance) as plain Python lists, same length as input.
109
+ """
110
+ arr = np.asarray(embedding, dtype=np.float64)
111
+ norm = np.linalg.norm(arr)
112
+ if norm < 1e-12:
113
+ mean = np.zeros_like(arr)
114
+ variance = np.full_like(arr, _VARIANCE_CEIL)
115
+ return mean.tolist(), variance.tolist()
116
+
117
+ mean = arr / norm
118
+
119
+ abs_vals = np.abs(mean)
120
+ max_abs = float(np.max(abs_vals))
121
+ if max_abs < 1e-12:
122
+ max_abs = 1.0
123
+
124
+ normalised_signal = abs_vals / max_abs # in [0, 1]
125
+ variance = _VARIANCE_CEIL - (_VARIANCE_CEIL - _VARIANCE_FLOOR) * normalised_signal
126
+ variance = np.clip(variance, _VARIANCE_FLOOR, _VARIANCE_CEIL)
127
+
128
+ return mean.tolist(), variance.tolist()
129
+
130
+ # ------------------------------------------------------------------
131
+ # Geodesic distance
132
+ # ------------------------------------------------------------------
133
+
134
+ def distance(
135
+ self,
136
+ mean_a: list[float],
137
+ var_a: list[float],
138
+ mean_b: list[float],
139
+ var_b: list[float],
140
+ ) -> float:
141
+ """Exact Fisher-Rao geodesic distance between diagonal Gaussians.
142
+
143
+ Uses Atkinson & Mitchell (1981) per-component closed form with
144
+ Pinele et al. (2020) product-manifold decomposition.
145
+
146
+ Args:
147
+ mean_a: Mean of distribution A.
148
+ var_a: Per-dimension variance of A (strictly positive).
149
+ mean_b: Mean of distribution B.
150
+ var_b: Per-dimension variance of B (strictly positive).
151
+
152
+ Returns:
153
+ Non-negative geodesic distance.
154
+
155
+ Raises:
156
+ ValueError: On NaN, non-positive variance, or length mismatch.
157
+ """
158
+ mu1 = np.asarray(mean_a, dtype=np.float64)
159
+ sig1 = np.asarray(var_a, dtype=np.float64)
160
+ mu2 = np.asarray(mean_b, dtype=np.float64)
161
+ sig2 = np.asarray(var_b, dtype=np.float64)
162
+
163
+ _validate(mu1, sig1, mu2, sig2)
164
+
165
+ # Per-component: delta_i = ((mu1-mu2)^2 + 2*(sigma1-sigma2)^2) / (4*s1*s2)
166
+ mu_diff_sq = (mu1 - mu2) ** 2
167
+ sig_diff_sq = (sig1 - sig2) ** 2
168
+ product = sig1 * sig2
169
+
170
+ delta = (mu_diff_sq + 2.0 * sig_diff_sq) / (4.0 * product)
171
+
172
+ # Per-component squared distance: 2 * arccosh(1 + delta_i)^2
173
+ acosh_vals = _stable_arccosh_1p_vec(delta)
174
+ total_dist_sq = float(np.sum(2.0 * acosh_vals ** 2))
175
+
176
+ return math.sqrt(max(total_dist_sq, 0.0))
177
+
178
+ # ------------------------------------------------------------------
179
+ # Similarity (exponential kernel)
180
+ # ------------------------------------------------------------------
181
+
182
+ def similarity(
183
+ self,
184
+ mean_a: list[float],
185
+ var_a: list[float],
186
+ mean_b: list[float],
187
+ var_b: list[float],
188
+ ) -> float:
189
+ """Convert Fisher-Rao distance to similarity in [0, 1].
190
+
191
+ sim = exp(-distance / temperature)
192
+
193
+ Args:
194
+ mean_a: Mean of distribution A.
195
+ var_a: Per-dimension variance of A.
196
+ mean_b: Mean of distribution B.
197
+ var_b: Per-dimension variance of B.
198
+
199
+ Returns:
200
+ Similarity in [0, 1]. 1 = identical, 0 = maximally different.
201
+ """
202
+ d = self.distance(mean_a, var_a, mean_b, var_b)
203
+ return float(np.exp(-d / self.temperature))
204
+
205
+ # ------------------------------------------------------------------
206
+ # Bayesian variance update (THE key V1 fix)
207
+ # ------------------------------------------------------------------
208
+
209
+ def bayesian_update(
210
+ self,
211
+ old_var: list[float],
212
+ observation_var: list[float],
213
+ ) -> list[float]:
214
+ """Bayesian precision-additive variance update.
215
+
216
+ Each observation tightens the posterior variance:
217
+
218
+ 1/new_var_i = 1/old_var_i + 1/obs_var_i
219
+
220
+ This is the standard conjugate update for a Gaussian likelihood
221
+ with known mean and unknown precision. After *k* observations
222
+ with identical variance sigma^2:
223
+
224
+ var_k = sigma^2 / k
225
+
226
+ So variance shrinks as 1/k --- giving well-confirmed memories
227
+ significantly tighter distributions and higher Fisher similarity
228
+ to matching queries.
229
+
230
+ Args:
231
+ old_var: Current per-dimension variance.
232
+ observation_var: Variance of the new observation (derived from
233
+ the embedding of the confirming content).
234
+
235
+ Returns:
236
+ Updated variance (strictly within [FLOOR, CEIL]).
237
+ """
238
+ old = np.asarray(old_var, dtype=np.float64)
239
+ obs = np.asarray(observation_var, dtype=np.float64)
240
+
241
+ if old.shape != obs.shape:
242
+ raise ValueError(
243
+ f"Variance shape mismatch: {old.shape} vs {obs.shape}"
244
+ )
245
+
246
+ # Clamp inputs to valid range before update
247
+ old = np.clip(old, _VARIANCE_FLOOR, _VARIANCE_CEIL)
248
+ obs = np.clip(obs, _VARIANCE_FLOOR, _VARIANCE_CEIL)
249
+
250
+ # Precision-additive update: 1/new = 1/old + 1/obs
251
+ new_precision = (1.0 / old) + (1.0 / obs)
252
+ new_var = 1.0 / new_precision
253
+
254
+ new_var = np.clip(new_var, _VARIANCE_FLOOR, _VARIANCE_CEIL)
255
+ return new_var.tolist()
256
+
257
+ # ------------------------------------------------------------------
258
+ # Adaptive temperature
259
+ # ------------------------------------------------------------------
260
+
261
+ def adaptive_temperature(
262
+ self,
263
+ variances: list[list[float]],
264
+ ) -> float:
265
+ """Compute data-driven temperature from corpus variance statistics.
266
+
267
+ Instead of a fixed temperature, adapt to the actual spread of
268
+ variances in the memory store. High average variance (uncertain
269
+ corpus) -> higher temperature (softer discrimination). Low average
270
+ variance (well-confirmed corpus) -> lower temperature (sharper).
271
+
272
+ Formula:
273
+ T = base_T * (1 + avg_variance) / 2
274
+
275
+ This ensures T stays close to base_T when avg_variance ~ 1.0
276
+ (the typical midpoint) and scales proportionally.
277
+
278
+ Args:
279
+ variances: List of per-fact variance vectors.
280
+
281
+ Returns:
282
+ Adapted temperature (always positive).
283
+ """
284
+ if not variances:
285
+ return self.temperature
286
+
287
+ all_vars = np.array(variances, dtype=np.float64)
288
+ avg = float(np.mean(all_vars))
289
+
290
+ adapted = self.temperature * (1.0 + avg) / 2.0
291
+ return max(adapted, 0.1) # floor to prevent division issues
292
+
293
+
294
+ # ---------------------------------------------------------------------------
295
+ # Validation
296
+ # ---------------------------------------------------------------------------
297
+
298
+ def _validate(
299
+ mu1: np.ndarray,
300
+ sig1: np.ndarray,
301
+ mu2: np.ndarray,
302
+ sig2: np.ndarray,
303
+ ) -> None:
304
+ """Validate Fisher metric inputs."""
305
+ for name, arr in [("mu1", mu1), ("sig1", sig1), ("mu2", mu2), ("sig2", sig2)]:
306
+ if np.any(np.isnan(arr)):
307
+ raise ValueError(f"{name} contains NaN")
308
+
309
+ if mu1.shape != mu2.shape:
310
+ raise ValueError(f"Mean shape mismatch: {mu1.shape} vs {mu2.shape}")
311
+ if sig1.shape != sig2.shape:
312
+ raise ValueError(f"Sigma shape mismatch: {sig1.shape} vs {sig2.shape}")
313
+ if mu1.shape[0] != sig1.shape[0]:
314
+ raise ValueError(
315
+ f"Mean/sigma length mismatch: {mu1.shape[0]} vs {sig1.shape[0]}"
316
+ )
317
+
318
+ if np.any(sig1 <= 0):
319
+ raise ValueError("sig1 must be strictly positive")
320
+ if np.any(sig2 <= 0):
321
+ raise ValueError("sig2 must be strictly positive")
322
+
323
+
324
+ # ---------------------------------------------------------------------------
325
+ # Numerically stable arccosh
326
+ # ---------------------------------------------------------------------------
327
+
328
+ def _stable_arccosh_1p_vec(delta: np.ndarray) -> np.ndarray:
329
+ """Vectorised stable arccosh(1 + delta).
330
+
331
+ For small delta, uses Taylor expansion to avoid catastrophic
332
+ cancellation: arccosh(1+d) ~ sqrt(2d) * (1 - d/12).
333
+ For larger delta, uses the identity:
334
+ arccosh(1+d) = log(1 + d + sqrt(d*(d+2))).
335
+
336
+ Args:
337
+ delta: Non-negative array.
338
+
339
+ Returns:
340
+ arccosh(1 + delta) element-wise.
341
+ """
342
+ delta = np.maximum(delta, 0.0)
343
+ result = np.empty_like(delta)
344
+
345
+ small = delta < _SMALL_DELTA_THRESHOLD
346
+ large = ~small
347
+
348
+ if np.any(small):
349
+ d_s = delta[small]
350
+ result[small] = np.sqrt(2.0 * d_s) * (1.0 - d_s / 12.0)
351
+
352
+ if np.any(large):
353
+ d_l = delta[large]
354
+ result[large] = np.log1p(d_l + np.sqrt(d_l * (d_l + 2.0)))
355
+
356
+ return result