superlocalmemory 3.6.22 → 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 (303) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +275 -72
  3. package/bin/slm-npm +43 -89
  4. package/docs/pi-dev-integration.md +43 -0
  5. package/ide/configs/antigravity-mcp.json +2 -2
  6. package/ide/configs/chatgpt-desktop-mcp.json +1 -1
  7. package/ide/configs/claude-desktop-mcp.json +2 -2
  8. package/ide/configs/windsurf-mcp.json +2 -2
  9. package/ide/hooks/context-hook.js +6 -2
  10. package/ide/hooks/post-recall-hook.js +7 -3
  11. package/ide/hooks/tool-event-hook.sh +2 -1
  12. package/package.json +19 -10
  13. package/plugin/.claude-plugin/plugin.json +1 -1
  14. package/plugin/_GENERATED.md +1 -1
  15. package/plugin/agents/slm-memory-advisor.md +1 -1
  16. package/plugin/requirements.txt +1 -1
  17. package/plugin/skills/slm-session/SKILL.md +1 -1
  18. package/plugin-src/rules/AGENTS.md +1 -1
  19. package/pyproject.toml +40 -8
  20. package/scripts/postinstall-interactive.js +17 -94
  21. package/scripts/postinstall.js +185 -258
  22. package/scripts/preuninstall.js +9 -50
  23. package/src/superlocalmemory/__init__.py +2 -2
  24. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  25. package/src/superlocalmemory/attribution/signer.py +34 -19
  26. package/src/superlocalmemory/attribution/watermark.py +1 -1
  27. package/src/superlocalmemory/cli/_lazy_init.py +3 -5
  28. package/src/superlocalmemory/cli/commands.py +490 -195
  29. package/src/superlocalmemory/cli/context_commands.py +5 -4
  30. package/src/superlocalmemory/cli/daemon.py +282 -187
  31. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  32. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  33. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  34. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  35. package/src/superlocalmemory/cli/main.py +128 -31
  36. package/src/superlocalmemory/cli/pending_store.py +54 -38
  37. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  38. package/src/superlocalmemory/cli/service_installer.py +57 -52
  39. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  40. package/src/superlocalmemory/cli/version_banner.py +2 -1
  41. package/src/superlocalmemory/code_graph/config.py +3 -1
  42. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  43. package/src/superlocalmemory/core/config.py +65 -20
  44. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  45. package/src/superlocalmemory/core/context_cache.py +56 -8
  46. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  47. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  48. package/src/superlocalmemory/core/embeddings.py +54 -18
  49. package/src/superlocalmemory/core/engine.py +150 -104
  50. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  51. package/src/superlocalmemory/core/engine_wiring.py +2 -0
  52. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  53. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  54. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  55. package/src/superlocalmemory/core/health_monitor.py +4 -2
  56. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  57. package/src/superlocalmemory/core/injection.py +69 -18
  58. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  59. package/src/superlocalmemory/core/maintenance.py +23 -22
  60. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  61. package/src/superlocalmemory/core/mutations.py +143 -0
  62. package/src/superlocalmemory/core/platform_utils.py +7 -4
  63. package/src/superlocalmemory/core/ram_lock.py +16 -5
  64. package/src/superlocalmemory/core/rate_limit.py +1 -1
  65. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  66. package/src/superlocalmemory/core/recall_worker.py +76 -59
  67. package/src/superlocalmemory/core/registry.py +1 -1
  68. package/src/superlocalmemory/core/scale_engine.py +293 -0
  69. package/src/superlocalmemory/core/score_contract.py +62 -0
  70. package/src/superlocalmemory/core/security_primitives.py +3 -1
  71. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  72. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  73. package/src/superlocalmemory/core/tier_manager.py +32 -20
  74. package/src/superlocalmemory/core/worker_pool.py +13 -4
  75. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  76. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  77. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  78. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  79. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  80. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  81. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  82. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  83. package/src/superlocalmemory/encoding/foresight.py +2 -0
  84. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  85. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  86. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  87. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  88. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  89. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  90. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  91. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  92. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  93. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  94. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  95. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  96. package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
  97. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  98. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  99. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  100. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  101. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  102. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  103. package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
  104. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  105. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  106. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  107. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  108. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  109. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  110. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  111. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  112. package/src/superlocalmemory/infra/backup.py +7 -5
  113. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  114. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  115. package/src/superlocalmemory/infra/data_root.py +199 -0
  116. package/src/superlocalmemory/infra/event_bus.py +3 -1
  117. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  118. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  119. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  120. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  121. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  122. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  123. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  124. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  125. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  126. package/src/superlocalmemory/learning/ensemble.py +11 -0
  127. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  128. package/src/superlocalmemory/learning/feedback.py +1 -1
  129. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  130. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  131. package/src/superlocalmemory/learning/ranker.py +4 -1
  132. package/src/superlocalmemory/learning/source_quality.py +1 -1
  133. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  134. package/src/superlocalmemory/llm/backbone.py +13 -8
  135. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  136. package/src/superlocalmemory/math/fisher.py +1 -1
  137. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  138. package/src/superlocalmemory/math/hopfield.py +1 -1
  139. package/src/superlocalmemory/math/langevin.py +1 -1
  140. package/src/superlocalmemory/math/polar_quant.py +3 -4
  141. package/src/superlocalmemory/math/qjl.py +1 -1
  142. package/src/superlocalmemory/math/sheaf.py +1 -1
  143. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  144. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  145. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  146. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  147. package/src/superlocalmemory/mcp/server.py +39 -13
  148. package/src/superlocalmemory/mcp/shared.py +69 -3
  149. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  150. package/src/superlocalmemory/mcp/tools_core.py +128 -29
  151. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  152. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  153. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  154. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  155. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  156. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  157. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  158. package/src/superlocalmemory/mesh/broker.py +2 -0
  159. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  160. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  161. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  162. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  163. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  164. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  165. package/src/superlocalmemory/optimize/config/store.py +6 -4
  166. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  167. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  168. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  169. package/src/superlocalmemory/optimize/storage/db.py +14 -13
  170. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  171. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  172. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  173. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  174. package/src/superlocalmemory/retrieval/engine.py +169 -79
  175. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  176. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  177. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  178. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  179. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  180. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  181. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  182. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  183. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  184. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  185. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  186. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  187. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  188. package/src/superlocalmemory/server/api.py +10 -7
  189. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  190. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  191. package/src/superlocalmemory/server/route_mutations.py +84 -0
  192. package/src/superlocalmemory/server/routes/agents.py +8 -6
  193. package/src/superlocalmemory/server/routes/brain.py +14 -12
  194. package/src/superlocalmemory/server/routes/chat.py +29 -12
  195. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  196. package/src/superlocalmemory/server/routes/helpers.py +29 -4
  197. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  198. package/src/superlocalmemory/server/routes/memories.py +104 -43
  199. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  200. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  201. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  202. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  203. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  204. package/src/superlocalmemory/server/security_middleware.py +1 -1
  205. package/src/superlocalmemory/server/ui.py +6 -3
  206. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  207. package/src/superlocalmemory/server/write_identity.py +147 -0
  208. package/src/superlocalmemory/storage/access_log.py +4 -3
  209. package/src/superlocalmemory/storage/database.py +118 -25
  210. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  211. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  212. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  213. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  214. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  215. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  216. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  217. package/src/superlocalmemory/storage/models.py +16 -0
  218. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  219. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  220. package/src/superlocalmemory/ui/favicon.svg +5 -0
  221. package/src/superlocalmemory/ui/index.html +1 -0
  222. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  223. package/src/superlocalmemory/ui/js/core.js +49 -8
  224. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  225. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  226. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  227. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  228. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  229. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  230. package/src/superlocalmemory/ui/js/settings.js +4 -2
  231. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  232. package/bin/slm +0 -59
  233. package/bin/slm.bat +0 -77
  234. package/bin/slm.cmd +0 -5
  235. package/ide/integrations/langchain/README.md +0 -106
  236. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  237. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  238. package/ide/integrations/langchain/pyproject.toml +0 -38
  239. package/ide/integrations/langchain/tests/__init__.py +0 -3
  240. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  241. package/ide/integrations/langchain/tests/test_security.py +0 -117
  242. package/ide/integrations/llamaindex/README.md +0 -81
  243. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  244. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  245. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  246. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  247. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  248. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  249. package/plugin-src/.mcp.json +0 -12
  250. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  251. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  252. package/plugin-src/hooks/.gitkeep +0 -0
  253. package/plugin-src/hooks/hooks.json +0 -23
  254. package/plugin-src/manifest.json +0 -25
  255. package/plugin-src/requirements.txt +0 -1
  256. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  257. package/plugin-src/scripts/ensure-venv.bat +0 -122
  258. package/plugin-src/scripts/ensure-venv.sh +0 -105
  259. package/plugin-src/scripts/slm-launch +0 -15
  260. package/plugin-src/scripts/slm-launch.bat +0 -17
  261. package/plugin-src/settings.json +0 -16
  262. package/plugin-src/skills/slm-cache/SKILL.md +0 -140
  263. package/plugin-src/skills/slm-compress/SKILL.md +0 -143
  264. package/plugin-src/skills/slm-graph/SKILL.md +0 -300
  265. package/plugin-src/skills/slm-recall/SKILL.md +0 -204
  266. package/plugin-src/skills/slm-remember/SKILL.md +0 -194
  267. package/plugin-src/skills/slm-session/SKILL.md +0 -207
  268. package/plugin-src/skills/slm-status/SKILL.md +0 -149
  269. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  270. package/scripts/_savings_math.py +0 -270
  271. package/scripts/build-dmg.sh +0 -417
  272. package/scripts/build-plugin.js +0 -742
  273. package/scripts/build-slm-hook.ps1 +0 -40
  274. package/scripts/build-slm-hook.sh +0 -45
  275. package/scripts/build_entry.py +0 -452
  276. package/scripts/ci/stage5b_gate.sh +0 -50
  277. package/scripts/dogfood_savings.py +0 -490
  278. package/scripts/generate-thumbnails.py +0 -218
  279. package/scripts/install-skills.ps1 +0 -4
  280. package/scripts/install-skills.sh +0 -5
  281. package/scripts/install.ps1 +0 -701
  282. package/scripts/install.sh +0 -1015
  283. package/scripts/postinstall_binary.js +0 -287
  284. package/scripts/prepack.js +0 -33
  285. package/scripts/release_manifest.py +0 -273
  286. package/scripts/slm-hook.spec +0 -56
  287. package/scripts/start-dashboard.ps1 +0 -52
  288. package/scripts/start-dashboard.sh +0 -41
  289. package/scripts/sync-wiki.ps1 +0 -127
  290. package/scripts/sync-wiki.sh +0 -82
  291. package/scripts/test-dmg.sh +0 -161
  292. package/scripts/test-npm-package.ps1 +0 -252
  293. package/scripts/test-npm-package.sh +0 -207
  294. package/scripts/verify-install.ps1 +0 -294
  295. package/scripts/verify-install.sh +0 -266
  296. package/scripts/verify-v27.ps1 +0 -301
  297. package/scripts/verify-v27.sh +0 -233
  298. package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
  299. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  300. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  301. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  302. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  303. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -9,10 +9,11 @@ Channels: semantic, BM25, entity_graph, temporal, spreading_activation, hopfield
