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,391 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3 — Configuration.
6
+
7
+ Unified configuration with Mode A/B/C capability matrix.
8
+ Clean — zero dead options, every config has a consumer.
9
+
10
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+
18
+ from superlocalmemory.storage.models import Mode
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Default Paths
23
+ # ---------------------------------------------------------------------------
24
+
25
+ DEFAULT_BASE_DIR = Path.home() / ".superlocalmemory"
26
+ DEFAULT_DB_NAME = "memory.db"
27
+ DEFAULT_PROFILES_FILE = "profiles.json"
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Embedding Config
32
+ # ---------------------------------------------------------------------------
33
+
34
+ @dataclass(frozen=True)
35
+ class EmbeddingConfig:
36
+ """Embedding model configuration per mode."""
37
+
38
+ model_name: str = "nomic-ai/nomic-embed-text-v1.5"
39
+ dimension: int = 768
40
+ # Azure / cloud settings (Mode C only)
41
+ api_endpoint: str = ""
42
+ api_key: str = ""
43
+ api_version: str = "2024-02-01"
44
+ deployment_name: str = ""
45
+
46
+ @property
47
+ def is_cloud(self) -> bool:
48
+ return bool(self.api_endpoint)
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # LLM Config
53
+ # ---------------------------------------------------------------------------
54
+
55
+ @dataclass(frozen=True)
56
+ class LLMConfig:
57
+ """LLM provider configuration per mode."""
58
+
59
+ provider: str = "" # "" = no LLM, "ollama", "azure", "openai", "anthropic"
60
+ model: str = "" # Model name/deployment
61
+ api_key: str = ""
62
+ api_base: str = ""
63
+ temperature: float = 0.0 # Deterministic by default
64
+ max_tokens: int = 4096
65
+ timeout_seconds: float = 60.0
66
+
67
+ @property
68
+ def is_available(self) -> bool:
69
+ return bool(self.provider)
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Channel Weights
74
+ # ---------------------------------------------------------------------------
75
+
76
+ @dataclass
77
+ class ChannelWeights:
78
+ """Retrieval channel weights — 4 channels, query-adaptive."""
79
+
80
+ # Entity-linked facts are high-precision matches that rank above BM25.
81
+ semantic: float = 1.2
82
+ bm25: float = 1.0
83
+ entity_graph: float = 1.3
84
+ temporal: float = 1.0
85
+
86
+ def as_dict(self) -> dict[str, float]:
87
+ return {
88
+ "semantic": self.semantic,
89
+ "bm25": self.bm25,
90
+ "entity_graph": self.entity_graph,
91
+ "temporal": self.temporal,
92
+ }
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Encoding Config
97
+ # ---------------------------------------------------------------------------
98
+
99
+ @dataclass
100
+ class EncodingConfig:
101
+ """Configuration for the encoding (memory creation) pipeline."""
102
+
103
+ # Fact extraction
104
+ chunk_size: int = 10 # Conversation turns per extraction chunk
105
+ max_facts_per_chunk: int = 5 # Max facts extracted per chunk
106
+ min_fact_confidence: float = 0.3
107
+
108
+ # Entity resolution
109
+ entity_similarity_threshold: float = 0.85
110
+ max_entity_candidates: int = 10
111
+
112
+ # Graph construction
113
+ semantic_edge_top_k: int = 5 # Top-K semantic edges per new fact
114
+ temporal_edge_window_hours: int = 168 # 1 week
115
+
116
+ # Consolidation
117
+ consolidation_similarity_threshold: float = 0.85
118
+ max_consolidation_candidates: int = 5
119
+
120
+ # Entropy gate
121
+ entropy_threshold: float = 0.95
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Retrieval Config
126
+ # ---------------------------------------------------------------------------
127
+
128
+ @dataclass
129
+ class RetrievalConfig:
130
+ """Configuration for the retrieval (recall) pipeline."""
131
+
132
+ # Fusion
133
+ rrf_k: int = 60 # RRF smoothing constant (D116: k=60 for diversity)
134
+ top_k: int = 20 # Final results to return
135
+
136
+ # Per-channel
137
+ semantic_top_k: int = 50 # ANN pre-filter candidates
138
+ bm25_top_k: int = 50
139
+ entity_graph_max_hops: int = 3
140
+ temporal_proximity_days: int = 30
141
+
142
+ # Reranking
143
+ use_cross_encoder: bool = True
144
+ cross_encoder_model: str = "BAAI/bge-reranker-v2-m3"
145
+
146
+ # Agentic (Mode C only)
147
+ agentic_max_rounds: int = 3
148
+ agentic_confidence_threshold: float = 0.3
149
+
150
+ # Spreading activation
151
+ spreading_activation_decay: float = 0.7
152
+ spreading_activation_threshold: float = 0.1
153
+
154
+ # Trust weighting — apply Bayesian trust scores to retrieval ranking.
155
+ # When enabled, each fact's score is multiplied by a trust weight in [0.5, 1.5].
156
+ # Low-trust facts are demoted; high-trust facts are promoted.
157
+ # Default trust = 1.0 (no effect when no trust data exists).
158
+ use_trust_weighting: bool = True
159
+
160
+ # Ablation channel control for experiments.
161
+ # List of channel names to SKIP during retrieval (e.g., ["bm25", "entity_graph"]).
162
+ # Used by s19_runner for ablation experiments. Empty = all channels active.
163
+ disabled_channels: list[str] = field(default_factory=list)
164
+
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # Math Config
168
+ # ---------------------------------------------------------------------------
169
+
170
+ @dataclass
171
+ class MathConfig:
172
+ """Configuration for mathematical layers."""
173
+
174
+ # Fisher-Rao
175
+ fisher_temperature: float = 15.0
176
+ fisher_bayesian_update: bool = True
177
+ # "simplified" = local Mahalanobis-like (fast, existing behaviour)
178
+ # "full" = Atkinson-Mitchell geodesic from FisherRaoMetric class
179
+ fisher_mode: str = "simplified"
180
+
181
+ # Langevin
182
+ langevin_dt: float = 0.005
183
+ langevin_temperature: float = 0.3
184
+ langevin_persist_positions: bool = True
185
+ langevin_weight_range: tuple[float, float] = (0.0, 1.0)
186
+
187
+ # Hopfield
188
+
189
+ # Sheaf (at encoding time, NOT retrieval)
190
+ sheaf_at_encoding: bool = True
191
+ sheaf_contradiction_threshold: float = 0.45
192
+ # Max edges to check per fact during sheaf consistency.
193
+ # At 18K+ edges, coboundary computation becomes O(N*dim^2) and hangs.
194
+ # Facts with more edges than this skip sheaf check (still get contradiction
195
+ # detection via consolidator UPDATE/SUPERSEDE path).
196
+ sheaf_max_edges_per_check: int = 200
197
+
198
+ # Rate-Distortion (production only, disabled for benchmarks)
199
+
200
+
201
+ # ---------------------------------------------------------------------------
202
+ # Master Config
203
+ # ---------------------------------------------------------------------------
204
+
205
+ @dataclass
206
+ class SLMConfig:
207
+ """Master configuration for SuperLocalMemory V3.
208
+
209
+ Create via SLMConfig.for_mode(Mode.A) for mode-specific defaults.
210
+ """
211
+
212
+ mode: Mode = Mode.A
213
+ base_dir: Path = DEFAULT_BASE_DIR
214
+ db_path: Path | None = None # Computed from base_dir if None
215
+ active_profile: str = "default"
216
+
217
+ embedding: EmbeddingConfig = field(default_factory=EmbeddingConfig)
218
+ llm: LLMConfig = field(default_factory=LLMConfig)
219
+ channel_weights: ChannelWeights = field(default_factory=ChannelWeights)
220
+ encoding: EncodingConfig = field(default_factory=EncodingConfig)
221
+ retrieval: RetrievalConfig = field(default_factory=RetrievalConfig)
222
+ math: MathConfig = field(default_factory=MathConfig)
223
+
224
+ def __post_init__(self) -> None:
225
+ if self.db_path is None:
226
+ self.db_path = self.base_dir / DEFAULT_DB_NAME
227
+
228
+ @classmethod
229
+ def load(cls, config_path: Path | None = None) -> SLMConfig:
230
+ """Load config from JSON file. Returns default Mode A if file doesn't exist."""
231
+ path = config_path or (DEFAULT_BASE_DIR / "config.json")
232
+ if not path.exists():
233
+ return cls.for_mode(Mode.A)
234
+ import json
235
+ data = json.loads(path.read_text())
236
+ mode = Mode(data.get("mode", "a"))
237
+ llm_data = data.get("llm", {})
238
+ config = cls.for_mode(
239
+ mode,
240
+ llm_provider=llm_data.get("provider", ""),
241
+ llm_model=llm_data.get("model", ""),
242
+ llm_api_key=llm_data.get("api_key", ""),
243
+ llm_api_base=llm_data.get("base_url", ""),
244
+ embedding_endpoint=data.get("embedding", {}).get("api_endpoint", ""),
245
+ embedding_key=data.get("embedding", {}).get("api_key", ""),
246
+ embedding_deployment=data.get("embedding", {}).get("deployment_name", ""),
247
+ )
248
+ config.active_profile = data.get("active_profile", "default")
249
+ return config
250
+
251
+ def save(self, config_path: Path | None = None) -> None:
252
+ """Save config to JSON file."""
253
+ import json
254
+ path = config_path or (self.base_dir / "config.json")
255
+ path.parent.mkdir(parents=True, exist_ok=True)
256
+ data = {
257
+ "mode": self.mode.value,
258
+ "active_profile": self.active_profile,
259
+ "llm": {
260
+ "provider": self.llm.provider,
261
+ "model": self.llm.model,
262
+ "api_key": self.llm.api_key,
263
+ "base_url": self.llm.api_base,
264
+ },
265
+ "embedding": {
266
+ "model_name": self.embedding.model_name,
267
+ "dimension": self.embedding.dimension,
268
+ "api_endpoint": self.embedding.api_endpoint,
269
+ "api_key": self.embedding.api_key,
270
+ "deployment_name": self.embedding.deployment_name,
271
+ },
272
+ }
273
+ path.write_text(json.dumps(data, indent=2))
274
+
275
+ @staticmethod
276
+ def provider_presets() -> dict[str, dict[str, str]]:
277
+ """Provider presets for setup wizard."""
278
+ return {
279
+ "openai": {
280
+ "base_url": "https://api.openai.com/v1",
281
+ "model": "gpt-4.1-mini",
282
+ "embedding_model": "text-embedding-3-large",
283
+ "env_key": "OPENAI_API_KEY",
284
+ },
285
+ "anthropic": {
286
+ "base_url": "https://api.anthropic.com",
287
+ "model": "claude-sonnet-4-6",
288
+ "embedding_model": "",
289
+ "env_key": "ANTHROPIC_API_KEY",
290
+ },
291
+ "ollama": {
292
+ "base_url": "http://localhost:11434",
293
+ "model": "llama3.2",
294
+ "embedding_model": "nomic-embed-text",
295
+ "env_key": "",
296
+ },
297
+ "openrouter": {
298
+ "base_url": "https://openrouter.ai/api/v1",
299
+ "model": "openai/gpt-4.1-mini",
300
+ "embedding_model": "",
301
+ "env_key": "OPENROUTER_API_KEY",
302
+ },
303
+ }
304
+
305
+ @classmethod
306
+ def default(cls) -> SLMConfig:
307
+ """Create default Mode A configuration."""
308
+ return cls.for_mode(Mode.A)
309
+
310
+ @classmethod
311
+ def for_mode(
312
+ cls,
313
+ mode: Mode,
314
+ base_dir: Path | None = None,
315
+ *,
316
+ llm_provider: str = "",
317
+ llm_model: str = "",
318
+ llm_api_key: str = "",
319
+ llm_api_base: str = "",
320
+ embedding_endpoint: str = "",
321
+ embedding_key: str = "",
322
+ embedding_deployment: str = "",
323
+ ) -> SLMConfig:
324
+ """Create config with mode-appropriate defaults."""
325
+ _base = base_dir or DEFAULT_BASE_DIR
326
+
327
+ if mode == Mode.A:
328
+ return cls(
329
+ mode=mode,
330
+ base_dir=_base,
331
+ embedding=EmbeddingConfig(
332
+ model_name="nomic-ai/nomic-embed-text-v1.5",
333
+ dimension=768,
334
+ ),
335
+ llm=LLMConfig(), # No LLM
336
+ retrieval=RetrievalConfig(
337
+ use_cross_encoder=True, # Hotpatch: CE ON for Mode A with bge-reranker
338
+ ),
339
+ math=MathConfig(
340
+ sheaf_contradiction_threshold=0.45, # 768d threshold
341
+ ),
342
+ )
343
+
344
+ if mode == Mode.B:
345
+ return cls(
346
+ mode=mode,
347
+ base_dir=_base,
348
+ embedding=EmbeddingConfig(
349
+ model_name="nomic-ai/nomic-embed-text-v1.5",
350
+ dimension=768,
351
+ ),
352
+ llm=LLMConfig(
353
+ provider=llm_provider or "ollama",
354
+ model=llm_model or "phi3:mini",
355
+ api_base=llm_api_base or "http://localhost:11434",
356
+ ),
357
+ retrieval=RetrievalConfig(use_cross_encoder=True),
358
+ )
359
+
360
+ # Mode C — FULL POWER, UNRESTRICTED
361
+ return cls(
362
+ mode=mode,
363
+ base_dir=_base,
364
+ embedding=EmbeddingConfig(
365
+ model_name="text-embedding-3-large",
366
+ dimension=3072,
367
+ api_endpoint=embedding_endpoint,
368
+ api_key=embedding_key,
369
+ deployment_name=embedding_deployment,
370
+ ),
371
+ llm=LLMConfig(
372
+ provider=llm_provider or "azure",
373
+ model=llm_model or "gpt-4.1-mini",
374
+ api_key=llm_api_key,
375
+ api_base=llm_api_base,
376
+ ),
377
+ channel_weights=ChannelWeights(
378
+ semantic=1.5,
379
+ bm25=1.2,
380
+ entity_graph=1.3,
381
+ temporal=1.0,
382
+ ),
383
+ retrieval=RetrievalConfig(
384
+ use_cross_encoder=True,
385
+ semantic_top_k=80,
386
+ agentic_max_rounds=2, # EverMemOS 2-round
387
+ ),
388
+ math=MathConfig(
389
+ sheaf_contradiction_threshold=0.65, # Higher for 3072d embeddings
390
+ ),
391
+ )
@@ -0,0 +1,293 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3 — Embedding Service.
6
+
7
+ Thread-safe, dimension-validated embedding with Fisher variance computation.
8
+ Supports local (768-dim nomic) and cloud (3072-dim) models with EXPLICIT errors
9
+ on dimension mismatch — NEVER silently falls back to a different dimension.
10
+
11
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import threading
18
+ import time
19
+ from typing import TYPE_CHECKING
20
+
21
+ import numpy as np
22
+
23
+ if TYPE_CHECKING:
24
+ from numpy.typing import NDArray
25
+
26
+ from superlocalmemory.core.config import EmbeddingConfig
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Fisher variance constants
32
+ # ---------------------------------------------------------------------------
33
+ _FISHER_VAR_MIN = 0.05
34
+ _FISHER_VAR_MAX = 2.0
35
+ _FISHER_VAR_RANGE = _FISHER_VAR_MAX - _FISHER_VAR_MIN # 1.95
36
+
37
+
38
+ class DimensionMismatchError(RuntimeError):
39
+ """Raised when the actual embedding dimension differs from config.
40
+
41
+ This is a HARD failure — V1 silently fell back to local embeddings
42
+ when Azure failed, changing dimension from 3072 to 768 mid-run.
43
+ We crash loudly instead.
44
+ """
45
+
46
+
47
+ class EmbeddingService:
48
+ """Thread-safe embedding service with strict dimension validation.
49
+
50
+ Lazy-loads the underlying model on first embed call.
51
+ Validates every output dimension against the configured expectation.
52
+ """
53
+
54
+ def __init__(self, config: EmbeddingConfig) -> None:
55
+ self._config = config
56
+ self._model: object | None = None
57
+ self._lock = threading.Lock()
58
+ self._loaded = False
59
+
60
+ # ------------------------------------------------------------------
61
+ # Public API
62
+ # ------------------------------------------------------------------
63
+
64
+ @property
65
+ def dimension(self) -> int:
66
+ """Expected embedding dimension (from config)."""
67
+ return self._config.dimension
68
+
69
+ def embed(self, text: str) -> list[float]:
70
+ """Embed a single text string.
71
+
72
+ Returns:
73
+ L2-normalized embedding of exactly ``self.dimension`` floats.
74
+
75
+ Raises:
76
+ ValueError: If text is empty.
77
+ DimensionMismatchError: If output dimension != config.
78
+ """
79
+ if not text or not text.strip():
80
+ raise ValueError("Cannot embed empty text")
81
+ vec = self._encode_single(text)
82
+ self._validate_dimension(vec)
83
+ return vec.tolist()
84
+
85
+ def embed_batch(self, texts: list[str]) -> list[list[float]]:
86
+ """Embed a batch of texts.
87
+
88
+ Returns:
89
+ List of L2-normalized embeddings, each ``self.dimension`` floats.
90
+
91
+ Raises:
92
+ ValueError: If any text is empty or list is empty.
93
+ DimensionMismatchError: If any output dimension != config.
94
+ """
95
+ if not texts:
96
+ raise ValueError("Cannot embed empty batch")
97
+ for i, t in enumerate(texts):
98
+ if not t or not t.strip():
99
+ raise ValueError(f"Text at index {i} is empty")
100
+
101
+ vectors = self._encode_batch(texts)
102
+ for vec in vectors:
103
+ self._validate_dimension(vec)
104
+ return [v.tolist() for v in vectors]
105
+
106
+ def compute_fisher_params(
107
+ self,
108
+ embedding: list[float],
109
+ ) -> tuple[list[float], list[float]]:
110
+ """Compute Fisher-Rao parameters from a raw embedding.
111
+
112
+ Variance is content-derived (NOT uniform). Dimensions with strong
113
+ signal (high absolute value) get LOW variance (high confidence).
114
+ Weak-signal dimensions get HIGH variance (uncertainty).
115
+
116
+ This heterogeneous variance is what gives Fisher-Rao metric
117
+ discriminative power beyond simple cosine similarity.
118
+
119
+ Args:
120
+ embedding: Raw embedding vector (already L2-normalized).
121
+
122
+ Returns:
123
+ (mean, variance) — both lists of ``self.dimension`` floats.
124
+ Variance values are clamped to [0.3, 2.0].
125
+ """
126
+ arr = np.asarray(embedding, dtype=np.float64)
127
+ norm = float(np.linalg.norm(arr))
128
+
129
+ if norm < 1e-10:
130
+ mean = np.zeros(len(arr), dtype=np.float64)
131
+ variance = np.full(len(arr), _FISHER_VAR_MAX, dtype=np.float64)
132
+ return mean.tolist(), variance.tolist()
133
+
134
+ mean = arr / norm
135
+
136
+ # Content-derived heterogeneous variance
137
+ abs_mean = np.abs(mean)
138
+ max_val = float(np.max(abs_mean)) + 1e-10
139
+ signal_strength = abs_mean / max_val # [0, 1]
140
+
141
+ # Inverse: strong signal -> low variance, weak -> high
142
+ variance = _FISHER_VAR_MAX - _FISHER_VAR_RANGE * signal_strength
143
+ variance = np.clip(variance, _FISHER_VAR_MIN, _FISHER_VAR_MAX)
144
+
145
+ return mean.tolist(), variance.tolist()
146
+
147
+ # ------------------------------------------------------------------
148
+ # Internals — model loading
149
+ # ------------------------------------------------------------------
150
+
151
+ def _ensure_loaded(self) -> None:
152
+ """Lazy-load the model on first use (thread-safe)."""
153
+ if self._loaded:
154
+ return
155
+ with self._lock:
156
+ if self._loaded:
157
+ return
158
+ if self._config.is_cloud:
159
+ # Cloud mode: no local model needed, validate config
160
+ if not self._config.api_endpoint or not self._config.api_key:
161
+ raise RuntimeError(
162
+ "Cloud embedding requires api_endpoint and api_key"
163
+ )
164
+ logger.info(
165
+ "EmbeddingService: cloud mode (%s, %d-dim)",
166
+ self._config.deployment_name,
167
+ self._config.dimension,
168
+ )
169
+ else:
170
+ self._load_local_model()
171
+ self._loaded = True
172
+
173
+ def _load_local_model(self) -> None:
174
+ """Load sentence-transformers model for local embedding."""
175
+ try:
176
+ from sentence_transformers import SentenceTransformer
177
+ except ImportError:
178
+ raise ImportError(
179
+ "sentence-transformers required: "
180
+ "pip install sentence-transformers"
181
+ )
182
+ model = SentenceTransformer(
183
+ self._config.model_name, trust_remote_code=False,
184
+ )
185
+ actual_dim = model.get_sentence_embedding_dimension()
186
+ if actual_dim != self._config.dimension:
187
+ raise DimensionMismatchError(
188
+ f"Model '{self._config.model_name}' produces {actual_dim}-dim "
189
+ f"embeddings but config expects {self._config.dimension}-dim"
190
+ )
191
+ self._model = model
192
+ logger.info(
193
+ "EmbeddingService: local model loaded (%s, %d-dim)",
194
+ self._config.model_name,
195
+ actual_dim,
196
+ )
197
+
198
+ # ------------------------------------------------------------------
199
+ # Internals — encoding
200
+ # ------------------------------------------------------------------
201
+
202
+ def _encode_single(self, text: str) -> NDArray[np.float32]:
203
+ """Encode one text. Dispatches to local or cloud."""
204
+ self._ensure_loaded()
205
+ if self._config.is_cloud:
206
+ return self._cloud_embed([text])[0]
207
+ return self._local_embed_batch([text])[0]
208
+
209
+ def _encode_batch(self, texts: list[str]) -> list[NDArray[np.float32]]:
210
+ """Encode a batch. Dispatches to local or cloud."""
211
+ self._ensure_loaded()
212
+ if self._config.is_cloud:
213
+ return self._cloud_embed(texts)
214
+ return self._local_embed_batch(texts)
215
+
216
+ def _local_embed_batch(
217
+ self,
218
+ texts: list[str],
219
+ ) -> list[NDArray[np.float32]]:
220
+ """Encode via local sentence-transformers (L2-normalized)."""
221
+ if self._model is None:
222
+ raise RuntimeError("Local model not loaded")
223
+ vecs = self._model.encode(texts, normalize_embeddings=True)
224
+ if isinstance(vecs, np.ndarray) and vecs.ndim == 2:
225
+ return [vecs[i] for i in range(vecs.shape[0])]
226
+ return [np.asarray(v, dtype=np.float32) for v in vecs]
227
+
228
+ def _cloud_embed(
229
+ self,
230
+ texts: list[str],
231
+ *,
232
+ max_retries: int = 3,
233
+ ) -> list[NDArray[np.float32]]:
234
+ """Encode via Azure OpenAI embedding API with retry logic.
235
+
236
+ Raises on failure — NEVER falls back to local model.
237
+ """
238
+ import httpx
239
+
240
+ url = (
241
+ f"{self._config.api_endpoint.rstrip('/')}/openai/deployments/"
242
+ f"{self._config.deployment_name}/embeddings"
243
+ f"?api-version={self._config.api_version}"
244
+ )
245
+ headers = {
246
+ "Content-Type": "application/json",
247
+ "api-key": self._config.api_key,
248
+ }
249
+ body = {"input": texts, "model": self._config.deployment_name}
250
+
251
+ last_error: Exception | None = None
252
+ for attempt in range(max_retries):
253
+ try:
254
+ with httpx.Client(timeout=httpx.Timeout(30.0)) as client:
255
+ resp = client.post(url, headers=headers, json=body)
256
+ resp.raise_for_status()
257
+ data = resp.json()
258
+ results: list[NDArray[np.float32]] = []
259
+ for item in sorted(data["data"], key=lambda d: d["index"]):
260
+ vec = np.asarray(item["embedding"], dtype=np.float32)
261
+ results.append(vec)
262
+ return results
263
+ except Exception as exc:
264
+ last_error = exc
265
+ wait = 2 ** attempt # 1s, 2s, 4s
266
+ logger.warning(
267
+ "Cloud embed attempt %d/%d failed: %s (retry in %ds)",
268
+ attempt + 1,
269
+ max_retries,
270
+ exc,
271
+ wait,
272
+ )
273
+ if attempt < max_retries - 1:
274
+ time.sleep(wait)
275
+
276
+ raise RuntimeError(
277
+ f"Cloud embedding failed after {max_retries} attempts: "
278
+ f"{last_error}"
279
+ )
280
+
281
+ # ------------------------------------------------------------------
282
+ # Validation
283
+ # ------------------------------------------------------------------
284
+
285
+ def _validate_dimension(self, vec: NDArray) -> None:
286
+ """Hard validation — crash on mismatch, never silently fall back."""
287
+ actual = len(vec)
288
+ if actual != self._config.dimension:
289
+ raise DimensionMismatchError(
290
+ f"Embedding dimension {actual} != "
291
+ f"expected {self._config.dimension}. "
292
+ f"This is a HARD failure — check your model/API config."
293
+ )