superlocalmemory 3.6.23 → 3.7.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 (301) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +271 -71
  3. package/bin/slm-npm +43 -89
  4. package/ide/configs/antigravity-mcp.json +2 -2
  5. package/ide/configs/chatgpt-desktop-mcp.json +1 -1
  6. package/ide/configs/claude-desktop-mcp.json +2 -2
  7. package/ide/configs/windsurf-mcp.json +2 -2
  8. package/ide/hooks/context-hook.js +6 -2
  9. package/ide/hooks/post-recall-hook.js +7 -3
  10. package/ide/hooks/tool-event-hook.sh +2 -1
  11. package/package.json +18 -10
  12. package/plugin/.claude-plugin/plugin.json +1 -1
  13. package/plugin/_GENERATED.md +1 -1
  14. package/plugin/agents/slm-memory-advisor.md +1 -1
  15. package/plugin/requirements.txt +1 -1
  16. package/plugin/skills/slm-session/SKILL.md +1 -1
  17. package/plugin-src/rules/AGENTS.md +1 -1
  18. package/pyproject.toml +40 -8
  19. package/scripts/postinstall-interactive.js +17 -94
  20. package/scripts/postinstall.js +185 -258
  21. package/scripts/preuninstall.js +9 -50
  22. package/src/superlocalmemory/__init__.py +2 -2
  23. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  24. package/src/superlocalmemory/attribution/signer.py +34 -19
  25. package/src/superlocalmemory/attribution/watermark.py +1 -1
  26. package/src/superlocalmemory/cli/_lazy_init.py +3 -5
  27. package/src/superlocalmemory/cli/commands.py +453 -191
  28. package/src/superlocalmemory/cli/context_commands.py +5 -4
  29. package/src/superlocalmemory/cli/daemon.py +282 -187
  30. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  31. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  32. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  33. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  34. package/src/superlocalmemory/cli/main.py +128 -31
  35. package/src/superlocalmemory/cli/pending_store.py +54 -38
  36. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  37. package/src/superlocalmemory/cli/service_installer.py +57 -52
  38. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  39. package/src/superlocalmemory/cli/version_banner.py +2 -1
  40. package/src/superlocalmemory/code_graph/config.py +3 -1
  41. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  42. package/src/superlocalmemory/core/config.py +65 -20
  43. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  44. package/src/superlocalmemory/core/context_cache.py +56 -8
  45. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  46. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  47. package/src/superlocalmemory/core/embeddings.py +54 -18
  48. package/src/superlocalmemory/core/engine.py +150 -104
  49. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  50. package/src/superlocalmemory/core/engine_wiring.py +2 -0
  51. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  52. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  53. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  54. package/src/superlocalmemory/core/health_monitor.py +4 -2
  55. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  56. package/src/superlocalmemory/core/injection.py +69 -18
  57. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  58. package/src/superlocalmemory/core/maintenance.py +1 -1
  59. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  60. package/src/superlocalmemory/core/mutations.py +143 -0
  61. package/src/superlocalmemory/core/platform_utils.py +7 -4
  62. package/src/superlocalmemory/core/ram_lock.py +16 -5
  63. package/src/superlocalmemory/core/rate_limit.py +1 -1
  64. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  65. package/src/superlocalmemory/core/recall_worker.py +76 -59
  66. package/src/superlocalmemory/core/registry.py +1 -1
  67. package/src/superlocalmemory/core/scale_engine.py +293 -0
  68. package/src/superlocalmemory/core/score_contract.py +62 -0
  69. package/src/superlocalmemory/core/security_primitives.py +3 -1
  70. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  71. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  72. package/src/superlocalmemory/core/tier_manager.py +32 -20
  73. package/src/superlocalmemory/core/worker_pool.py +13 -4
  74. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  75. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  76. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  77. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  78. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  79. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  80. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  81. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  82. package/src/superlocalmemory/encoding/foresight.py +2 -0
  83. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  84. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  85. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  86. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  87. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  88. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  89. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  90. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  91. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  92. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  93. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  94. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  95. package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
  96. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  97. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  98. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  99. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  100. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  101. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  102. package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
  103. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  104. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  105. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  106. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  107. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  108. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  109. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  110. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  111. package/src/superlocalmemory/infra/backup.py +7 -5
  112. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  113. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  114. package/src/superlocalmemory/infra/data_root.py +199 -0
  115. package/src/superlocalmemory/infra/event_bus.py +3 -1
  116. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  117. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  118. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  119. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  120. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  121. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  122. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  123. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  124. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  125. package/src/superlocalmemory/learning/ensemble.py +11 -0
  126. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  127. package/src/superlocalmemory/learning/feedback.py +1 -1
  128. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  129. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  130. package/src/superlocalmemory/learning/ranker.py +4 -1
  131. package/src/superlocalmemory/learning/source_quality.py +1 -1
  132. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  133. package/src/superlocalmemory/llm/backbone.py +1 -1
  134. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  135. package/src/superlocalmemory/math/fisher.py +1 -1
  136. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  137. package/src/superlocalmemory/math/hopfield.py +1 -1
  138. package/src/superlocalmemory/math/langevin.py +1 -1
  139. package/src/superlocalmemory/math/polar_quant.py +3 -4
  140. package/src/superlocalmemory/math/qjl.py +1 -1
  141. package/src/superlocalmemory/math/sheaf.py +1 -1
  142. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  143. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  144. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  145. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  146. package/src/superlocalmemory/mcp/server.py +39 -13
  147. package/src/superlocalmemory/mcp/shared.py +69 -3
  148. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  149. package/src/superlocalmemory/mcp/tools_core.py +128 -29
  150. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  151. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  152. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  153. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  154. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  155. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  156. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  157. package/src/superlocalmemory/mesh/broker.py +2 -0
  158. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  159. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  160. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  161. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  162. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  163. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  164. package/src/superlocalmemory/optimize/config/store.py +6 -4
  165. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  166. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  167. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  168. package/src/superlocalmemory/optimize/storage/db.py +12 -12
  169. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  170. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  171. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  172. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  173. package/src/superlocalmemory/retrieval/engine.py +169 -79
  174. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  175. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  176. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  177. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  178. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  179. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  180. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  181. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  182. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  183. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  184. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  185. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  186. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  187. package/src/superlocalmemory/server/api.py +10 -7
  188. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  189. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  190. package/src/superlocalmemory/server/route_mutations.py +84 -0
  191. package/src/superlocalmemory/server/routes/agents.py +8 -6
  192. package/src/superlocalmemory/server/routes/brain.py +14 -12
  193. package/src/superlocalmemory/server/routes/chat.py +29 -12
  194. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  195. package/src/superlocalmemory/server/routes/helpers.py +8 -63
  196. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  197. package/src/superlocalmemory/server/routes/memories.py +104 -43
  198. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  199. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  200. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  201. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  202. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  203. package/src/superlocalmemory/server/security_middleware.py +1 -1
  204. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  205. package/src/superlocalmemory/server/write_identity.py +147 -0
  206. package/src/superlocalmemory/storage/access_log.py +4 -3
  207. package/src/superlocalmemory/storage/database.py +118 -25
  208. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  209. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  210. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  211. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  212. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  213. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  214. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  215. package/src/superlocalmemory/storage/models.py +16 -0
  216. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  217. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  218. package/src/superlocalmemory/ui/favicon.svg +5 -0
  219. package/src/superlocalmemory/ui/index.html +1 -0
  220. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  221. package/src/superlocalmemory/ui/js/core.js +49 -8
  222. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  223. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  224. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  225. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  226. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  227. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  228. package/src/superlocalmemory/ui/js/settings.js +4 -2
  229. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  230. package/bin/slm +0 -59
  231. package/bin/slm.bat +0 -77
  232. package/bin/slm.cmd +0 -5
  233. package/ide/integrations/langchain/README.md +0 -106
  234. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  235. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  236. package/ide/integrations/langchain/pyproject.toml +0 -38
  237. package/ide/integrations/langchain/tests/__init__.py +0 -3
  238. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  239. package/ide/integrations/langchain/tests/test_security.py +0 -117
  240. package/ide/integrations/llamaindex/README.md +0 -81
  241. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  242. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  243. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  244. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  245. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  246. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  247. package/plugin-src/.mcp.json +0 -12
  248. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  249. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  250. package/plugin-src/hooks/.gitkeep +0 -0
  251. package/plugin-src/hooks/hooks.json +0 -23
  252. package/plugin-src/manifest.json +0 -25
  253. package/plugin-src/requirements.txt +0 -1
  254. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  255. package/plugin-src/scripts/ensure-venv.bat +0 -122
  256. package/plugin-src/scripts/ensure-venv.sh +0 -105
  257. package/plugin-src/scripts/slm-launch +0 -15
  258. package/plugin-src/scripts/slm-launch.bat +0 -17
  259. package/plugin-src/settings.json +0 -16
  260. package/plugin-src/skills/slm-cache/SKILL.md +0 -140
  261. package/plugin-src/skills/slm-compress/SKILL.md +0 -143
  262. package/plugin-src/skills/slm-graph/SKILL.md +0 -300
  263. package/plugin-src/skills/slm-recall/SKILL.md +0 -204
  264. package/plugin-src/skills/slm-remember/SKILL.md +0 -194
  265. package/plugin-src/skills/slm-session/SKILL.md +0 -207
  266. package/plugin-src/skills/slm-status/SKILL.md +0 -149
  267. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  268. package/scripts/_savings_math.py +0 -270
  269. package/scripts/build-dmg.sh +0 -417
  270. package/scripts/build-plugin.js +0 -742
  271. package/scripts/build-slm-hook.ps1 +0 -40
  272. package/scripts/build-slm-hook.sh +0 -45
  273. package/scripts/build_entry.py +0 -452
  274. package/scripts/ci/stage5b_gate.sh +0 -50
  275. package/scripts/dogfood_savings.py +0 -490
  276. package/scripts/generate-thumbnails.py +0 -218
  277. package/scripts/install-skills.ps1 +0 -4
  278. package/scripts/install-skills.sh +0 -5
  279. package/scripts/install.ps1 +0 -701
  280. package/scripts/install.sh +0 -1015
  281. package/scripts/postinstall_binary.js +0 -287
  282. package/scripts/prepack.js +0 -33
  283. package/scripts/release_manifest.py +0 -273
  284. package/scripts/slm-hook.spec +0 -56
  285. package/scripts/start-dashboard.ps1 +0 -52
  286. package/scripts/start-dashboard.sh +0 -41
  287. package/scripts/sync-wiki.ps1 +0 -127
  288. package/scripts/sync-wiki.sh +0 -82
  289. package/scripts/test-dmg.sh +0 -161
  290. package/scripts/test-npm-package.ps1 +0 -252
  291. package/scripts/test-npm-package.sh +0 -207
  292. package/scripts/verify-install.ps1 +0 -294
  293. package/scripts/verify-install.sh +0 -266
  294. package/scripts/verify-v27.ps1 +0 -301
  295. package/scripts/verify-v27.sh +0 -233
  296. package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
  297. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  298. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  299. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  300. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  301. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -22,7 +22,7 @@ Key patterns implemented:
22
22
  - Narrative fact extraction in LLM modes (self-contained, context-rich)
23
23
 
24
24
  Part of Qualixar | Author: Varun Pratap Bhardwaj
25
- License: Elastic-2.0
25
+ License: AGPL-3.0-or-later
26
26
  """
27
27
 
28
28
  from __future__ import annotations
@@ -73,6 +73,8 @@ def foresight_to_temporal_events(
73
73
  for eid in entity_ids:
74
74
  event = TemporalEvent(
75
75
  profile_id=profile_id,
76
+ scope=fact.scope,
77
+ shared_with=fact.shared_with,
76
78
  entity_id=eid,
77
79
  fact_id=fact.fact_id,
78
80
  observation_date=fact.observation_date,
@@ -12,7 +12,7 @@ Edge types: ENTITY (shared entity, weight 1.0), TEMPORAL (exp-decay,
12
12
  weight 0.8). CONTRADICTION exposed for external Sheaf module (Wave 4).
13
13
 
14
14
  Part of Qualixar | Author: Varun Pratap Bhardwaj
15
- License: Elastic-2.0
15
+ License: AGPL-3.0-or-later
16
16
  """
17
17
  from __future__ import annotations
18
18
 
@@ -306,6 +306,8 @@ class TemporalParser:
306
306
 
307
307
  return TemporalEvent(
308
308
  profile_id=fact.profile_id,
309
+ scope=fact.scope,
310
+ shared_with=fact.shared_with,
309
311
  entity_id=entity_id,
310
312
  fact_id=fact.fact_id,
311
313
  observation_date=obs_date,
@@ -0,0 +1,13 @@
1
+ """Evaluation artifacts that are isolated from production retrieval paths."""
2
+
3
+ from superlocalmemory.evaluation.calibration import (
4
+ CalibrationArtifactError,
5
+ build_calibration_report,
6
+ compute_calibration_metrics,
7
+ )
8
+
9
+ __all__ = [
10
+ "CalibrationArtifactError",
11
+ "build_calibration_report",
12
+ "compute_calibration_metrics",
13
+ ]
@@ -0,0 +1,308 @@
1
+ """Held-out answer-probability calibration report tooling.
2
+
3
+ Raw retrieval scores are ranking utilities. They are retained for distribution
4
+ diagnostics but are never passed to Brier, ECE, reliability, or selective-risk
5
+ calculations. Those metrics require an explicitly supplied probability from a
6
+ separately fitted calibrator and a disjoint held-out evaluation artifact.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import math
14
+ from collections.abc import Iterable, Sequence
15
+ from typing import Any
16
+
17
+ INPUT_SCHEMA = "superlocalmemory.calibration-input/v1"
18
+ REPORT_SCHEMA = "superlocalmemory.calibration-report/v1"
19
+ MIN_HELD_OUT_EXAMPLES = 100
20
+ DEFAULT_THRESHOLDS = (0.0, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95)
21
+ _RAW_AS_PROBABILITY_METHODS = {
22
+ "identity",
23
+ "raw",
24
+ "raw_score",
25
+ "raw_retrieval_score",
26
+ "none",
27
+ }
28
+
29
+
30
+ class CalibrationArtifactError(ValueError):
31
+ """The input cannot support a truthful held-out calibration report."""
32
+
33
+
34
+ def _canonical_sha256(value: object) -> str:
35
+ payload = json.dumps(
36
+ value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
37
+ ).encode("utf-8")
38
+ return hashlib.sha256(payload).hexdigest()
39
+
40
+
41
+ def _finite_number(value: object, *, field: str) -> float:
42
+ if isinstance(value, bool):
43
+ raise CalibrationArtifactError(f"{field} must be a finite number")
44
+ try:
45
+ number = float(value)
46
+ except (TypeError, ValueError) as exc:
47
+ raise CalibrationArtifactError(f"{field} must be a finite number") from exc
48
+ if not math.isfinite(number):
49
+ raise CalibrationArtifactError(f"{field} must be a finite number")
50
+ return number
51
+
52
+
53
+ def _validated_rows(examples: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
54
+ validated: list[dict[str, Any]] = []
55
+ seen_ids: set[str] = set()
56
+ for index, example in enumerate(examples):
57
+ if not isinstance(example, dict):
58
+ raise CalibrationArtifactError(f"examples[{index}] must be an object")
59
+ example_id = str(example.get("example_id", "")).strip()
60
+ if not example_id:
61
+ raise CalibrationArtifactError(f"examples[{index}].example_id is required")
62
+ if example_id in seen_ids:
63
+ raise CalibrationArtifactError("example_id values must be unique")
64
+ seen_ids.add(example_id)
65
+
66
+ if "predicted_probability" not in example:
67
+ raise CalibrationArtifactError(
68
+ "predicted_probability is required; raw retrieval scores are not probabilities"
69
+ )
70
+ probability = _finite_number(
71
+ example["predicted_probability"], field="predicted_probability"
72
+ )
73
+ if probability < 0.0 or probability > 1.0:
74
+ raise CalibrationArtifactError(
75
+ "predicted_probability must be between 0 and 1"
76
+ )
77
+
78
+ label = example.get("label")
79
+ if isinstance(label, bool):
80
+ label = int(label)
81
+ if label not in (0, 1):
82
+ raise CalibrationArtifactError("label must be binary (0 or 1)")
83
+ raw_score = _finite_number(
84
+ example.get("raw_retrieval_score"), field="raw_retrieval_score"
85
+ )
86
+ validated.append(
87
+ {
88
+ "example_id": example_id,
89
+ "raw_retrieval_score": raw_score,
90
+ "predicted_probability": probability,
91
+ "label": int(label),
92
+ }
93
+ )
94
+ return validated
95
+
96
+
97
+ def _round(value: float) -> float:
98
+ return round(value, 12)
99
+
100
+
101
+ def compute_calibration_metrics(
102
+ examples: Sequence[dict[str, Any]],
103
+ *,
104
+ bin_count: int = 10,
105
+ thresholds: Iterable[float] = DEFAULT_THRESHOLDS,
106
+ ) -> dict[str, Any]:
107
+ """Compute probability metrics while reporting raw scores separately."""
108
+ if not isinstance(bin_count, int) or isinstance(bin_count, bool) or bin_count < 2:
109
+ raise CalibrationArtifactError("bin_count must be an integer >= 2")
110
+ rows = _validated_rows(examples)
111
+ if not rows:
112
+ raise CalibrationArtifactError("at least one example is required")
113
+ total = len(rows)
114
+
115
+ brier = sum(
116
+ (row["predicted_probability"] - row["label"]) ** 2 for row in rows
117
+ ) / total
118
+
119
+ reliability: list[dict[str, Any]] = []
120
+ ece = 0.0
121
+ mce = 0.0
122
+ for index in range(bin_count):
123
+ lower = index / bin_count
124
+ upper = (index + 1) / bin_count
125
+ members = [
126
+ row for row in rows
127
+ if lower <= row["predicted_probability"]
128
+ and (row["predicted_probability"] < upper or index == bin_count - 1)
129
+ ]
130
+ if members:
131
+ mean_probability = sum(row["predicted_probability"] for row in members) / len(members)
132
+ observed_accuracy = sum(row["label"] for row in members) / len(members)
133
+ gap = abs(mean_probability - observed_accuracy)
134
+ ece += len(members) / total * gap
135
+ mce = max(mce, gap)
136
+ mean_value: float | None = _round(mean_probability)
137
+ accuracy_value: float | None = _round(observed_accuracy)
138
+ gap_value: float | None = _round(gap)
139
+ else:
140
+ mean_value = accuracy_value = gap_value = None
141
+ reliability.append(
142
+ {
143
+ "bin_index": index,
144
+ "lower_inclusive": _round(lower),
145
+ "upper_inclusive": index == bin_count - 1,
146
+ "upper": _round(upper),
147
+ "count": len(members),
148
+ "mean_probability": mean_value,
149
+ "observed_accuracy": accuracy_value,
150
+ "absolute_gap": gap_value,
151
+ }
152
+ )
153
+
154
+ selective: list[dict[str, Any]] = []
155
+ previous = -1.0
156
+ for raw_threshold in thresholds:
157
+ threshold = _finite_number(raw_threshold, field="selective threshold")
158
+ if threshold < 0.0 or threshold > 1.0:
159
+ raise CalibrationArtifactError("selective threshold must be between 0 and 1")
160
+ if threshold <= previous:
161
+ raise CalibrationArtifactError("selective thresholds must be strictly increasing")
162
+ previous = threshold
163
+ retained = [row for row in rows if row["predicted_probability"] >= threshold]
164
+ retained_count = len(retained)
165
+ coverage = retained_count / total
166
+ risk = (
167
+ sum(1 - row["label"] for row in retained) / retained_count
168
+ if retained_count
169
+ else None
170
+ )
171
+ selective.append(
172
+ {
173
+ "threshold": _round(threshold),
174
+ "retained": retained_count,
175
+ "coverage": _round(coverage),
176
+ "abstention_rate": _round(1.0 - coverage),
177
+ "selective_risk": _round(risk) if risk is not None else None,
178
+ }
179
+ )
180
+
181
+ raw_scores = [row["raw_retrieval_score"] for row in rows]
182
+ probabilities = [row["predicted_probability"] for row in rows]
183
+ return {
184
+ "example_count": total,
185
+ "brier_score": _round(brier),
186
+ "expected_calibration_error": _round(ece),
187
+ "maximum_calibration_error": _round(mce),
188
+ "reliability_bins": reliability,
189
+ "selective_risk": selective,
190
+ "probability_summary": {
191
+ "minimum": _round(min(probabilities)),
192
+ "maximum": _round(max(probabilities)),
193
+ "mean": _round(sum(probabilities) / total),
194
+ "unique_count": len(set(probabilities)),
195
+ "boundary_count": sum(value in (0.0, 1.0) for value in probabilities),
196
+ },
197
+ "raw_retrieval_score_summary": {
198
+ "minimum": _round(min(raw_scores)),
199
+ "maximum": _round(max(raw_scores)),
200
+ "mean": _round(sum(raw_scores) / total),
201
+ "unique_count": len(set(raw_scores)),
202
+ "boundary_count": sum(value in (0.0, 1.0) for value in raw_scores),
203
+ "used_in_probability_metrics": False,
204
+ },
205
+ }
206
+
207
+
208
+ def _require_sha256(value: object, *, field: str) -> str:
209
+ text = str(value or "").lower()
210
+ if len(text) != 64 or any(char not in "0123456789abcdef" for char in text):
211
+ raise CalibrationArtifactError(f"{field} must be a 64-character SHA-256")
212
+ return text
213
+
214
+
215
+ def build_calibration_report(
216
+ artifact: dict[str, Any],
217
+ *,
218
+ bin_count: int = 10,
219
+ thresholds: Iterable[float] = DEFAULT_THRESHOLDS,
220
+ ) -> dict[str, Any]:
221
+ """Validate a disjoint held-out artifact and build a non-activating report."""
222
+ if not isinstance(artifact, dict) or artifact.get("schema") != INPUT_SCHEMA:
223
+ raise CalibrationArtifactError(f"schema must be {INPUT_SCHEMA}")
224
+ calibrator = artifact.get("calibrator")
225
+ evaluation = artifact.get("evaluation")
226
+ examples = artifact.get("examples")
227
+ if not isinstance(calibrator, dict) or not isinstance(evaluation, dict):
228
+ raise CalibrationArtifactError("calibrator and evaluation metadata are required")
229
+ if not isinstance(examples, list):
230
+ raise CalibrationArtifactError("examples must be a list")
231
+
232
+ calibration_id = str(calibrator.get("calibration_id", "")).strip()
233
+ if not calibration_id:
234
+ raise CalibrationArtifactError("calibration_id is required")
235
+ method = str(calibrator.get("method", "")).strip().lower()
236
+ if not method or method in _RAW_AS_PROBABILITY_METHODS:
237
+ raise CalibrationArtifactError(
238
+ "a fitted calibrator is required; a raw score identity mapping is forbidden"
239
+ )
240
+ fit_dataset = str(calibrator.get("fit_dataset_id", "")).strip()
241
+ eval_dataset = str(evaluation.get("dataset_id", "")).strip()
242
+ if not fit_dataset or not eval_dataset or fit_dataset == eval_dataset:
243
+ raise CalibrationArtifactError(
244
+ "calibrator fit and held-out evaluation datasets must be distinct"
245
+ )
246
+ fit_split = str(calibrator.get("fit_split", "")).strip()
247
+ if not fit_split or fit_split == "held_out":
248
+ raise CalibrationArtifactError("fit_split must be distinct from held_out")
249
+ if evaluation.get("split") != "held_out":
250
+ raise CalibrationArtifactError("evaluation split must be held_out")
251
+ fit_artifact_hash = _require_sha256(
252
+ calibrator.get("fit_artifact_sha256"), field="fit_artifact_sha256"
253
+ )
254
+
255
+ validated = _validated_rows(examples)
256
+ if len(validated) < MIN_HELD_OUT_EXAMPLES:
257
+ raise CalibrationArtifactError(
258
+ f"held-out evaluation requires at least {MIN_HELD_OUT_EXAMPLES} examples"
259
+ )
260
+ expected_hash = _require_sha256(
261
+ evaluation.get("examples_sha256"), field="examples_sha256"
262
+ )
263
+ actual_hash = _canonical_sha256(examples)
264
+ if expected_hash != actual_hash:
265
+ raise CalibrationArtifactError("examples hash does not match held-out artifact")
266
+
267
+ metrics = compute_calibration_metrics(
268
+ validated, bin_count=bin_count, thresholds=thresholds
269
+ )
270
+ return {
271
+ "schema": REPORT_SCHEMA,
272
+ "evidence_status": "held_out_report",
273
+ "calibration_id": calibration_id,
274
+ "calibrator": {
275
+ "method": method,
276
+ "fit_dataset_id": fit_dataset,
277
+ "fit_split": fit_split,
278
+ "fit_artifact_sha256": fit_artifact_hash,
279
+ },
280
+ "evaluation": {
281
+ "dataset_id": eval_dataset,
282
+ "split": "held_out",
283
+ "examples_sha256": actual_hash,
284
+ },
285
+ "metrics": metrics,
286
+ "separation": {
287
+ "raw_score_field": "raw_retrieval_score",
288
+ "probability_metric_input": "predicted_probability",
289
+ "raw_retrieval_score_used_as_probability": False,
290
+ },
291
+ "runtime_calibration_status": "uncalibrated",
292
+ "runtime_activation_allowed": False,
293
+ "runtime_activation_reason": (
294
+ "This artifact is evaluation evidence only; production activation "
295
+ "requires a separately reviewed and frozen calibrator contract."
296
+ ),
297
+ "input_artifact_sha256": _canonical_sha256(artifact),
298
+ }
299
+
300
+
301
+ __all__ = [
302
+ "CalibrationArtifactError",
303
+ "INPUT_SCHEMA",
304
+ "MIN_HELD_OUT_EXAMPLES",
305
+ "REPORT_SCHEMA",
306
+ "build_calibration_report",
307
+ "compute_calibration_metrics",
308
+ ]
@@ -25,6 +25,7 @@ from datetime import datetime, timezone
25
25
  from pathlib import Path
26
26
  from typing import Optional
27
27
 
28
+ from superlocalmemory.infra.data_root import canonical_data_root
28
29
  from superlocalmemory.evolution.types import (
29
30
  EvolutionCandidate,
30
31
  EvolutionRecord,
@@ -128,7 +129,7 @@ class SkillEvolver:
128
129
  # at ~/.superlocalmemory so production callers pick it up
129
130
  # automatically. Tests inject their own.
130
131
  if budget is None:
131
- slm_home = Path.home() / ".superlocalmemory"
132
+ slm_home = canonical_data_root()
132
133
  budget = EvolutionBudget(
133
134
  profile_id=profile_id,
134
135
  learning_db=Path(self._db_path),