9
9
  Replaces V1's broken 10-channel triple-re-fusion pipeline.
10
10
 
11
11
  Part of Qualixar | Author: Varun Pratap Bhardwaj
12
- License: Elastic-2.0
12
+ License: AGPL-3.0-or-later
13
13
  """
14
14
  from __future__ import annotations
15
15
 
16
+ import concurrent.futures
16
17
  import logging
17
18
  import math
18
19
  import re
@@ -24,7 +25,10 @@ from superlocalmemory.core.config import ChannelWeights, RetrievalConfig
24
25
  from superlocalmemory.retrieval.fusion import FusionResult, weighted_rrf
25
26
  from superlocalmemory.retrieval.strategy import QueryStrategy, QueryStrategyClassifier
26
27
  from superlocalmemory.storage.models import (
27
- AtomicFact, Mode, RecallResponse, RetrievalResult,
28
+ AtomicFact,
29
+ Mode,
30
+ RecallResponse,
31
+ RetrievalResult,
28
32
  )
29
33
 
30
34
  if TYPE_CHECKING:
@@ -50,7 +54,7 @@ class EmbeddingProvider(Protocol):
50
54
 
51
55
 
52
56
  class RetrievalEngine:
53
- """6-channel retrieval: semantic + BM25 + entity_graph + temporal + spreading_activation + hopfield.
57
+ """Six-channel retrieval orchestrator.
54
58
 
55
59
  Usage::
56
60
  engine = RetrievalEngine(db, config, channels, embedder)
@@ -91,6 +95,17 @@ class RetrievalEngine:
91
95
  # recall A's mid-flight on the shared channels. Uncontended for a single
92
96
  # recall (~0 cost); only the channel phase of concurrent recalls serialises.
93
97
  self._scope_lock = threading.Lock()
98
+ # One executor belongs to one retrieval engine. Creating/destroying six
99
+ # worker threads on every recall caused allocator/thread-stack RSS churn
100
+ # under sustained sessions. The scope lock already serializes channel
101
+ # execution, so one six-worker pool preserves the existing concurrency
102
+ # semantics while making ownership and shutdown deterministic.
103
+ self._channel_executor = concurrent.futures.ThreadPoolExecutor(
104
+ max_workers=6,
105
+ thread_name_prefix="slm-recall-channel",
106
+ )
107
+ self._close_lock = threading.Lock()
108
+ self._closed = False
94
109
 
95
110
  # V3.3.4: LRU cache for query embeddings (avoids redundant Ollama API calls)
96
111
  # V3.4.40 (2026-05-09): bumped 64 -> 512. Each cached embedding is ~3KB
@@ -124,14 +139,14 @@ class RetrievalEngine:
124
139
  mode: Mode = Mode.A, limit: int = 20,
125
140
  *,
126
141
  extra_disabled_channels: set[str] | None = None,
127
- include_global: bool = True,
128
- include_shared: bool = True,
142
+ include_global: bool = False,
143
+ include_shared: bool = False,
129
144
  ) -> RecallResponse:
130
145
  """Full retrieval pipeline: strategy -> channels -> RRF -> rerank.
