memleaf 0.2.83__tar.gz → 0.2.85__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.83 → memleaf-0.2.85}/CHANGELOG.md +34 -0
  2. {memleaf-0.2.83/src/memleaf.egg-info → memleaf-0.2.85}/PKG-INFO +7 -4
  3. {memleaf-0.2.83 → memleaf-0.2.85}/README.en.md +6 -3
  4. {memleaf-0.2.83 → memleaf-0.2.85}/README.md +6 -3
  5. memleaf-0.2.85/docs/hermes-source-comparison-context.md +60 -0
  6. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-planner-preview.md +9 -6
  7. memleaf-0.2.85/docs/memory-lifecycle-tools.md +26 -0
  8. {memleaf-0.2.83 → memleaf-0.2.85}/pyproject.toml +1 -1
  9. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/__init__.py +1 -1
  10. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/capture.py +39 -1
  11. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/_provider.py +176 -9
  12. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  13. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/inbox.py +3 -0
  14. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_preview.py +13 -1
  15. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_prompts.py +7 -6
  16. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_protocol.py +8 -7
  17. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/mcp_server.py +71 -8
  18. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/retrieval_gate.py +19 -0
  19. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/service.py +4 -0
  20. {memleaf-0.2.83 → memleaf-0.2.85/src/memleaf.egg-info}/PKG-INFO +7 -4
  21. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf.egg-info/SOURCES.txt +2 -0
  22. {memleaf-0.2.83 → memleaf-0.2.85}/LICENSE +0 -0
  23. {memleaf-0.2.83 → memleaf-0.2.85}/MANIFEST.in +0 -0
  24. {memleaf-0.2.83 → memleaf-0.2.85}/docs/acceptance-evidence-guide.md +0 -0
  25. {memleaf-0.2.83 → memleaf-0.2.85}/docs/automatic-processing-route.md +0 -0
  26. {memleaf-0.2.83 → memleaf-0.2.85}/docs/body-compaction.md +0 -0
  27. {memleaf-0.2.83 → memleaf-0.2.85}/docs/candidate-reconciliation.md +0 -0
  28. {memleaf-0.2.83 → memleaf-0.2.85}/docs/capture-budget-design.md +0 -0
  29. {memleaf-0.2.83 → memleaf-0.2.85}/docs/config-migrations.md +0 -0
  30. {memleaf-0.2.83 → memleaf-0.2.85}/docs/core-refactor.md +0 -0
  31. {memleaf-0.2.83 → memleaf-0.2.85}/docs/development-closeout.md +0 -0
  32. {memleaf-0.2.83 → memleaf-0.2.85}/docs/evidence-retention.md +0 -0
  33. {memleaf-0.2.83 → memleaf-0.2.85}/docs/explicit-memory-update.md +0 -0
  34. {memleaf-0.2.83 → memleaf-0.2.85}/docs/extraction-latency.md +0 -0
  35. {memleaf-0.2.83 → memleaf-0.2.85}/docs/failed-run-recovery.md +0 -0
  36. {memleaf-0.2.83 → memleaf-0.2.85}/docs/gate-evidence-boundary.md +0 -0
  37. {memleaf-0.2.83 → memleaf-0.2.85}/docs/general-processing.md +0 -0
  38. {memleaf-0.2.83 → memleaf-0.2.85}/docs/hermes-mcp-runtime.md +0 -0
  39. {memleaf-0.2.83 → memleaf-0.2.85}/docs/hermes-provider-compatibility.md +0 -0
  40. {memleaf-0.2.83 → memleaf-0.2.85}/docs/host-acceptance-runbook.md +0 -0
  41. {memleaf-0.2.83 → memleaf-0.2.85}/docs/immutable-release-assets.md +0 -0
  42. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-commit-contract.md +0 -0
  43. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-execution.md +0 -0
  44. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-native-comparison.md +0 -0
  45. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-partial-recovery.md +0 -0
  46. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-runtime-contract.md +0 -0
  47. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-scope-registration.md +0 -0
  48. {memleaf-0.2.83 → memleaf-0.2.85}/docs/incremental-selected-retention.md +0 -0
  49. {memleaf-0.2.83 → memleaf-0.2.85}/docs/installed-artifact-verification.md +0 -0
  50. {memleaf-0.2.83 → memleaf-0.2.85}/docs/memory-state-contract.md +0 -0
  51. {memleaf-0.2.83 → memleaf-0.2.85}/docs/migration-preflight.md +0 -0
  52. {memleaf-0.2.83 → memleaf-0.2.85}/docs/processing-quality-acceptance.md +0 -0
  53. {memleaf-0.2.83 → memleaf-0.2.85}/docs/public-query-integrity.md +0 -0
  54. {memleaf-0.2.83 → memleaf-0.2.85}/docs/query-performance.md +0 -0
  55. {memleaf-0.2.83 → memleaf-0.2.85}/docs/remember-incremental-route.md +0 -0
  56. {memleaf-0.2.83 → memleaf-0.2.85}/docs/runtime-state-retention.md +0 -0
  57. {memleaf-0.2.83 → memleaf-0.2.85}/docs/semantic-extraction-protocol.md +0 -0
  58. {memleaf-0.2.83 → memleaf-0.2.85}/docs/source-work-contract.md +0 -0
  59. {memleaf-0.2.83 → memleaf-0.2.85}/docs/v0.2.26-processing-status.md +0 -0
  60. {memleaf-0.2.83 → memleaf-0.2.85}/examples/README.md +0 -0
  61. {memleaf-0.2.83 → memleaf-0.2.85}/examples/basic_usage.py +0 -0
  62. {memleaf-0.2.83 → memleaf-0.2.85}/examples/incremental_acceptance.json +0 -0
  63. {memleaf-0.2.83 → memleaf-0.2.85}/examples/mcp_stdio.ndjson +0 -0
  64. {memleaf-0.2.83 → memleaf-0.2.85}/examples/query_benchmark.py +0 -0
  65. {memleaf-0.2.83 → memleaf-0.2.85}/install.ps1 +0 -0
  66. {memleaf-0.2.83 → memleaf-0.2.85}/install.sh +0 -0
  67. {memleaf-0.2.83 → memleaf-0.2.85}/setup.cfg +0 -0
  68. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/__main__.py +0 -0
  69. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/acceptance.py +0 -0
  70. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/adapters/__init__.py +0 -0
  71. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/adapters/antigravity.py +0 -0
  72. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/adapters/base.py +0 -0
  73. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/adapters/codex.py +0 -0
  74. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/adapters/hermes.py +0 -0
  75. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/admission.py +0 -0
  76. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/batch_review.py +0 -0
  77. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/budget.py +0 -0
  78. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/cli.py +0 -0
  79. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/compaction.py +0 -0
  80. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/config.py +0 -0
  81. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/create_coordinator.py +0 -0
  82. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/credentials.py +0 -0
  83. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/evidence_budget.py +0 -0
  84. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/evidence_policy.py +0 -0
  85. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/evidence_structure.py +0 -0
  86. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/evidence_syntax.py +0 -0
  87. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/explicit_text_source.py +0 -0
  88. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/extraction_budget.py +0 -0
  89. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/extraction_capability.py +0 -0
  90. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/extraction_work_state.py +0 -0
  91. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/frontmatter.py +0 -0
  92. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/README.md +0 -0
  93. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/__init__.py +0 -0
  94. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  95. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/_shared.py +0 -0
  96. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  97. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  98. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/hermes_runtime.py +0 -0
  99. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/host_events.py +0 -0
  100. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/host_runtime.py +0 -0
  101. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_commit.py +0 -0
  102. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_dates.py +0 -0
  103. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_execution.py +0 -0
  104. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_explicit.py +0 -0
  105. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_failed_recovery.py +0 -0
  106. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_journal.py +0 -0
  107. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_native.py +0 -0
  108. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_partial.py +0 -0
  109. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_recovery.py +0 -0
  110. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_run_state.py +0 -0
  111. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_runtime.py +0 -0
  112. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_scopes.py +0 -0
  113. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/incremental_selection.py +0 -0
  114. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/index.py +0 -0
  115. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/inspection.py +0 -0
  116. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/installer.py +0 -0
  117. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/legacy_update_journal.py +0 -0
  118. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/__init__.py +0 -0
  119. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/base.py +0 -0
  120. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/claude_compatible.py +0 -0
  121. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/gemini.py +0 -0
  122. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/openai_compatible.py +0 -0
  123. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/router.py +0 -0
  124. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/llm/thinking.py +0 -0
  125. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/locking.py +0 -0
  126. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/memory_commit.py +0 -0
  127. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/memory_planner.py +0 -0
  128. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/memory_retraction.py +0 -0
  129. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/memory_update.py +0 -0
  130. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/memory_writer.py +0 -0
  131. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/migration.py +0 -0
  132. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/model_capabilities.py +0 -0
  133. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/model_discovery.py +0 -0
  134. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/model_execution.py +0 -0
  135. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/models.py +0 -0
  136. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/native_index.py +0 -0
  137. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/native_registration.py +0 -0
  138. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/parallel_model.py +0 -0
  139. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/planning_context.py +0 -0
  140. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/process_common.py +0 -0
  141. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/process_jobs.py +0 -0
  142. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/process_journal.py +0 -0
  143. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/process_owner.py +0 -0
  144. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/processing.py +0 -0
  145. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/processing_route.py +0 -0
  146. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/prompts.py +0 -0
  147. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/provenance.py +0 -0
  148. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/provider_compatibility.py +0 -0
  149. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/query_clock.py +0 -0
  150. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/query_progress.py +0 -0
  151. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/query_scan.py +0 -0
  152. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/receipt_codec.py +0 -0
  153. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/recording_policy.py +0 -0
  154. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/redaction.py +0 -0
  155. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/remember_route.py +0 -0
  156. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/retention.py +0 -0
  157. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/retrieval.py +0 -0
  158. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/runtime_retention.py +0 -0
  159. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/scope_maintenance.py +0 -0
  160. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/scope_state.py +0 -0
  161. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/semantic_maintenance.py +0 -0
  162. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/semantic_protocol.py +0 -0
  163. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/single_pass_memory_planner.py +0 -0
  164. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/single_pass_plan.py +0 -0
  165. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/source_policy.py +0 -0
  166. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/state_layout.py +0 -0
  167. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/subprocess_flags.py +0 -0
  168. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/summary_batch.py +0 -0
  169. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/target_reconciliation.py +0 -0
  170. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/turn_audit.py +0 -0
  171. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/turn_plan.py +0 -0
  172. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/update_coordinator.py +0 -0
  173. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/update_review.py +0 -0
  174. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/validation.py +0 -0
  175. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf/vault.py +0 -0
  176. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf.egg-info/dependency_links.txt +0 -0
  177. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf.egg-info/entry_points.txt +0 -0
  178. {memleaf-0.2.83 → memleaf-0.2.85}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -4,6 +4,40 @@ All notable changes to memleaf are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.85 — 2026-10-03
8
+
9
+ ### Incremental field protocol
10
+
11
+ - Stop requiring redundant model `deadline_decision` assertions; absent UPDATE
12
+ deadline fields preserve the current deadline. Older assertions remain checked
13
+ for contradictions, and selected/cancelled deadlines still require source quotes.
14
+ - Allow CREATE to omit an unknown assignee and store null without inventing a
15
+ responsibility observation. Non-null responsibility changes still require proof.
16
+ - Consolidate the model field instructions and examples without extra model calls;
17
+ advance the semantic protocol to `incremental-turn-v12` for cached-run guards.
18
+
19
+ ## 0.2.84 — 2026-09-30
20
+
21
+ ### Task lifecycle, Hermes source times and read-linked comparison
22
+
23
+ - Expose revision-bound MCP `update_memory` for completion, cancellation and
24
+ reopening; retain the memory body, other fields and version history. Both MCP
25
+ permanent-forgetting tools now require strict `confirm_delete=true`.
26
+ - Verify Hermes' native file-mutation footer as an exact display transformation
27
+ instead of losing both message times. Verify legacy correction rows against
28
+ their native scaffold and full merge format; capture each source event with
29
+ its own timestamp. Unknown inputs remain unknown.
30
+ - Recognize a repeated source delivery before consuming another same-text turn;
31
+ copy messages at callback invocation and retain the queued retrieval identity.
32
+ - Freeze successful current-turn read IDs in assistant capture metadata and
33
+ immutable receipts. Prioritize those targets for automatic extraction; reject
34
+ mismatched source/session/turn context and block missing or over-budget targets.
35
+ Recover captured context after Inbox/receipt interruption even if the gate expires.
36
+ - Require structured deadline maintenance before NO_CHANGE when current evidence
37
+ verifies an existing textual deadline whose date is absent or unresolved.
38
+ Advance semantic protocol to v11 without changing legacy snapshot field contracts.
39
+ - No production memory migration or automatic retry of historical runs is performed.
40
+
7
41
  ## 0.2.83 — 2026-09-30
8
42
 
9
43
  ### Reviewed extraction fields and existing-item maintenance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.83
3
+ Version: 0.2.85
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ 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.83。**
26
+ > **版本:0.2.85。**
27
27
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
28
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。Core 不按标题强行合并。
29
29
  > 新请求使用 `field-reviewed-v1`:CREATE/UPDATE 显式判断结构化期限;未知执行人填 `null`,新增或变更非空负责人必须引用来源原文。Core 校验字段和来源,模型判断业务含义;非法行进入现有 partial 处理。来源时间用于解析相对日期,不补造事实日期。
@@ -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,7 +4,7 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.2.83.**
7
+ > **Version: 0.2.85.**
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
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
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.
@@ -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,7 +4,7 @@
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.83。**
7
+ > **版本:0.2.85。**
8
8
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
9
9
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。Core 不按标题强行合并。
10
10
  > 新请求使用 `field-reviewed-v1`:CREATE/UPDATE 显式判断结构化期限;未知执行人填 `null`,新增或变更非空负责人必须引用来源原文。Core 校验字段和来源,模型判断业务含义;非法行进入现有 partial 处理。来源时间用于解析相对日期,不补造事实日期。
@@ -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.
@@ -61,12 +61,15 @@ The version is `incremental-items-v1`; only `{"items": [...]}` is accepted.
61
61
 
62
62
  New prepared requests also carry `extraction_contract=field-reviewed-v1`:
63
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
64
+ - CREATE supplies `memory.deadline` only for an established deadline. UPDATE
65
+ supplies `patch.deadline` for a change or explicit cancellation; omission
66
+ preserves the current deadline. The model does not need `deadline_decision`.
67
+ Older outputs may retain it, but contradictions with the actual fields are
68
+ still rejected. A deadline mentioned only in prose is insufficient; Core
69
+ does not infer a deadline from the body or detect every semantic omission.
70
+ - CREATE may omit `memory.assignee` when the executor is unknown; Core stores
71
+ null. UPDATE omission preserves the current executor. New or changed non-null
72
+ `assignee`/`waiting_on` requires row-level
70
73
  `responsibility_basis`, keyed by that field, with `{ref, text}` citing an exact
71
74
  source quote in the row's evidence. An unchanged inherited owner needs no new
72
75
  quote. Field provenance uses the selected source rather than the generic row
@@ -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.83"
7
+ version = "0.2.85"
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.83"
3
+ __version__ = "0.2.85"
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