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,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