131
146
 
132
147
  Multi-scope: ``include_global`` / ``include_shared`` control which
133
- scopes participate in retrieval. Both default to True for backward
134
- compatibility (existing data has scope='personal' no effect).
148
+ scopes participate in retrieval. Both default to False so direct
149
+ retrieval-engine callers are private unless they explicitly opt in.
135
150
 
136
151
  V3.4.40 (2026-05-09): ``extra_disabled_channels`` allows callers to
137
152
  skip specific channels for a single recall (e.g. SpreadingActivation
@@ -140,12 +155,6 @@ class RetrievalEngine:
140
155
  t0 = time.monotonic()
141
156
  self._extra_disabled = set(extra_disabled_channels or ())
142
157
 
143
- # Multi-scope: scope flags are set on the (shared) channel instances +
144
- # the channels executed atomically under self._scope_lock — see the
145
- # `# 3. Run channels` block below. (profile_channel does not read scope.)
146
- self._include_global = include_global
147
- self._include_shared = include_shared
148
-
149
158
  # v3.5.0 diagnostic: stage timing inside retrieval (SLM_RECALL_TIMING=1).
150
159
  import os as _os_e
151
160
  import time as _time_e
@@ -218,10 +227,17 @@ class RetrievalEngine:
218
227
  fused_ids = {fr.fact_id for fr in fused}
219
228
  fused_scores = {fr.fact_id: fr.fused_score for fr in fused}
220
229
 
221
- if self._bridge is not None and strat.query_type in ("multi_hop", "entity", "factual", "general"):
230
+ bridge_query_types = ("multi_hop", "entity", "factual", "general")
231
+ if self._bridge is not None and strat.query_type in bridge_query_types:
222
232
  try:
223
233
  seed_ids = [fr.fact_id for fr in fused[:10]]
224
- bridges = self._bridge.discover(seed_ids, profile_id, max_bridges=10)
234
+ bridges = self._bridge.discover(
235
+ seed_ids,
236
+ profile_id,
237
+ max_bridges=10,
238
+ include_global=include_global,
239
+ include_shared=include_shared,
240
+ )
225
241
  for fid, score in bridges:
226
242
  if fid not in fused_ids:
227
243
  new_score = score * 0.8
@@ -268,7 +284,11 @@ class RetrievalEngine:
268
284
  try:
269
285
  candidate_ids = [fr.fact_id for fr in fused[:100]]
270
286
  eg_scores = self._entity.score_candidates(
271
- query, candidate_ids, profile_id,
287
+ query,
288
+ candidate_ids,
289
+ profile_id,
290
+ include_global=include_global,
291
+ include_shared=include_shared,
272
292
  )
273
293
  if eg_scores:
274
294
  boosted = []
@@ -293,7 +313,12 @@ class RetrievalEngine:
293
313
  # 4. Load facts for rerank pool
294
314
  pool = min(len(fused), max(effective_limit * 3, 30))
295
315
  top = fused[:pool]
296
- facts = self._load_facts(top, profile_id)
316
+ facts = self._load_facts(
317
+ top,
318
+ profile_id,
319
+ include_global=include_global,
320
+ include_shared=include_shared,
321
+ )
297
322
  _em("load_facts")
298
323
 
299
324
  # V3.3.21: Session diversity for aggregation queries.
@@ -309,21 +334,20 @@ class RetrievalEngine:
309
334
  self._reranker is not None
310
335
  and getattr(self._reranker, '_worker_ready', False)
311
336
  )
337
+ reranker_applied = False
338
+ reranker_status = (
339
+ "fallback_not_ready" if self._reranker is not None
340
+ else "not_configured"
341
+ )
312
342
  if reranker_ready and facts:
313
343
  ce_alpha = 0.5 if strat.query_type in ("multi_hop", "temporal") else 0.75
314
- top = self._apply_reranker(query, top, facts, alpha=ce_alpha)
344
+ top, reranker_applied, reranker_status = self._apply_reranker(
345
+ query, top, facts, alpha=ce_alpha,
346
+ )
347
+ elif reranker_ready:
348
+ reranker_status = "no_candidates"
315
349
  _em(f"rerank(ready={reranker_ready})")
316
350
 
317
- # V3.4.11: Channel diversity — guarantee entity_graph results appear in
318
- # the final output. Applied AFTER reranker so results can't be pushed out.
319
- final_top = top[:effective_limit]
320
- final_top = self._enforce_channel_diversity(
321
- final_top, fused, ch_results, effective_limit,
322
- )
323
- # Reload facts for any newly injected results
324
- if len(final_top) > len(top[:effective_limit]):
325
- facts = self._load_facts(final_top, profile_id)
326
-
327
351
  # v3.6.6: Evidence floor — gate on per-channel scores (NOT fused/RRF score).
328
352
  # Nonsense queries fuse at 0.75-0.78 because RRF is rank-derived and
329
353
  # uncalibrated. The discriminator is EARNED CHANNEL EVIDENCE:
@@ -339,10 +363,34 @@ class RetrievalEngine:
339
363
  )
340
364
  if floor_enabled:
341
365
  min_sem = getattr(self._config, "min_semantic_evidence", 0.60)
342
- final_top = self._apply_evidence_floor(final_top, facts, min_sem)
343
- # Trim facts dict to match filtered final_top
344
- filtered_ids = {fr.fact_id for fr in final_top}
345
- facts = {fid: f for fid, f in facts.items() if fid in filtered_ids}
366
+ # Qualify the rerank pool BEFORE applying the caller's limit. RRF
367
+ # can rank associative-only hits above an exact BM25 match; slicing
368
+ # first allowed those hits to occupy every output slot and then be
369
+ # removed by the floor, producing a false abstention even though a
370
+ # qualified candidate was immediately below the slice.
371
+ top = self._apply_evidence_floor(top, facts, min_sem)
372
+
373
+ # V3.4.11: Channel diversity — guarantee entity_graph results appear in
374
+ # the final output. Applied AFTER reranking and evidence qualification
375
+ # so an associative-only candidate cannot be reintroduced after the gate.
376
+ final_top = top[:effective_limit]
377
+ final_top = self._enforce_channel_diversity(
378
+ final_top, fused, ch_results, effective_limit,
379
+ )
380
+
381
+ # A channel-diversity promotion may come from outside the rerank pool.
382
+ # Load only when that happens; ordinary recalls reuse the existing map.
383
+ if any(fr.fact_id not in facts for fr in final_top):
384
+ facts.update(self._load_facts(
385
+ final_top,
386
+ profile_id,
387
+ include_global=include_global,
388
+ include_shared=include_shared,
389
+ ))
390
+
391
+ # Trim facts to the selected, qualified result set.
392
+ selected_ids = {fr.fact_id for fr in final_top}
393
+ facts = {fid: f for fid, f in facts.items() if fid in selected_ids}
346
394
 
347
395
  # 6. Build response
348
396
  results = self._build_results(final_top, facts, strat)
@@ -353,6 +401,8 @@ class RetrievalEngine:
353
401
  query_type=strat.query_type, channel_weights=strat.weights,
354
402
  total_candidates=total, retrieval_time_ms=ms,
355
403
  no_confident_match=no_match,
404
+ reranker_applied=reranker_applied,
405
+ reranker_status=reranker_status,
356
406
  )
357
407
 
358
408
  # -- Evidence floor (v3.6.6) -------------------------------------------
@@ -582,7 +632,6 @@ class RetrievalEngine:
582
632
  down to max(semantic,bm25,entity,temporal,hopfield,sa) — roughly a
583
633
  3-5x speedup for the channel phase.
584
634
  """
585
- import concurrent.futures
586
635
  import os as _os_e
587
636
  import time as _time_e
588
637
  _et = bool(_os_e.environ.get("SLM_RECALL_TIMING"))
@@ -627,41 +676,45 @@ class RetrievalEngine:
627
676
  logger.warning("%s channel: %s", name, exc)
628
677
  return (name, None)
629
678
 
630
- with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor:
631
- if self._semantic is not None and q_emb is not None and "semantic" not in disabled:
632
- futures["semantic"] = executor.submit(
633
- _safe_channel, "semantic",
634
- self._semantic.search, q_emb, profile_id, self._config.semantic_top_k,
635
- )
636
- if self._bm25 is not None and "bm25" not in disabled:
637
- futures["bm25"] = executor.submit(
638
- _safe_channel, "bm25",
639
- self._bm25.search, query, profile_id, self._config.bm25_top_k,
640
- )
641
- if self._temporal is not None and "temporal" not in disabled:
642
- futures["temporal"] = executor.submit(
643
- _safe_channel, "temporal",
644
- self._temporal.search, query, profile_id, self._config.bm25_top_k,
645
- )
646
- if self._hopfield is not None and q_emb is not None and "hopfield" not in disabled:
647
- futures["hopfield"] = executor.submit(
648
- _safe_channel, "hopfield",
649
- self._hopfield.search, q_emb, profile_id, self._config.hopfield_top_k,
650
- )
651
- if self._spreading_activation is not None and q_emb is not None and "spreading_activation" not in disabled:
652
- futures["spreading_activation"] = executor.submit(
653
- _safe_channel, "spreading_activation",
654
- self._spreading_activation.search, q_emb, profile_id, self._config.bm25_top_k,
655
- )
679
+ executor = self._channel_executor
680
+ if self._semantic is not None and q_emb is not None and "semantic" not in disabled:
681
+ futures["semantic"] = executor.submit(
682
+ _safe_channel, "semantic",
683
+ self._semantic.search, q_emb, profile_id, self._config.semantic_top_k,
684
+ )
685
+ if self._bm25 is not None and "bm25" not in disabled:
686
+ futures["bm25"] = executor.submit(
687
+ _safe_channel, "bm25",
688
+ self._bm25.search, query, profile_id, self._config.bm25_top_k,
689
+ )
690
+ if self._temporal is not None and "temporal" not in disabled:
691
+ futures["temporal"] = executor.submit(
692
+ _safe_channel, "temporal",
693
+ self._temporal.search, query, profile_id, self._config.bm25_top_k,
694
+ )
695
+ if self._hopfield is not None and q_emb is not None and "hopfield" not in disabled:
696
+ futures["hopfield"] = executor.submit(
697
+ _safe_channel, "hopfield",
698
+ self._hopfield.search, q_emb, profile_id, self._config.hopfield_top_k,
699
+ )
700
+ if (
701
+ self._spreading_activation is not None
702
+ and q_emb is not None
703
+ and "spreading_activation" not in disabled
704
+ ):
705
+ futures["spreading_activation"] = executor.submit(
706
+ _safe_channel, "spreading_activation",
707
+ self._spreading_activation.search, q_emb, profile_id, self._config.bm25_top_k,
708
+ )
656
709
 
657
- # Collect results as channels complete
658
- for name, fut in futures.items():
659
- try:
660
- ch_name, result = fut.result(timeout=30)
661
- if result:
662
- out[ch_name] = result
663
- except Exception as exc:
664
- logger.warning("Channel %s timed out or failed: %s", name, exc)
710
+ # Collect results as channels complete.
711
+ for name, fut in futures.items():
712
+ try:
713
+ ch_name, result = fut.result(timeout=30)
714
+ if result:
715
+ out[ch_name] = result
716
+ except Exception as exc:
717
+ logger.warning("Channel %s timed out or failed: %s", name, exc)
665
718
 
666
719
  # Apply registered post-retrieval filters (forgetting filter, etc.)
667
720
  if hasattr(self, '_registry') and self._registry._filters:
@@ -673,10 +726,23 @@ class RetrievalEngine:
673
726
 
674
727
  return out
675
728
 
729
+ def close(self) -> None:
730
+ """Release the channel workers owned by this retrieval engine."""
731
+ with self._close_lock:
732
+ if self._closed:
733
+ return
734
+ self._closed = True
735
+ self._channel_executor.shutdown(wait=True, cancel_futures=True)
736
+
676
737
  # -- Fact loading -------------------------------------------------------
677
738
 
678
739
  def _load_facts(
679
- self, fused: list[FusionResult], profile_id: str,
740
+ self,
741
+ fused: list[FusionResult],
742
+ profile_id: str,
743
+ *,
744
+ include_global: bool = False,
745
+ include_shared: bool = False,
680
746
  ) -> dict[str, AtomicFact]:
681
747
  """Load facts by ID — targeted query, not full-table scan.
682
748
 
@@ -688,8 +754,8 @@ class RetrievalEngine:
688
754
  return {}
689
755
  facts = self._db.get_facts_by_ids(
690
756
  needed, profile_id,
691
- include_global=getattr(self, '_include_global', True),
692
- include_shared=getattr(self, '_include_shared', True),
757
+ include_global=include_global,
758
+ include_shared=include_shared,
693
759
  )
694
760
  return {f.fact_id: f for f in facts}
695
761
 
@@ -705,7 +771,7 @@ class RetrievalEngine:
705
771
  self, query: str, fused: list[FusionResult],
706
772
  fact_map: dict[str, AtomicFact],
707
773
  alpha: float = 0.75,
708
- ) -> list[FusionResult]:
774
+ ) -> tuple[list[FusionResult], bool, str]:
709
775
  """Rerank with blended CE + RRF scores (Bug 1 fix).
710
776
 
711
777
  Blended: alpha * sigmoid(CE_score) + (1 - alpha) * rrf_score.
@@ -717,7 +783,7 @@ class RetrievalEngine:
717
783
  for fr in fused if fr.fact_id in fact_map
718
784
  ]
719
785
  if not candidates:
720
- return fused
786
+ return fused, False, "no_candidates"
721
787
 
722
788
  # V3.3.16: Strip speaker tags WITHOUT copying full AtomicFact objects.
723
789
  # Previously created full copies including 768-dim embeddings (~6KB each),
@@ -730,17 +796,33 @@ class RetrievalEngine:
730
796
  originals.append((fact, orig))
731
797
 
732
798
  try:
733
- scored = self._reranker.rerank( # type: ignore[union-attr]
734
- query, candidates, top_k=len(candidates),
799
+ rerank_with_status = getattr(
800
+ self._reranker, "rerank_with_status", None,
735
801
  )
802
+ # MagicMock fabricates arbitrary attributes; only use the richer
803
+ # contract when it is defined by the reranker type itself.
804
+ if callable(rerank_with_status) and hasattr(
805
+ type(self._reranker), "rerank_with_status",
806
+ ):
807
+ scored, applied, status = rerank_with_status(
808
+ query, candidates, top_k=len(candidates),
809
+ )
810
+ else:
811
+ scored = self._reranker.rerank( # type: ignore[union-attr]
812
+ query, candidates, top_k=len(candidates),
813
+ )
814
+ applied, status = True, "applied"
736
815
  except Exception as exc:
737
816
  logger.warning("Cross-encoder rerank failed: %s", exc)
738
- return fused
817
+ return fused, False, "error"
739
818
  finally:
740
819
  # Restore original content (with speaker tags)
741
820
  for fact, orig_content in originals:
742
821
  fact.content = orig_content
743
822
 
823
+ if not applied:
824
+ return fused, False, status
825
+
744
826
  score_map = {fact.fact_id: score for fact, score in scored}
745
827
 
746
828
  # Min-max normalize CE scores to [0, 1] within the batch instead of
@@ -768,7 +850,7 @@ class RetrievalEngine:
768
850
  for fr in fused
769
851
  ]
770
852
  updated.sort(key=lambda r: r.fused_score, reverse=True)
771
- return updated
853
+ return updated, True, "applied"
772
854
 
773
855
  # -- Agentic adapter -----------------------------------
774
856
 
@@ -871,11 +953,14 @@ class RetrievalEngine:
871
953
  # boosts push raw scores well above 1 (observed: 27.97). A sigmoid
872
954
  # preserves rank (monotonic) while giving users a readable 0-1 range.
873
955
  normalized_score = 1.0 / (1.0 + math.exp(-boosted_score * 0.5))
874
- confidence = min(1.0, normalized_score * 10.0) * fact.confidence
875
956
  results.append(RetrievalResult(
876
957
  fact=fact, score=round(normalized_score, 4),
877
958
  channel_scores=fr.channel_scores,
878
- confidence=confidence, evidence_chain=evidence,
959
+ confidence=fact.confidence,
960
+ relevance_score=round(normalized_score, 4),
961
+ ranking_score=boosted_score,
962
+ memory_confidence=fact.confidence,
963
+ evidence_chain=evidence,
879
964
  trust_score=raw_trust,
880
965
  ))
881
966
  return results
@@ -924,10 +1009,15 @@ def apply_channel_weights(
924
1009
  new_score = (base if base > 0.0 else float(c.score)) * ce_bias
925
1010
  out.append(RetrievalResult(
926
1011
  fact=c.fact,
927
- score=new_score,
1012
+ score=c.score,
928
1013
  channel_scores=new_cs,
929
1014
  confidence=c.confidence,
1015
+ relevance_score=c.relevance_score,
1016
+ ranking_score=new_score,
1017
+ memory_confidence=c.memory_confidence,
1018
+ rank_position=c.rank_position,
930
1019
  evidence_chain=c.evidence_chain,
931
1020
  trust_score=c.trust_score,
1021
+ marker=c.marker,
932
1022
  ))
933
1023
  return out