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
@@ -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
+ ]
@@ -613,6 +613,8 @@ def wire_hooks(
613
613
  ctx.get("agent_id", "unknown"), ctx.get("profile_id", profile_id)))
614
614
  hooks.register_pre("delete", lambda ctx: gate.check_delete(
615
615
  ctx.get("agent_id", "unknown"), ctx.get("profile_id", profile_id)))
616
+ hooks.register_pre("update", lambda ctx: gate.check_write(
617
+ ctx.get("agent_id", "unknown"), ctx.get("profile_id", profile_id)))
616
618
 
617
619
  # -- Post-store hooks (async, never block) --
618
620
  if trust_scorer: