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
@@ -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: