memleaf 0.2.82__tar.gz → 0.2.84__tar.gz

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 (178) hide show
  1. {memleaf-0.2.82 → memleaf-0.2.84}/CHANGELOG.md +44 -0
  2. {memleaf-0.2.82/src/memleaf.egg-info → memleaf-0.2.84}/PKG-INFO +9 -6
  3. {memleaf-0.2.82 → memleaf-0.2.84}/README.en.md +8 -5
  4. {memleaf-0.2.82 → memleaf-0.2.84}/README.md +8 -5
  5. memleaf-0.2.84/docs/hermes-source-comparison-context.md +60 -0
  6. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-planner-preview.md +30 -0
  7. memleaf-0.2.84/docs/memory-lifecycle-tools.md +26 -0
  8. {memleaf-0.2.82 → memleaf-0.2.84}/pyproject.toml +1 -1
  9. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/__init__.py +1 -1
  10. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/capture.py +39 -1
  11. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/_provider.py +176 -9
  12. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  13. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/inbox.py +3 -0
  14. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_preview.py +15 -3
  15. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_prompts.py +7 -4
  16. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_protocol.py +63 -6
  17. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_recovery.py +3 -1
  18. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/mcp_server.py +71 -8
  19. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/retrieval_gate.py +19 -0
  20. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/service.py +4 -0
  21. {memleaf-0.2.82 → memleaf-0.2.84/src/memleaf.egg-info}/PKG-INFO +9 -6
  22. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf.egg-info/SOURCES.txt +2 -0
  23. {memleaf-0.2.82 → memleaf-0.2.84}/LICENSE +0 -0
  24. {memleaf-0.2.82 → memleaf-0.2.84}/MANIFEST.in +0 -0
  25. {memleaf-0.2.82 → memleaf-0.2.84}/docs/acceptance-evidence-guide.md +0 -0
  26. {memleaf-0.2.82 → memleaf-0.2.84}/docs/automatic-processing-route.md +0 -0
  27. {memleaf-0.2.82 → memleaf-0.2.84}/docs/body-compaction.md +0 -0
  28. {memleaf-0.2.82 → memleaf-0.2.84}/docs/candidate-reconciliation.md +0 -0
  29. {memleaf-0.2.82 → memleaf-0.2.84}/docs/capture-budget-design.md +0 -0
  30. {memleaf-0.2.82 → memleaf-0.2.84}/docs/config-migrations.md +0 -0
  31. {memleaf-0.2.82 → memleaf-0.2.84}/docs/core-refactor.md +0 -0
  32. {memleaf-0.2.82 → memleaf-0.2.84}/docs/development-closeout.md +0 -0
  33. {memleaf-0.2.82 → memleaf-0.2.84}/docs/evidence-retention.md +0 -0
  34. {memleaf-0.2.82 → memleaf-0.2.84}/docs/explicit-memory-update.md +0 -0
  35. {memleaf-0.2.82 → memleaf-0.2.84}/docs/extraction-latency.md +0 -0
  36. {memleaf-0.2.82 → memleaf-0.2.84}/docs/failed-run-recovery.md +0 -0
  37. {memleaf-0.2.82 → memleaf-0.2.84}/docs/gate-evidence-boundary.md +0 -0
  38. {memleaf-0.2.82 → memleaf-0.2.84}/docs/general-processing.md +0 -0
  39. {memleaf-0.2.82 → memleaf-0.2.84}/docs/hermes-mcp-runtime.md +0 -0
  40. {memleaf-0.2.82 → memleaf-0.2.84}/docs/hermes-provider-compatibility.md +0 -0
  41. {memleaf-0.2.82 → memleaf-0.2.84}/docs/host-acceptance-runbook.md +0 -0
  42. {memleaf-0.2.82 → memleaf-0.2.84}/docs/immutable-release-assets.md +0 -0
  43. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-commit-contract.md +0 -0
  44. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-execution.md +0 -0
  45. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-native-comparison.md +0 -0
  46. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-partial-recovery.md +0 -0
  47. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-runtime-contract.md +0 -0
  48. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-scope-registration.md +0 -0
  49. {memleaf-0.2.82 → memleaf-0.2.84}/docs/incremental-selected-retention.md +0 -0
  50. {memleaf-0.2.82 → memleaf-0.2.84}/docs/installed-artifact-verification.md +0 -0
  51. {memleaf-0.2.82 → memleaf-0.2.84}/docs/memory-state-contract.md +0 -0
  52. {memleaf-0.2.82 → memleaf-0.2.84}/docs/migration-preflight.md +0 -0
  53. {memleaf-0.2.82 → memleaf-0.2.84}/docs/processing-quality-acceptance.md +0 -0
  54. {memleaf-0.2.82 → memleaf-0.2.84}/docs/public-query-integrity.md +0 -0
  55. {memleaf-0.2.82 → memleaf-0.2.84}/docs/query-performance.md +0 -0
  56. {memleaf-0.2.82 → memleaf-0.2.84}/docs/remember-incremental-route.md +0 -0
  57. {memleaf-0.2.82 → memleaf-0.2.84}/docs/runtime-state-retention.md +0 -0
  58. {memleaf-0.2.82 → memleaf-0.2.84}/docs/semantic-extraction-protocol.md +0 -0
  59. {memleaf-0.2.82 → memleaf-0.2.84}/docs/source-work-contract.md +0 -0
  60. {memleaf-0.2.82 → memleaf-0.2.84}/docs/v0.2.26-processing-status.md +0 -0
  61. {memleaf-0.2.82 → memleaf-0.2.84}/examples/README.md +0 -0
  62. {memleaf-0.2.82 → memleaf-0.2.84}/examples/basic_usage.py +0 -0
  63. {memleaf-0.2.82 → memleaf-0.2.84}/examples/incremental_acceptance.json +0 -0
  64. {memleaf-0.2.82 → memleaf-0.2.84}/examples/mcp_stdio.ndjson +0 -0
  65. {memleaf-0.2.82 → memleaf-0.2.84}/examples/query_benchmark.py +0 -0
  66. {memleaf-0.2.82 → memleaf-0.2.84}/install.ps1 +0 -0
  67. {memleaf-0.2.82 → memleaf-0.2.84}/install.sh +0 -0
  68. {memleaf-0.2.82 → memleaf-0.2.84}/setup.cfg +0 -0
  69. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/__main__.py +0 -0
  70. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/acceptance.py +0 -0
  71. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/adapters/__init__.py +0 -0
  72. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/adapters/antigravity.py +0 -0
  73. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/adapters/base.py +0 -0
  74. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/adapters/codex.py +0 -0
  75. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/adapters/hermes.py +0 -0
  76. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/admission.py +0 -0
  77. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/batch_review.py +0 -0
  78. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/budget.py +0 -0
  79. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/cli.py +0 -0
  80. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/compaction.py +0 -0
  81. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/config.py +0 -0
  82. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/create_coordinator.py +0 -0
  83. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/credentials.py +0 -0
  84. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/evidence_budget.py +0 -0
  85. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/evidence_policy.py +0 -0
  86. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/evidence_structure.py +0 -0
  87. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/evidence_syntax.py +0 -0
  88. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/explicit_text_source.py +0 -0
  89. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/extraction_budget.py +0 -0
  90. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/extraction_capability.py +0 -0
  91. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/extraction_work_state.py +0 -0
  92. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/frontmatter.py +0 -0
  93. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/README.md +0 -0
  94. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/__init__.py +0 -0
  95. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  96. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/_shared.py +0 -0
  97. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  98. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  99. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/hermes_runtime.py +0 -0
  100. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/host_events.py +0 -0
  101. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/host_runtime.py +0 -0
  102. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_commit.py +0 -0
  103. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_dates.py +0 -0
  104. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_execution.py +0 -0
  105. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_explicit.py +0 -0
  106. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_failed_recovery.py +0 -0
  107. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_journal.py +0 -0
  108. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_native.py +0 -0
  109. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_partial.py +0 -0
  110. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_run_state.py +0 -0
  111. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_runtime.py +0 -0
  112. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_scopes.py +0 -0
  113. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/incremental_selection.py +0 -0
  114. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/index.py +0 -0
  115. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/inspection.py +0 -0
  116. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/installer.py +0 -0
  117. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/legacy_update_journal.py +0 -0
  118. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/__init__.py +0 -0
  119. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/base.py +0 -0
  120. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/claude_compatible.py +0 -0
  121. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/gemini.py +0 -0
  122. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/openai_compatible.py +0 -0
  123. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/router.py +0 -0
  124. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/llm/thinking.py +0 -0
  125. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/locking.py +0 -0
  126. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/memory_commit.py +0 -0
  127. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/memory_planner.py +0 -0
  128. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/memory_retraction.py +0 -0
  129. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/memory_update.py +0 -0
  130. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/memory_writer.py +0 -0
  131. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/migration.py +0 -0
  132. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/model_capabilities.py +0 -0
  133. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/model_discovery.py +0 -0
  134. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/model_execution.py +0 -0
  135. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/models.py +0 -0
  136. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/native_index.py +0 -0
  137. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/native_registration.py +0 -0
  138. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/parallel_model.py +0 -0
  139. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/planning_context.py +0 -0
  140. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/process_common.py +0 -0
  141. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/process_jobs.py +0 -0
  142. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/process_journal.py +0 -0
  143. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/process_owner.py +0 -0
  144. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/processing.py +0 -0
  145. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/processing_route.py +0 -0
  146. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/prompts.py +0 -0
  147. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/provenance.py +0 -0
  148. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/provider_compatibility.py +0 -0
  149. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/query_clock.py +0 -0
  150. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/query_progress.py +0 -0
  151. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/query_scan.py +0 -0
  152. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/receipt_codec.py +0 -0
  153. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/recording_policy.py +0 -0
  154. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/redaction.py +0 -0
  155. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/remember_route.py +0 -0
  156. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/retention.py +0 -0
  157. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/retrieval.py +0 -0
  158. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/runtime_retention.py +0 -0
  159. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/scope_maintenance.py +0 -0
  160. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/scope_state.py +0 -0
  161. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/semantic_maintenance.py +0 -0
  162. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/semantic_protocol.py +0 -0
  163. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/single_pass_memory_planner.py +0 -0
  164. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/single_pass_plan.py +0 -0
  165. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/source_policy.py +0 -0
  166. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/state_layout.py +0 -0
  167. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/subprocess_flags.py +0 -0
  168. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/summary_batch.py +0 -0
  169. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/target_reconciliation.py +0 -0
  170. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/turn_audit.py +0 -0
  171. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/turn_plan.py +0 -0
  172. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/update_coordinator.py +0 -0
  173. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/update_review.py +0 -0
  174. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/validation.py +0 -0
  175. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf/vault.py +0 -0
  176. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf.egg-info/dependency_links.txt +0 -0
  177. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf.egg-info/entry_points.txt +0 -0
  178. {memleaf-0.2.82 → memleaf-0.2.84}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -4,6 +4,50 @@ All notable changes to memleaf are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.84 — 2026-09-30
8
+
9
+ ### Task lifecycle, Hermes source times and read-linked comparison
10
+
11
+ - Expose revision-bound MCP `update_memory` for completion, cancellation and
12
+ reopening; retain the memory body, other fields and version history. Both MCP
13
+ permanent-forgetting tools now require strict `confirm_delete=true`.
14
+ - Verify Hermes' native file-mutation footer as an exact display transformation
15
+ instead of losing both message times. Verify legacy correction rows against
16
+ their native scaffold and full merge format; capture each source event with
17
+ its own timestamp. Unknown inputs remain unknown.
18
+ - Recognize a repeated source delivery before consuming another same-text turn;
19
+ copy messages at callback invocation and retain the queued retrieval identity.
20
+ - Freeze successful current-turn read IDs in assistant capture metadata and
21
+ immutable receipts. Prioritize those targets for automatic extraction; reject
22
+ mismatched source/session/turn context and block missing or over-budget targets.
23
+ Recover captured context after Inbox/receipt interruption even if the gate expires.
24
+ - Require structured deadline maintenance before NO_CHANGE when current evidence
25
+ verifies an existing textual deadline whose date is absent or unresolved.
26
+ Advance semantic protocol to v11 without changing legacy snapshot field contracts.
27
+ - No production memory migration or automatic retry of historical runs is performed.
28
+
29
+ ## 0.2.83 — 2026-09-30
30
+
31
+ ### Reviewed extraction fields and existing-item maintenance
32
+
33
+ - Compare established identifiers and other facts against existing unknowns
34
+ before NO_CHANGE, including shared project facts when creating an independent
35
+ task. Project/task identity remains model-owned; Core does not merge titles.
36
+ - Add the `field-reviewed-v1` request contract: CREATE/UPDATE explicitly decide
37
+ whether to select a structural deadline or leave it absent/unchanged. CREATE
38
+ explicitly records unknown executors as null; changed non-null responsibility
39
+ fields require a verifiable source quote.
40
+ - Preserve selected deadline and responsibility provenance. Unknown CREATE
41
+ responsibility and unchanged inherited responsibility do not advance its basis.
42
+ Project existing `actionable` metadata and clarify new/context boundaries.
43
+ - Advance semantic protocol to v10. Legacy snapshot digests remain readable;
44
+ repair retains its original field contract and replan uses the current one.
45
+ Existing runtime protocol gates still apply to historical partial runs.
46
+ - Validation: 86 focused checks, six real-model synthetic scenarios and six
47
+ isolated capture/commit/repeated-replay scenarios passed. The 182-check local
48
+ comparison has nine pre-existing date assertions failing on both versions,
49
+ with no new failures. No formal Vault or historical run was changed.
50
+
7
51
  ## 0.2.82 — 2026-09-30
8
52
 
9
53
  ### Hermes installation tool discovery
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.82
3
+ Version: 0.2.84
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -23,10 +23,10 @@ Dynamic: license-file
23
23
 
24
24
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
25
25
 
26
- > **版本:0.2.82。**
26
+ > **版本:0.2.84。**
27
27
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
- > 提炼契约现在只有一份中文语义契约(旧版英文 B3 提示词已删除),首轮、主题选择、候选修复和 `semantic_maintenance` 使用同一套保留价值判断;同一件事只写一次。
29
- > Core 不再因为归属、日期、任务依据或跨项目而丢弃整条候选:归属缺失会归一到证据里唯一被点名的项目、否则落 `global`;日期只接受能在本候选证据里找到出处、且能锚定成 ISO 的写法(`明天`、`周五`、`月底` 都会被解析),无法解析时只丢掉日期字段,记忆照常写入;未获授权的改归属沿用目标原归属。
28
+ > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。Core 不按标题强行合并。
29
+ > 新请求使用 `field-reviewed-v1`:CREATE/UPDATE 显式判断结构化期限;未知执行人填 `null`,新增或变更非空负责人必须引用来源原文。Core 校验字段和来源,模型判断业务含义;非法行进入现有 partial 处理。来源时间用于解析相对日期,不补造事实日期。
30
30
  > 待办 `due_date` 只表示待办动作本身明确声明的截止日;属于待办主题或预期结果的日期不会被误作截止日。项目归属由模型按语义判断,不要求项目名在证据里逐字出现。复核阶段会保留首轮已选定的更新目标,避免“状态变更”被降级成新增记忆。Markdown 仍是唯一事实源。
31
31
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
32
32
 
@@ -283,11 +283,14 @@ python -m memleaf.mcp_server --vault "$HOME/.memleaf"
283
283
  | `process_status` | 只读查询异步处理 job 状态 |
284
284
  | `recover_failed_run` | 预览并显式恢复终态传输失败,保留原请求预算 |
285
285
  | `remember` | 用户明确要求时创建或更新记忆 |
286
- | `forget_memory` | 按精确 ID 删除一条记忆 |
287
- | `forget_about` | 忘记明确主题;有歧义时只返回候选 |
286
+ | `update_memory` | 携带当前 revision 办结、取消或重新激活任务,保留正文与历史 |
287
+ | `forget_memory` | 用户明确要求遗忘时,携带 `confirm_delete=true` 按精确 ID 永久删除 |
288
+ | `forget_about` | 携带 `confirm_delete=true` 遗忘明确主题;有歧义时只返回候选 |
288
289
  | `rebuild_index` | 重建可重建的本地派生索引 |
289
290
  | `stats` | 返回 Vault 计数和诊断统计 |
290
291
 
292
+ 任务办结与永久遗忘的接口和兼容性见 [生命周期工具](docs/memory-lifecycle-tools.md);来源时间及本轮比较目标的捕获边界见 [Hermes 来源与比较上下文](docs/hermes-source-comparison-context.md)。
293
+
291
294
  `search` 的候选只是线索,标题不能单独作为事实依据。受管理检索必须使用同一条检索链的 `retrieval_id` 完成 `search → read`。Hermes/Codex Hook 可直接提供该 token;只有 MCP 的客户端可在第一条 `search` / `list_todos` 省略 token,并使用返回值继续分页和读取。带 cursor 的后续页以及所有 `read` 都不能省略 `retrieval_id`。工具错误、Scope 冲突或读取失败都应如实处理。
292
295
 
293
296
  ## Python API
@@ -4,10 +4,10 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.2.82.**
7
+ > **Version: 0.2.84.**
8
8
  > Automatic extraction and review share one future-value standard: the model weighs likely reuse, information gain, direct usability when read again, and the cost of forgetting. It keeps only the smallest core that can materially help future understanding, decisions, or actions; information without clear value is not extracted, and no business-specific exclusion rule is hard-coded.
9
- > A single Chinese semantic contract is sent for extraction, topic selection, candidate repair, and `semantic_maintenance`; the older English B3 prompt has been deleted and B3 remains only as a compatibility read format. One topic is written once.
10
- > Core no longer discards a whole candidate over ownership, dates, task basis or cross-project wording. A missing owner normalizes to the single project named by that candidate's own evidence, otherwise `global`. A deadline is accepted only when it appears in the candidate's own evidence and can be anchored to ISO (`明天`, `周五`, `月底` all resolve); when it cannot, only the date field is dropped and the memory is still written. An unauthorized ownership change keeps the target's existing scope.
9
+ > Extraction compares existing identifiers, objectives, progress, responsibility and deadlines first. Filling an unknown also maintains the original item; a separate task does not replace updating shared project facts. Core does not merge matching titles.
10
+ > New requests use `field-reviewed-v1`: CREATE/UPDATE explicitly decide structural deadlines; unknown executors use `null`, and new or changed non-null responsibility requires a source quote. Core validates fields and provenance while the model judges business meaning; invalid rows use existing partial handling. Source time resolves relative dates without inventing factual dates.
11
11
  > A todo's `due_date` means only the explicit deadline of the todo action; dates describing its subject or desired outcome are not deadlines. Project ownership remains the model's semantic judgement and needs no literal name match. The review stage now keeps the update target chosen by the first pass, so a state change is no longer downgraded into a new memory. Markdown remains the sole source of truth.
12
12
  > **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
13
13
 
@@ -264,11 +264,14 @@ The server exposes the following tools:
264
264
  | `process_status` | Read the status of a background processing job |
265
265
  | `recover_failed_run` | Preview and explicitly recover terminal transport failures within the original request budget |
266
266
  | `remember` | Create or update memory after an explicit request |
267
- | `forget_memory` | Delete one memory by exact ID |
268
- | `forget_about` | Forget an unambiguous topic; return candidates when ambiguous |
267
+ | `update_memory` | Complete, cancel or reopen a task using its current revision; retain body and history |
268
+ | `forget_memory` | Permanently delete an exact ID after explicit user forgetting, with `confirm_delete=true` |
269
+ | `forget_about` | Forget an unambiguous topic with `confirm_delete=true`; return candidates when ambiguous |
269
270
  | `rebuild_index` | Rebuild local derived indexes |
270
271
  | `stats` | Return Vault counts and diagnostic statistics |
271
272
 
273
+ See [Lifecycle tools](docs/memory-lifecycle-tools.md) and [Hermes source and comparison context](docs/hermes-source-comparison-context.md) for operation and capture boundaries.
274
+
272
275
  Search results are clues; a title alone is not a business fact. Managed retrieval must use the same `retrieval_id` for `search → read`. Tool errors, Scope conflicts, retrieval-turn violations, and read page/version errors must be handled as such.
273
276
 
274
277
  ## Python API
@@ -4,10 +4,10 @@
4
4
 
5
5
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **版本:0.2.82。**
7
+ > **版本:0.2.84。**
8
8
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
9
- > 提炼契约现在只有一份中文语义契约(旧版英文 B3 提示词已删除),首轮、主题选择、候选修复和 `semantic_maintenance` 使用同一套保留价值判断;同一件事只写一次。
10
- > Core 不再因为归属、日期、任务依据或跨项目而丢弃整条候选:归属缺失会归一到证据里唯一被点名的项目、否则落 `global`;日期只接受能在本候选证据里找到出处、且能锚定成 ISO 的写法(`明天`、`周五`、`月底` 都会被解析),无法解析时只丢掉日期字段,记忆照常写入;未获授权的改归属沿用目标原归属。
9
+ > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。Core 不按标题强行合并。
10
+ > 新请求使用 `field-reviewed-v1`:CREATE/UPDATE 显式判断结构化期限;未知执行人填 `null`,新增或变更非空负责人必须引用来源原文。Core 校验字段和来源,模型判断业务含义;非法行进入现有 partial 处理。来源时间用于解析相对日期,不补造事实日期。
11
11
  > 待办 `due_date` 只表示待办动作本身明确声明的截止日;属于待办主题或预期结果的日期不会被误作截止日。项目归属由模型按语义判断,不要求项目名在证据里逐字出现。复核阶段会保留首轮已选定的更新目标,避免“状态变更”被降级成新增记忆。Markdown 仍是唯一事实源。
12
12
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
13
13
 
@@ -264,11 +264,14 @@ python -m memleaf.mcp_server --vault "$HOME/.memleaf"
264
264
  | `process_status` | 只读查询异步处理 job 状态 |
265
265
  | `recover_failed_run` | 预览并显式恢复终态传输失败,保留原请求预算 |
266
266
  | `remember` | 用户明确要求时创建或更新记忆 |
267
- | `forget_memory` | 按精确 ID 删除一条记忆 |
268
- | `forget_about` | 忘记明确主题;有歧义时只返回候选 |
267
+ | `update_memory` | 携带当前 revision 办结、取消或重新激活任务,保留正文与历史 |
268
+ | `forget_memory` | 用户明确要求遗忘时,携带 `confirm_delete=true` 按精确 ID 永久删除 |
269
+ | `forget_about` | 携带 `confirm_delete=true` 遗忘明确主题;有歧义时只返回候选 |
269
270
  | `rebuild_index` | 重建可重建的本地派生索引 |
270
271
  | `stats` | 返回 Vault 计数和诊断统计 |
271
272
 
273
+ 任务办结与永久遗忘的接口和兼容性见 [生命周期工具](docs/memory-lifecycle-tools.md);来源时间及本轮比较目标的捕获边界见 [Hermes 来源与比较上下文](docs/hermes-source-comparison-context.md)。
274
+
272
275
  `search` 的候选只是线索,标题不能单独作为事实依据。受管理检索必须使用同一条检索链的 `retrieval_id` 完成 `search → read`。Hermes/Codex Hook 可直接提供该 token;只有 MCP 的客户端可在第一条 `search` / `list_todos` 省略 token,并使用返回值继续分页和读取。带 cursor 的后续页以及所有 `read` 都不能省略 `retrieval_id`。工具错误、Scope 冲突或读取失败都应如实处理。
273
276
 
274
277
  ## Python API
@@ -0,0 +1,60 @@
1
+ # Hermes source and comparison context
2
+
3
+ ## Message times
4
+
5
+ `source_time` comes from the corresponding message's `source_time` or
6
+ `timestamp`. Missing or invalid values stay unknown. Capturing or processing
7
+ an event does not create a source time.
8
+
9
+ The legacy callback checks the final assistant and the nearest user. It has no
10
+ 32-message lookback limit. It does not borrow a matching user from an older turn.
11
+
12
+ Some Hermes versions persist a reply before adding their file-mutation verifier
13
+ footer. The Provider accepts that display transformation only when the installed
14
+ Hermes formatter reconstructs the entire footer from uniquely associated,
15
+ current-turn file-tool receipts and the full resulting reply matches. Arbitrary
16
+ prefixes, suffixes, unsupported formatters and ambiguous receipts remain unknown.
17
+ Tool receipts supply no times or memory facts. The displayed reply remains the
18
+ captured assistant content.
19
+
20
+ For legacy corrections, the Provider verifies the real user rows, native replay
21
+ scaffold and the exact full merged callback string. It never splits arbitrary
22
+ user text by a separator. A bounded chain must have a queued original admission;
23
+ then each source row is captured separately with its own time. It does not assign
24
+ the final correction's time to the combined user text. A repeated source delivery
25
+ is recognized before consuming another same-text admission. Host-v1 admission
26
+ snapshots remain the preferred interface for compaction and transformations that
27
+ legacy callbacks cannot verify. The list is copied at callback invocation; this
28
+ cannot recover a host snapshot already lost before the callback arrives.
29
+
30
+ ## Successful reads as comparison targets
31
+
32
+ Hermes uses the retrieval identity frozen at `on_turn_start` for final capture.
33
+ The assistant capture includes `retrieval_id` and `retrieval_turn_id`; the latter
34
+ must equal its `turn_id`. Core checks that the existing gate belongs to exactly
35
+ the declared source, session and turn. It never resolves a session's latest gate
36
+ on behalf of an older callback. Live search and read still reject stale tokens.
37
+
38
+ Core freezes only successfully read memory IDs into `comparison_context` on the
39
+ assistant event, including the immutable capture receipt and payload digest.
40
+ It accepts no caller-supplied list of IDs through this interface. The capsule is
41
+ comparison metadata, not factual evidence, and its token is not projected into
42
+ the model request. Automatic preparation gives these required targets priority
43
+ alongside explicit-write receipts before filling the bounded candidate budget.
44
+ A missing target or insufficient budget blocks preparation instead of silently
45
+ replacing or dropping the required target. No model call is added to capture.
46
+
47
+ A delayed callback can use its own unexpired gate even after a later turn starts.
48
+ Once the capsule is captured, gate expiry cannot erase it or break a duplicate
49
+ capture. If Inbox was saved but receipt persistence failed, retries first recover
50
+ the capsule from Inbox before consulting a live gate. An uncaptured, expired or
51
+ mismatched gate cannot supply comparison context.
52
+
53
+ The extraction prompt also requires structured deadline maintenance before
54
+ NO_CHANGE when current evidence makes an existing unstructured deadline
55
+ verifiable. Unchanged wording does not mean a missing due_date is complete.
56
+ This rule advances the semantic protocol to v11.
57
+
58
+ This supplies the model with the old targets. It does not force semantic UPDATE,
59
+ merge similar titles, or automatically alter historical memories. Recovery of
60
+ existing production runs and deleted records is a separate operation.
@@ -59,6 +59,36 @@ CREATE proposals have no permanent ID. UPDATE proposals carry the original
59
59
 
60
60
  The version is `incremental-items-v1`; only `{"items": [...]}` is accepted.
61
61
 
62
+ New prepared requests also carry `extraction_contract=field-reviewed-v1`:
63
+
64
+ - Every CREATE/UPDATE includes row-level `deadline_decision`. Use `selected`
65
+ with `memory.deadline` for CREATE or `patch.deadline` for UPDATE; use `none`
66
+ for CREATE without an established deadline, or `unchanged` for UPDATE without
67
+ a deadline change. A deadline mentioned only in prose is insufficient.
68
+ - CREATE explicitly supplies `memory.assignee`, using null when the executor
69
+ is unknown. New or changed non-null `assignee`/`waiting_on` requires row-level
70
+ `responsibility_basis`, keyed by that field, with `{ref, text}` citing an exact
71
+ source quote in the row's evidence. An unchanged inherited owner needs no new
72
+ quote. Field provenance uses the selected source rather than the generic row
73
+ observation; merely inheriting responsibility does not advance its basis.
74
+ - The model compares established identifiers and other new facts against old
75
+ unknowns before choosing NO_CHANGE, including project facts when it also
76
+ creates an independent task. Existing `actionable` metadata is projected.
77
+
78
+ Core checks explicit choices, shape and source provenance; it does not classify
79
+ the meaning of a quote or automatically merge similar titles. Model semantic
80
+ errors remain possible. Invalid rows become unresolved through the existing
81
+ partial handling, without an additional model dispatch.
82
+
83
+ Frozen snapshots without this marker retain their original contract and digest.
84
+ Partial repair retains the original contract; replanning uses the current one.
85
+ The semantic request version advances so a pre-commit request prepared under an
86
+ older prompt cannot silently continue under this new prompt.
87
+ These snapshot rules preserve capsule reading and reconstruction; the existing
88
+ runtime protocol gate still applies. An older partial run may return
89
+ `partial_protocol_upgrade_required` before repair/replan; this change does not
90
+ authorize continuing that historical run across protocol versions.
91
+
62
92
  | action | Required fields besides action/evidence | Behavior |
63
93
  |---|---|---|
64
94
  | CREATE | memory.type/scope/title/body; todo also status | Independent new assertion; Core defaults validity=valid |
@@ -0,0 +1,26 @@
1
+ # 任务办结与永久遗忘
2
+
3
+ MCP `update_memory` 用于完成、取消或重新打开现有事项。它调用 Core 的 revision-bound 更新接口,保留原正文、未修改字段及历史版本,不调用模型。任务办结不等于删除记忆。
4
+
5
+ 先通过 `search` / `list_todos` 和 `read` 确认目标。使用 `read` 返回的 `revision`,而不是分页用的 `version`:
6
+
7
+ ```json
8
+ {
9
+ "memory_id": "mem-example",
10
+ "expected_revision": "<read 返回的 revision>",
11
+ "patch": {"status": "completed"}
12
+ }
13
+ ```
14
+
15
+ - 已完成或关闭的事项使用 `completed`,放弃的事项使用 `cancelled`。
16
+ - 本 MCP 接口仅接受 `patch.status`,不修改正文、类型或范围。
17
+ - 重新打开事项使用 `patch.status=active` 和 `reopen=true`,应有用户明确重新打开的要求。
18
+ - 只有明确知道事项的完成事件时间时才传 `source_time`(带时区的 ISO 时间);完成时会保存为 `completed_at`。未知时省略,不使用当前执行时间补造完成时间。
19
+ - 相同请求可以安全重试,不重复创建历史。旧 revision 的不同修改会被拒绝,应重新读取目标。
20
+ - 混合了项目事实和待办的记忆,办结只改变状态,仍保留项目事实。
21
+
22
+ `forget_memory` 与 `forget_about` 永久删除选中的记忆及其历史,没有“删除后收档”的历史副本。两个 MCP 工具均要求 `confirm_delete=true`,缺失或不是布尔值 `true` 时服务器拒绝执行。
23
+
24
+ 这个参数只能表示调用方确认用户明确要求永久遗忘。完成、关闭、取消或归档任务不能作为设置它的理由。布尔参数本身不是用户自然语言授权的证明,也不能保证模型永远正确选择工具。需要撤销任务完成状态时,应通过 `update_memory` 重新打开;已永久删除的内容不能用此接口恢复。
25
+
26
+ 这是 MCP 删除接口的兼容性变更:旧客户端应只在已有明确永久遗忘要求时补传 `confirm_delete=true`。Python Core 的 `forget_memory` / `forget_about` 原有删除语义及参数保持不变。
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.82"
7
+ version = "0.2.84"
8
8
  description = "A local-first Markdown memory core for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  """Local-first Markdown memory core for AI agents."""
2
2
 
3
- __version__ = "0.2.82"
3
+ __version__ = "0.2.84"
4
4
 
5
5
  from .config import DEFAULT_CONFIG, default_config, load_config, save_config
6
6
  from .frontmatter import FrontmatterError, dump_frontmatter, dump_yaml, load_yaml, parse_frontmatter
@@ -199,6 +199,7 @@ def _append_event(
199
199
  final: bool | None,
200
200
  tool_evidence: list[dict[str, str]] | None = None,
201
201
  explicit_input: dict[str, Any] | None = None,
202
+ comparison_context: dict[str, Any] | None = None,
202
203
  ) -> str:
203
204
  if not existing:
204
205
  existing = _new_session_text(source, session_id, captured_at)
@@ -232,6 +233,8 @@ def _append_event(
232
233
  metadata["final"] = final
233
234
  if explicit_input is not None:
234
235
  metadata["explicit_input"] = explicit_input
236
+ if comparison_context is not None:
237
+ metadata["comparison_context"] = comparison_context
235
238
  if tool_evidence:
236
239
  metadata["tool_evidence"] = [dict(item) for item in tool_evidence]
237
240
  metadata_line = json.dumps(metadata, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
@@ -359,6 +362,8 @@ def _payload_digest(metadata: Mapping[str, Any], content: str) -> str:
359
362
  if metadata.get("explicit_input") is not None:
360
363
  from .explicit_text_source import validate_origin
361
364
  value["explicit_input"] = validate_origin(metadata["explicit_input"])
365
+ if metadata.get("comparison_context") is not None:
366
+ value["comparison_context"] = metadata["comparison_context"]
362
367
  value["content"] = content.rstrip("\n")
363
368
  return hashlib.sha256(json.dumps(value, ensure_ascii=False, sort_keys=True,
364
369
  separators=(",", ":")).encode("utf-8")).hexdigest()
@@ -414,7 +419,7 @@ def recover_capture_receipts_unlocked(vault: Vault, processed: dict, path: Path)
414
419
  turn_key_value=metadata["turn_key"], turn_index=metadata["turn_index"],
415
420
  message_id=metadata["message_id"], message_revision=metadata["message_revision"],
416
421
  )
417
- entry = {field: metadata[field] for field in (*_IMMUTABLE_EVENT_FIELDS, "turn_id", "turn_index", "captured_at", "explicit_input")
422
+ entry = {field: metadata[field] for field in (*_IMMUTABLE_EVENT_FIELDS, "turn_id", "turn_index", "captured_at", "explicit_input", "comparison_context")
418
423
  if field in metadata}
419
424
  entry.update(event_key=key, payload_digest=digest)
420
425
  events[key] = entry
@@ -447,6 +452,8 @@ def capture_event(
447
452
  previous_message_id: Optional[str] = None,
448
453
  final: Optional[bool] = None,
449
454
  _explicit_input: dict[str, Any] | None = None,
455
+ retrieval_id: Optional[str] = None,
456
+ retrieval_turn_id: Optional[str] = None,
450
457
  ) -> CaptureResult:
451
458
  """Capture one visible event; all persisted text is redacted first."""
452
459
 
@@ -478,6 +485,9 @@ def capture_event(
478
485
  # Opting into source envelopes does not implicitly certify completion.
479
486
  # Old callers with no source metadata retain their legacy boundary.
480
487
  final = False
488
+ if (retrieval_id is None) != (retrieval_turn_id is None) or (
489
+ retrieval_id is not None and (role != "assistant" or retrieval_turn_id != turn_id)):
490
+ raise ValueError("invalid_comparison_binding")
481
491
  resolved_event_id, resolved_message_id, resolved_event_key = _event_identity(
482
492
  source, session_id, turn_id, role, event_id, message_id, message_revision
483
493
  )
@@ -488,6 +498,31 @@ def capture_event(
488
498
  if not visible or role not in ("user", "assistant"):
489
499
  return CaptureResult(resolved_event_id, stored=False, duplicate=False, content=safe_content)
490
500
 
501
+ comparison_context = None
502
+ if retrieval_id is not None:
503
+ # Never hold the vault lock while acquiring the retrieval lock: gated
504
+ # reads acquire them in the opposite order. Durable retries reuse their
505
+ # immutable receipt even after the short-lived gate has expired.
506
+ with vault.lock():
507
+ recovered = _read_processed(vault.processed_state_path)
508
+ from .recording_policy import recording_allowed
509
+ comparison_permitted = record and recording_allowed(recovered, source, session_id, resolved_turn_key)
510
+ if comparison_permitted:
511
+ path = vault.session_path(source, session_id)
512
+ if path.is_symlink():
513
+ raise ValueError("unsafe inbox session path")
514
+ if recover_capture_receipts_unlocked(vault, recovered, path):
515
+ atomic_write_json(vault.processed_state_path, recovered)
516
+ receipt = recovered.get("events", {}).get(resolved_event_key, {})
517
+ saved = receipt.get("comparison_context") if isinstance(receipt, Mapping) else None
518
+ if (isinstance(saved, dict) and saved.get("retrieval_id") == retrieval_id
519
+ and saved.get("turn_id") == retrieval_turn_id):
520
+ comparison_context = saved
521
+ elif comparison_permitted:
522
+ from .retrieval_gate import snapshot_read_context
523
+ comparison_context = snapshot_read_context(vault, retrieval_id,
524
+ source=source, session_id=session_id, turn_id=retrieval_turn_id)
525
+
491
526
  with vault.lock():
492
527
  processed = _read_processed(vault.processed_state_path)
493
528
  if _explicit_input is not None:
@@ -526,6 +561,8 @@ def capture_event(
526
561
  "previous_message_revision": previous_message_revision, "source_sequence": source_sequence,
527
562
  "previous_message_id": previous_message_id, "source_time": source_time, "final": final,
528
563
  }
564
+ if retrieval_id is not None:
565
+ incoming["comparison_context"] = comparison_context
529
566
  if _explicit_input is not None:
530
567
  incoming["explicit_input"] = _explicit_input
531
568
  digest = _payload_digest(incoming, safe_content)
@@ -584,6 +621,7 @@ def capture_event(
584
621
  final=final,
585
622
  tool_evidence=safe_tool_evidence,
586
623
  explicit_input=_explicit_input,
624
+ comparison_context=comparison_context,
587
625
  )
588
626
  atomic_write_text(path, updated)
589
627