superlocalmemory 3.6.23 → 3.7.1

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 (285) hide show
  1. package/CHANGELOG.md +61 -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 +17 -9
  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/agents/slm-memory-advisor.md +1 -1
  18. package/plugin-src/manifest.json +1 -1
  19. package/plugin-src/requirements.txt +1 -1
  20. package/plugin-src/rules/AGENTS.md +1 -1
  21. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  22. package/pyproject.toml +40 -8
  23. package/scripts/postinstall-interactive.js +17 -94
  24. package/scripts/postinstall.js +185 -258
  25. package/scripts/preuninstall.js +9 -50
  26. package/src/superlocalmemory/__init__.py +2 -2
  27. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  28. package/src/superlocalmemory/attribution/signer.py +34 -19
  29. package/src/superlocalmemory/attribution/watermark.py +1 -1
  30. package/src/superlocalmemory/cli/_lazy_init.py +7 -7
  31. package/src/superlocalmemory/cli/commands.py +453 -191
  32. package/src/superlocalmemory/cli/context_commands.py +5 -4
  33. package/src/superlocalmemory/cli/daemon.py +282 -187
  34. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  35. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  36. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  37. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  38. package/src/superlocalmemory/cli/main.py +128 -31
  39. package/src/superlocalmemory/cli/pending_store.py +54 -38
  40. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  41. package/src/superlocalmemory/cli/service_installer.py +57 -52
  42. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  43. package/src/superlocalmemory/cli/version_banner.py +2 -1
  44. package/src/superlocalmemory/code_graph/config.py +3 -1
  45. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  46. package/src/superlocalmemory/core/config.py +65 -20
  47. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  48. package/src/superlocalmemory/core/context_cache.py +56 -8
  49. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  50. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  51. package/src/superlocalmemory/core/embeddings.py +54 -18
  52. package/src/superlocalmemory/core/engine.py +150 -104
  53. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  54. package/src/superlocalmemory/core/engine_wiring.py +32 -23
  55. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  56. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  57. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  58. package/src/superlocalmemory/core/health_monitor.py +4 -2
  59. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  60. package/src/superlocalmemory/core/injection.py +69 -18
  61. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  62. package/src/superlocalmemory/core/maintenance.py +1 -1
  63. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  64. package/src/superlocalmemory/core/mutations.py +143 -0
  65. package/src/superlocalmemory/core/platform_utils.py +7 -4
  66. package/src/superlocalmemory/core/ram_lock.py +16 -5
  67. package/src/superlocalmemory/core/rate_limit.py +1 -1
  68. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  69. package/src/superlocalmemory/core/recall_worker.py +76 -59
  70. package/src/superlocalmemory/core/registry.py +1 -1
  71. package/src/superlocalmemory/core/scale_engine.py +293 -0
  72. package/src/superlocalmemory/core/score_contract.py +62 -0
  73. package/src/superlocalmemory/core/security_primitives.py +3 -1
  74. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  75. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  76. package/src/superlocalmemory/core/tier_manager.py +32 -20
  77. package/src/superlocalmemory/core/worker_pool.py +13 -4
  78. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  79. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  80. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  81. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  82. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  83. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  84. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  85. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  86. package/src/superlocalmemory/encoding/foresight.py +2 -0
  87. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  88. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  89. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  90. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  91. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  92. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  93. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  94. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  95. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  96. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  97. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  98. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  99. package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
  100. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  101. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  102. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  103. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  104. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  105. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  106. package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
  107. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  108. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  109. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  110. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  111. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  112. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  113. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  114. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  115. package/src/superlocalmemory/infra/backup.py +7 -5
  116. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  117. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  118. package/src/superlocalmemory/infra/data_root.py +199 -0
  119. package/src/superlocalmemory/infra/event_bus.py +3 -1
  120. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  121. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  122. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  123. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  124. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  125. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  126. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  127. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  128. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  129. package/src/superlocalmemory/learning/ensemble.py +11 -0
  130. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  131. package/src/superlocalmemory/learning/feedback.py +1 -1
  132. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  133. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  134. package/src/superlocalmemory/learning/ranker.py +4 -1
  135. package/src/superlocalmemory/learning/source_quality.py +1 -1
  136. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  137. package/src/superlocalmemory/llm/backbone.py +1 -1
  138. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  139. package/src/superlocalmemory/math/fisher.py +1 -1
  140. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  141. package/src/superlocalmemory/math/hopfield.py +1 -1
  142. package/src/superlocalmemory/math/langevin.py +1 -1
  143. package/src/superlocalmemory/math/polar_quant.py +3 -4
  144. package/src/superlocalmemory/math/qjl.py +1 -1
  145. package/src/superlocalmemory/math/sheaf.py +1 -1
  146. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  147. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  148. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  149. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  150. package/src/superlocalmemory/mcp/server.py +39 -13
  151. package/src/superlocalmemory/mcp/shared.py +69 -3
  152. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  153. package/src/superlocalmemory/mcp/tools_core.py +130 -31
  154. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  155. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  156. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  157. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  158. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  159. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  160. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  161. package/src/superlocalmemory/mesh/broker.py +2 -0
  162. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  163. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  164. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  165. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  166. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  167. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  168. package/src/superlocalmemory/optimize/config/store.py +6 -4
  169. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  170. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  171. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  172. package/src/superlocalmemory/optimize/storage/db.py +12 -12
  173. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  174. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  175. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  176. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  177. package/src/superlocalmemory/retrieval/engine.py +169 -79
  178. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  179. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  180. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  181. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  182. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  183. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  184. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  185. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  186. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  187. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  188. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  189. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  190. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  191. package/src/superlocalmemory/server/api.py +10 -7
  192. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  193. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  194. package/src/superlocalmemory/server/route_mutations.py +84 -0
  195. package/src/superlocalmemory/server/routes/agents.py +8 -6
  196. package/src/superlocalmemory/server/routes/brain.py +14 -12
  197. package/src/superlocalmemory/server/routes/chat.py +29 -12
  198. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  199. package/src/superlocalmemory/server/routes/helpers.py +8 -63
  200. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  201. package/src/superlocalmemory/server/routes/memories.py +104 -43
  202. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  203. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  204. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  205. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  206. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  207. package/src/superlocalmemory/server/security_middleware.py +1 -1
  208. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  209. package/src/superlocalmemory/server/write_identity.py +147 -0
  210. package/src/superlocalmemory/storage/access_log.py +4 -3
  211. package/src/superlocalmemory/storage/database.py +118 -25
  212. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  213. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  214. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  215. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  216. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  217. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  218. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  219. package/src/superlocalmemory/storage/models.py +16 -0
  220. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  221. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  222. package/src/superlocalmemory/ui/favicon.svg +5 -0
  223. package/src/superlocalmemory/ui/index.html +1 -0
  224. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  225. package/src/superlocalmemory/ui/js/core.js +49 -8
  226. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  227. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  228. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  229. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  230. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  231. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  232. package/src/superlocalmemory/ui/js/settings.js +4 -2
  233. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  234. package/bin/slm +0 -59
  235. package/bin/slm.bat +0 -77
  236. package/bin/slm.cmd +0 -5
  237. package/ide/integrations/langchain/README.md +0 -106
  238. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  239. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  240. package/ide/integrations/langchain/pyproject.toml +0 -38
  241. package/ide/integrations/langchain/tests/__init__.py +0 -3
  242. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  243. package/ide/integrations/langchain/tests/test_security.py +0 -117
  244. package/ide/integrations/llamaindex/README.md +0 -81
  245. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  246. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  247. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  248. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  249. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  250. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  251. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  252. package/scripts/_savings_math.py +0 -270
  253. package/scripts/build-dmg.sh +0 -417
  254. package/scripts/build-plugin.js +0 -742
  255. package/scripts/build-slm-hook.ps1 +0 -40
  256. package/scripts/build-slm-hook.sh +0 -45
  257. package/scripts/build_entry.py +0 -452
  258. package/scripts/ci/stage5b_gate.sh +0 -50
  259. package/scripts/dogfood_savings.py +0 -490
  260. package/scripts/generate-thumbnails.py +0 -218
  261. package/scripts/install-skills.ps1 +0 -4
  262. package/scripts/install-skills.sh +0 -5
  263. package/scripts/install.ps1 +0 -701
  264. package/scripts/install.sh +0 -1015
  265. package/scripts/postinstall_binary.js +0 -287
  266. package/scripts/prepack.js +0 -33
  267. package/scripts/release_manifest.py +0 -273
  268. package/scripts/slm-hook.spec +0 -56
  269. package/scripts/start-dashboard.ps1 +0 -52
  270. package/scripts/start-dashboard.sh +0 -41
  271. package/scripts/sync-wiki.ps1 +0 -127
  272. package/scripts/sync-wiki.sh +0 -82
  273. package/scripts/test-dmg.sh +0 -161
  274. package/scripts/test-npm-package.ps1 +0 -252
  275. package/scripts/test-npm-package.sh +0 -207
  276. package/scripts/verify-install.ps1 +0 -294
  277. package/scripts/verify-install.sh +0 -266
  278. package/scripts/verify-v27.ps1 +0 -301
  279. package/scripts/verify-v27.sh +0 -233
  280. package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
  281. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  282. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  283. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  284. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  285. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -0,0 +1,513 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+
4
+ """Production adapter between MemoryEngine and canonical ingestion.
5
+
6
+ This is the anti-corruption layer for the expand-migrate-contract rollout:
7
+ the command owns durability and state, while the existing engine remains the
8
+ implementation of queryable projection and complete derivation.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import uuid
15
+ from typing import TYPE_CHECKING
16
+
17
+ from superlocalmemory.core.ingestion_command import (
18
+ IngestionCommand,
19
+ IngestionOperation,
20
+ IngestionOperationRepository,
21
+ IngestionRequest,
22
+ MaterializationResult,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from superlocalmemory.core.engine import MemoryEngine
27
+ from superlocalmemory.storage.models import AtomicFact
28
+
29
+
30
+ _PREBUILT_FACT_KEY = "_slm_prebuilt_fact_v1"
31
+
32
+
33
+ def content_passes_admission(content: str) -> bool:
34
+ """Return whether raw content is eligible to reach durable ingestion.
35
+
36
+ This is deliberately stateless: canonical admission must not populate the
37
+ live entropy gate's duplicate window before the materialization pipeline
38
+ evaluates the same content.
39
+ """
40
+ if not content or not content.strip():
41
+ return False
42
+
43
+ from superlocalmemory.core.injection import is_low_quality
44
+ from superlocalmemory.encoding.entropy_gate import EntropyGate
45
+
46
+ return not is_low_quality(content) and EntropyGate().should_pass(content)
47
+
48
+
49
+ def _prebuilt_fact_payload(fact: AtomicFact) -> dict:
50
+ """Serialize the stable public AtomicFact contract into M018 metadata."""
51
+ enum_fields = {"fact_type", "lifecycle", "signal_type"}
52
+ fields = (
53
+ "fact_id", "memory_id", "profile_id", "scope", "shared_with",
54
+ "content", "fact_type", "entities", "canonical_entities",
55
+ "observation_date", "referenced_date", "interval_start", "interval_end",
56
+ "confidence", "importance", "evidence_count", "access_count",
57
+ "source_turn_ids", "session_id", "embedding", "fisher_mean",
58
+ "fisher_variance", "lifecycle", "langevin_position",
59
+ "emotional_valence", "emotional_arousal", "signal_type", "pinned",
60
+ "created_at",
61
+ )
62
+ payload = {}
63
+ for name in fields:
64
+ value = getattr(fact, name)
65
+ payload[name] = value.value if name in enum_fields else value
66
+ return payload
67
+
68
+
69
+ def _prebuilt_fact_from_payload(payload: dict):
70
+ from superlocalmemory.storage.models import (
71
+ AtomicFact,
72
+ FactType,
73
+ MemoryLifecycle,
74
+ SignalType,
75
+ )
76
+
77
+ values = dict(payload)
78
+ values["fact_type"] = FactType(values.get("fact_type", FactType.SEMANTIC.value))
79
+ values["lifecycle"] = MemoryLifecycle(
80
+ values.get("lifecycle", MemoryLifecycle.ACTIVE.value)
81
+ )
82
+ values["signal_type"] = SignalType(
83
+ values.get("signal_type", SignalType.FACTUAL.value)
84
+ )
85
+ return AtomicFact(**values)
86
+
87
+
88
+ def local_trusted_actor_id(actor_kind: str) -> str:
89
+ """Derive a stable local actor from the private install capability."""
90
+ from superlocalmemory.core.security_primitives import ensure_install_token
91
+ from superlocalmemory.infra.daemon_identity import namespace_id_for, owner_id
92
+ from superlocalmemory.infra.data_root import canonical_data_root
93
+
94
+ token = ensure_install_token()
95
+ material = (
96
+ b"superlocalmemory-local-actor-v1\0"
97
+ + token.encode("utf-8")
98
+ + b"\0"
99
+ + actor_kind.encode("utf-8")
100
+ )
101
+ fingerprint = hashlib.sha256(material).hexdigest()
102
+ return (
103
+ f"local-capability:{actor_kind}:{owner_id()}:"
104
+ f"{namespace_id_for(canonical_data_root())}:{fingerprint}"
105
+ )
106
+
107
+
108
+ def canonical_store(
109
+ engine: MemoryEngine,
110
+ content: str,
111
+ *,
112
+ source_type: str,
113
+ trusted_actor_id: str,
114
+ metadata: dict | None = None,
115
+ scope: str = "personal",
116
+ shared_with: list[str] | tuple[str, ...] | None = None,
117
+ session_id: str = "",
118
+ session_date: str | None = None,
119
+ speaker: str = "",
120
+ role: str = "user",
121
+ idempotency_key: str = "",
122
+ require_complete: bool = True,
123
+ return_receipt: bool = False,
124
+ ) -> list[str] | IngestionOperation:
125
+ """Synchronously submit and completely materialize one canonical write."""
126
+ import time
127
+
128
+ from superlocalmemory.core.ingestion_command import IngestionRequest, IngestionState
129
+ from superlocalmemory.infra.local_diagnostics import record_operation
130
+
131
+ started = time.monotonic()
132
+
133
+ # Preserve the long-standing Python/API contract for rejected content:
134
+ # low-information input is a no-op and never becomes an M018 operation.
135
+ if not content_passes_admission(content):
136
+ record_operation(
137
+ "remember",
138
+ client=trusted_actor_id,
139
+ duration_ms=(time.monotonic() - started) * 1000.0,
140
+ error=ValueError("content rejected by local admission policy"),
141
+ )
142
+ return []
143
+ try:
144
+ command = build_engine_ingestion_command(engine)
145
+ receipt = command.submit(IngestionRequest(
146
+ content=content,
147
+ profile_id=engine._profile_id,
148
+ source_type=source_type,
149
+ idempotency_key=idempotency_key or uuid.uuid4().hex,
150
+ metadata=dict(metadata or {}),
151
+ scope=scope,
152
+ shared_with=tuple(shared_with or ()),
153
+ trusted_actor_id=trusted_actor_id,
154
+ session_id=session_id,
155
+ session_date=session_date or "",
156
+ speaker=speaker,
157
+ role=role,
158
+ ))
159
+ result = command.materialize(receipt.operation_id)
160
+ if result.state is not IngestionState.COMPLETE:
161
+ if not require_complete and receipt.fact_ids:
162
+ import logging
163
+ logging.getLogger(__name__).warning(
164
+ "Canonical operation %s remains %s and will retry: %s",
165
+ result.operation_id,
166
+ result.state.value,
167
+ result.last_error,
168
+ )
169
+ record_operation(
170
+ "remember", client=trusted_actor_id,
171
+ duration_ms=(time.monotonic() - started) * 1000.0,
172
+ )
173
+ return list(receipt.fact_ids)
174
+ raise RuntimeError(result.last_error or "canonical materialization failed")
175
+ except Exception as exc:
176
+ record_operation(
177
+ "remember",
178
+ client=trusted_actor_id,
179
+ duration_ms=(time.monotonic() - started) * 1000.0,
180
+ error=exc,
181
+ )
182
+ raise
183
+ record_operation(
184
+ "remember",
185
+ client=trusted_actor_id,
186
+ duration_ms=(time.monotonic() - started) * 1000.0,
187
+ )
188
+ return result if return_receipt else list(result.fact_ids)
189
+
190
+
191
+ def canonical_store_fn(
192
+ engine: MemoryEngine,
193
+ *,
194
+ source_type: str,
195
+ trusted_actor_id: str,
196
+ ):
197
+ """Return an AutoCapture-compatible synchronous canonical store callable."""
198
+ def store(content: str, metadata: dict | None = None) -> list[str]:
199
+ values = dict(metadata or {})
200
+ return canonical_store(
201
+ engine,
202
+ content,
203
+ source_type=source_type,
204
+ trusted_actor_id=trusted_actor_id,
205
+ metadata=values,
206
+ session_id=str(values.get("session_id") or ""),
207
+ )
208
+
209
+ return store
210
+
211
+
212
+ def canonical_store_fact(
213
+ engine: MemoryEngine,
214
+ fact: AtomicFact,
215
+ *,
216
+ trusted_actor_id: str,
217
+ ) -> str:
218
+ """Durably ingest a caller-built fact without changing its public ID."""
219
+ from superlocalmemory.core.ingestion_command import IngestionRequest, IngestionState
220
+ from superlocalmemory.core.injection import is_low_quality
221
+
222
+ if is_low_quality(fact.content):
223
+ return fact.fact_id
224
+ command = build_engine_ingestion_command(engine)
225
+ receipt = command.submit(IngestionRequest(
226
+ content=fact.content,
227
+ profile_id=engine._profile_id,
228
+ source_type="python-api-prebuilt",
229
+ idempotency_key=f"prebuilt:{fact.fact_id}",
230
+ metadata={_PREBUILT_FACT_KEY: _prebuilt_fact_payload(fact)},
231
+ scope=fact.scope or "personal",
232
+ shared_with=tuple(fact.shared_with or ()),
233
+ trusted_actor_id=trusted_actor_id,
234
+ session_id=fact.session_id,
235
+ session_date=fact.observation_date or "",
236
+ ))
237
+ result = command.materialize(receipt.operation_id)
238
+ if result.state is not IngestionState.COMPLETE:
239
+ raise RuntimeError(result.last_error or "prebuilt fact materialization failed")
240
+ if fact.fact_id not in result.fact_ids:
241
+ raise RuntimeError("prebuilt fact ID was not preserved by canonical ingestion")
242
+ stored = engine._db.get_fact(fact.fact_id)
243
+ if stored is not None:
244
+ fact.memory_id = stored.memory_id
245
+ return fact.fact_id
246
+
247
+
248
+ def build_engine_ingestion_command(engine: MemoryEngine) -> IngestionCommand:
249
+ """Bind one initialized engine to the durable ingestion command."""
250
+ engine._require_full("canonical_ingestion")
251
+ engine._ensure_init()
252
+ repository = IngestionOperationRepository(engine._db)
253
+
254
+ def write_queryable(request: IngestionRequest, operation_id: str) -> list[str]:
255
+ if request.profile_id != engine._profile_id:
256
+ raise ValueError("ingestion request profile does not match engine")
257
+ if not request.trusted_actor_id:
258
+ raise ValueError("trusted actor identity is required")
259
+ hook_context = {
260
+ "operation": "store",
261
+ "agent_id": request.trusted_actor_id or "unknown",
262
+ "profile_id": request.profile_id,
263
+ "content_preview": request.content[:100],
264
+ "ingestion_operation_id": operation_id,
265
+ }
266
+ # Authorization and trust policy must run before raw evidence reaches
267
+ # durable storage. Materialization reuses this authorization decision.
268
+ engine._hooks.run_pre("store", hook_context)
269
+ metadata = dict(request.metadata)
270
+ metadata["ingestion_operation_id"] = operation_id
271
+ if request.session_id:
272
+ metadata.setdefault("session_id", request.session_id)
273
+ prebuilt_payload = metadata.get(_PREBUILT_FACT_KEY)
274
+ if isinstance(prebuilt_payload, dict):
275
+ from superlocalmemory.storage.models import MemoryRecord
276
+
277
+ fact = _prebuilt_fact_from_payload(prebuilt_payload)
278
+ fact.profile_id = request.profile_id
279
+ fact.scope = request.scope
280
+ fact.shared_with = list(request.shared_with) or None
281
+ fact.session_id = request.session_id or fact.session_id
282
+ if request.session_date:
283
+ fact.observation_date = request.session_date
284
+ memory_id = fact.memory_id
285
+ memory_rows = (
286
+ engine._db.execute(
287
+ "SELECT memory_id FROM memories WHERE memory_id=? AND profile_id=?",
288
+ (memory_id, request.profile_id),
289
+ )
290
+ if memory_id else []
291
+ )
292
+ if not memory_rows:
293
+ record = MemoryRecord(
294
+ memory_id=memory_id or uuid.uuid4().hex,
295
+ profile_id=request.profile_id,
296
+ content=request.content,
297
+ session_id=request.session_id,
298
+ session_date=request.session_date,
299
+ metadata=metadata,
300
+ scope=request.scope,
301
+ shared_with=list(request.shared_with) or None,
302
+ )
303
+ engine._db.store_memory(record)
304
+ memory_id = record.memory_id
305
+ fact.memory_id = memory_id
306
+ engine._db.store_fact(fact)
307
+ return [fact.fact_id]
308
+ return engine.store_fast(
309
+ request.content,
310
+ metadata=metadata,
311
+ scope=request.scope,
312
+ shared_with=list(request.shared_with) or None,
313
+ session_date=request.session_date or None,
314
+ speaker=request.speaker,
315
+ role=request.role,
316
+ index_external=False,
317
+ )
318
+
319
+ def materialize(operation: IngestionOperation) -> MaterializationResult:
320
+ facts = engine._db.get_facts_by_ids(
321
+ list(operation.queryable_fact_ids),
322
+ operation.profile_id,
323
+ )
324
+ if len(facts) != len(operation.queryable_fact_ids):
325
+ raise ValueError("queryable fact profile mismatch or missing fact")
326
+ memory_ids = {fact.memory_id for fact in facts}
327
+ if len(memory_ids) != 1:
328
+ raise ValueError("queryable facts do not share one source memory")
329
+ memory_id = next(iter(memory_ids))
330
+
331
+ from superlocalmemory.core.store_pipeline import run_store
332
+
333
+ is_prebuilt = isinstance(operation.metadata.get(_PREBUILT_FACT_KEY), dict)
334
+
335
+ class _QueryableProjectionExtractor:
336
+ @staticmethod
337
+ def extract_facts(**_kwargs):
338
+ return []
339
+
340
+ pipeline_state: dict[str, bool] = {}
341
+ fact_ids = run_store(
342
+ operation.raw_content,
343
+ operation.profile_id,
344
+ session_id=operation.session_id,
345
+ session_date=operation.session_date or None,
346
+ speaker=operation.speaker,
347
+ role=operation.role,
348
+ metadata=dict(operation.metadata),
349
+ scope=operation.scope,
350
+ shared_with=list(operation.shared_with) or None,
351
+ config=engine._config,
352
+ db=engine._db,
353
+ embedder=engine._embedder,
354
+ fact_extractor=(
355
+ _QueryableProjectionExtractor() if is_prebuilt else engine._fact_extractor
356
+ ),
357
+ entity_resolver=engine._entity_resolver,
358
+ temporal_parser=engine._temporal_parser,
359
+ type_router=None if is_prebuilt else engine._type_router,
360
+ graph_builder=engine._graph_builder,
361
+ consolidator=None if is_prebuilt else engine._consolidator,
362
+ observation_builder=engine._observation_builder,
363
+ scene_builder=engine._scene_builder,
364
+ entropy_gate=engine._entropy_gate,
365
+ # External indexes are projected only after the relational unit of
366
+ # work commits; sqlite-vec uses a separate connection.
367
+ ann_index=None,
368
+ sheaf_checker=engine._sheaf_checker,
369
+ retrieval_engine=None,
370
+ provenance=engine._provenance,
371
+ hooks=engine._hooks,
372
+ vector_store=None,
373
+ context_generator=engine._context_generator,
374
+ temporal_validator=engine._temporal_validator,
375
+ auto_linker=engine._auto_linker,
376
+ consolidation_engine=engine._consolidation_engine,
377
+ existing_memory_id=memory_id,
378
+ queryable_fact_ids=operation.queryable_fact_ids,
379
+ trusted_actor_id=operation.trusted_actor_id,
380
+ pre_authorized=True,
381
+ ingestion_source_type=operation.source_type,
382
+ ingestion_operation_id=operation.operation_id,
383
+ derivation_report=pipeline_state,
384
+ )
385
+ if not fact_ids:
386
+ return MaterializationResult((), {"relational": False})
387
+ if is_prebuilt:
388
+ pipeline_state["extraction"] = True
389
+ pipeline_state["consolidation"] = True
390
+
391
+ placeholders = ",".join("?" for _ in fact_ids)
392
+ relational_count = engine._db.execute(
393
+ f"SELECT COUNT(*) AS count FROM atomic_facts "
394
+ f"WHERE fact_id IN ({placeholders}) AND profile_id=?",
395
+ (*fact_ids, operation.profile_id),
396
+ )
397
+ fts_count = engine._db.execute(
398
+ f"SELECT COUNT(*) AS count FROM atomic_facts_fts "
399
+ f"WHERE fact_id IN ({placeholders})",
400
+ tuple(fact_ids),
401
+ )
402
+ provenance_count = engine._db.execute(
403
+ f"SELECT COUNT(DISTINCT fact_id) AS count FROM provenance "
404
+ f"WHERE fact_id IN ({placeholders}) AND profile_id=? "
405
+ "AND source_type=? AND source_id=? AND created_by=?",
406
+ (
407
+ *fact_ids,
408
+ operation.profile_id,
409
+ operation.source_type,
410
+ operation.operation_id,
411
+ operation.trusted_actor_id,
412
+ ),
413
+ )
414
+ temporal_required = engine._temporal_validator is not None
415
+ temporal_complete = True
416
+ if temporal_required:
417
+ temporal_count = engine._db.execute(
418
+ f"SELECT COUNT(DISTINCT fact_id) AS count FROM fact_temporal_validity "
419
+ f"WHERE fact_id IN ({placeholders}) AND profile_id=?",
420
+ (*fact_ids, operation.profile_id),
421
+ )
422
+ temporal_complete = int(dict(temporal_count[0])["count"]) == len(fact_ids)
423
+
424
+ embeddings_complete = True
425
+ if engine._embedder is not None:
426
+ embedding_count = engine._db.execute(
427
+ f"SELECT COUNT(*) AS count FROM atomic_facts "
428
+ f"WHERE fact_id IN ({placeholders}) AND embedding IS NOT NULL",
429
+ tuple(fact_ids),
430
+ )
431
+ embeddings_complete = (
432
+ int(dict(embedding_count[0])["count"]) == len(fact_ids)
433
+ )
434
+
435
+ derivation_state = {
436
+ "relational": int(dict(relational_count[0])["count"]) == len(fact_ids),
437
+ "fts": int(dict(fts_count[0])["count"]) == len(fact_ids),
438
+ "extraction": pipeline_state.get("extraction", False),
439
+ "canonicalization": pipeline_state.get("canonicalization", False),
440
+ "consolidation": pipeline_state.get("consolidation", False),
441
+ "graph": pipeline_state.get("graph", False),
442
+ "temporal": (
443
+ temporal_complete and pipeline_state.get("temporal", False)
444
+ ),
445
+ "provenance": (
446
+ int(dict(provenance_count[0])["count"]) == len(fact_ids)
447
+ and pipeline_state.get("provenance", False)
448
+ ),
449
+ "trust_policy": bool(operation.trusted_actor_id),
450
+ "embeddings": embeddings_complete,
451
+ }
452
+ return MaterializationResult(tuple(fact_ids), derivation_state)
453
+
454
+ def project(operation: IngestionOperation) -> dict[str, bool]:
455
+ facts = engine._db.get_facts_by_ids(
456
+ list(operation.final_fact_ids),
457
+ operation.profile_id,
458
+ )
459
+ if len(facts) != len(operation.final_fact_ids):
460
+ raise RuntimeError("external projection facts are missing")
461
+
462
+ ann_required = engine._ann_index is not None
463
+ vector_store = engine._vector_store
464
+ vector_required = bool(
465
+ vector_store is not None and getattr(vector_store, "available", False)
466
+ )
467
+ ann_complete = True
468
+ vector_complete = True
469
+ bm25_complete = True
470
+ bm25 = getattr(engine._retrieval_engine, "_bm25", None)
471
+ for fact in facts:
472
+ if not fact.embedding:
473
+ if ann_required:
474
+ ann_complete = False
475
+ if vector_required:
476
+ vector_complete = False
477
+ if fact.embedding and ann_required:
478
+ try:
479
+ engine._ann_index.add(fact.fact_id, fact.embedding)
480
+ except Exception:
481
+ ann_complete = False
482
+ if fact.embedding and vector_required:
483
+ if not vector_store.upsert(
484
+ fact_id=fact.fact_id,
485
+ profile_id=operation.profile_id,
486
+ embedding=fact.embedding,
487
+ ):
488
+ vector_complete = False
489
+ if bm25 is not None:
490
+ try:
491
+ bm25.add(fact.fact_id, fact.content, operation.profile_id)
492
+ except Exception:
493
+ bm25_complete = False
494
+ return {
495
+ "ann": ann_complete,
496
+ "vector": vector_complete,
497
+ "bm25": bm25_complete,
498
+ }
499
+
500
+ return IngestionCommand(
501
+ repository,
502
+ write_queryable=write_queryable,
503
+ materialize=materialize,
504
+ project=project,
505
+ )
506
+
507
+
508
+ __all__ = [
509
+ "build_engine_ingestion_command",
510
+ "canonical_store",
511
+ "canonical_store_fn",
512
+ "local_trusted_actor_id",
513
+ ]
@@ -22,6 +22,30 @@ if TYPE_CHECKING:
22
22
  logger = logging.getLogger(__name__)
23
23
 
24
24
 
25
+ def _log_reranker_warmup_status(reranker: Any) -> None:
26
+ """Record non-blocking reranker warmup state without alarming first-run users."""
27
+ ready = reranker.warmup_sync(timeout=180)
28
+ if ready:
29
+ logger.info("Cross-encoder reranker warm and ready")
30
+ return
31
+ try:
32
+ from superlocalmemory.retrieval.reranker import _is_reranker_worker_alive
33
+
34
+ if _is_reranker_worker_alive():
35
+ logger.info(
36
+ "Cross-encoder reranker worker held by another process "
37
+ "(machine-wide singleton — usually the unified daemon); "
38
+ "this process will route reranking through that worker"
39
+ )
40
+ return
41
+ except Exception:
42
+ pass
43
+ logger.info(
44
+ "Cross-encoder reranker did not become ready during background warmup; "
45
+ "recalls use fallback scoring. Run 'slm doctor' for diagnostics."
46
+ )
47
+
48
+
25
49
  # ---------------------------------------------------------------------------
26
50
  # init_embedder (was MemoryEngine._init_embedder + helpers)
27
51
  # ---------------------------------------------------------------------------
@@ -552,29 +576,12 @@ def init_retrieval(
552
576
  # trust in slm health / slm doctor output.
553
577
  if reranker is not None:
554
578
  import threading
555
- def _log_warmup_status() -> None:
556
- ready = reranker.warmup_sync(timeout=180)
557
- if ready:
558
- logger.info("Cross-encoder reranker warm and ready")
559
- return
560
- # warmup_sync returned False. Could be (a) singleton held by
561
- # another process (benign), or (b) actual model load failure.
562
- # Disambiguate by probing the singleton PID file.
563
- try:
564
- from superlocalmemory.retrieval.reranker import _is_reranker_worker_alive
565
- if _is_reranker_worker_alive():
566
- logger.info(
567
- "Cross-encoder reranker worker held by another process "
568
- "(machine-wide singleton — usually the unified daemon); "
569
- "this process will route reranking through that worker"
570
- )
571
- return
572
- except Exception:
573
- pass
574
- logger.warning(
575
- "Cross-encoder reranker warmup failed — recalls will use fallback scoring"
576
- )
577
- t = threading.Thread(target=_log_warmup_status, daemon=True, name="ce-init-warmup")
579
+ t = threading.Thread(
580
+ target=_log_reranker_warmup_status,
581
+ args=(reranker,),
582
+ daemon=True,
583
+ name="ce-init-warmup",
584
+ )
578
585
  t.start()
579
586
 
580
587
  # Phase A: Register forgetting filter into the channel registry
@@ -613,6 +620,8 @@ def wire_hooks(
613
620
  ctx.get("agent_id", "unknown"), ctx.get("profile_id", profile_id)))
614
621
  hooks.register_pre("delete", lambda ctx: gate.check_delete(
615
622
  ctx.get("agent_id", "unknown"), ctx.get("profile_id", profile_id)))
623
+ hooks.register_pre("update", lambda ctx: gate.check_write(
624
+ ctx.get("agent_id", "unknown"), ctx.get("profile_id", profile_id)))
616
625
 
617
626
  # -- Post-store hooks (async, never block) --
618
627
  if trust_scorer: