memleaf 0.2.89__tar.gz → 0.2.90__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 (184) hide show
  1. {memleaf-0.2.89 → memleaf-0.2.90}/CHANGELOG.md +21 -0
  2. {memleaf-0.2.89/src/memleaf.egg-info → memleaf-0.2.90}/PKG-INFO +2 -2
  3. {memleaf-0.2.89 → memleaf-0.2.90}/README.en.md +1 -1
  4. {memleaf-0.2.89 → memleaf-0.2.90}/README.md +1 -1
  5. {memleaf-0.2.89 → memleaf-0.2.90}/docs/memory-state-contract.md +20 -0
  6. {memleaf-0.2.89 → memleaf-0.2.90}/pyproject.toml +1 -1
  7. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/__init__.py +1 -1
  8. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  9. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/mcp_server.py +13 -3
  10. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/retrieval_gate.py +3 -1
  11. memleaf-0.2.90/src/memleaf/retrieval_lifecycle.py +60 -0
  12. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/service.py +13 -4
  13. {memleaf-0.2.89 → memleaf-0.2.90/src/memleaf.egg-info}/PKG-INFO +2 -2
  14. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/SOURCES.txt +1 -0
  15. {memleaf-0.2.89 → memleaf-0.2.90}/LICENSE +0 -0
  16. {memleaf-0.2.89 → memleaf-0.2.90}/MANIFEST.in +0 -0
  17. {memleaf-0.2.89 → memleaf-0.2.90}/docs/acceptance-evidence-guide.md +0 -0
  18. {memleaf-0.2.89 → memleaf-0.2.90}/docs/automatic-processing-route.md +0 -0
  19. {memleaf-0.2.89 → memleaf-0.2.90}/docs/body-compaction.md +0 -0
  20. {memleaf-0.2.89 → memleaf-0.2.90}/docs/candidate-reconciliation.md +0 -0
  21. {memleaf-0.2.89 → memleaf-0.2.90}/docs/capture-budget-design.md +0 -0
  22. {memleaf-0.2.89 → memleaf-0.2.90}/docs/config-migrations.md +0 -0
  23. {memleaf-0.2.89 → memleaf-0.2.90}/docs/core-refactor.md +0 -0
  24. {memleaf-0.2.89 → memleaf-0.2.90}/docs/development-closeout.md +0 -0
  25. {memleaf-0.2.89 → memleaf-0.2.90}/docs/evidence-retention.md +0 -0
  26. {memleaf-0.2.89 → memleaf-0.2.90}/docs/explicit-memory-update.md +0 -0
  27. {memleaf-0.2.89 → memleaf-0.2.90}/docs/extraction-latency.md +0 -0
  28. {memleaf-0.2.89 → memleaf-0.2.90}/docs/failed-run-recovery.md +0 -0
  29. {memleaf-0.2.89 → memleaf-0.2.90}/docs/gate-evidence-boundary.md +0 -0
  30. {memleaf-0.2.89 → memleaf-0.2.90}/docs/general-processing.md +0 -0
  31. {memleaf-0.2.89 → memleaf-0.2.90}/docs/hermes-mcp-runtime.md +0 -0
  32. {memleaf-0.2.89 → memleaf-0.2.90}/docs/hermes-provider-compatibility.md +0 -0
  33. {memleaf-0.2.89 → memleaf-0.2.90}/docs/hermes-source-comparison-context.md +0 -0
  34. {memleaf-0.2.89 → memleaf-0.2.90}/docs/host-acceptance-runbook.md +0 -0
  35. {memleaf-0.2.89 → memleaf-0.2.90}/docs/immutable-release-assets.md +0 -0
  36. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-commit-contract.md +0 -0
  37. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-execution.md +0 -0
  38. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-native-comparison.md +0 -0
  39. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-partial-recovery.md +0 -0
  40. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-planner-preview.md +0 -0
  41. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-runtime-contract.md +0 -0
  42. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-scope-registration.md +0 -0
  43. {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-selected-retention.md +0 -0
  44. {memleaf-0.2.89 → memleaf-0.2.90}/docs/installed-artifact-verification.md +0 -0
  45. {memleaf-0.2.89 → memleaf-0.2.90}/docs/memory-lifecycle-tools.md +0 -0
  46. {memleaf-0.2.89 → memleaf-0.2.90}/docs/migration-preflight.md +0 -0
  47. {memleaf-0.2.89 → memleaf-0.2.90}/docs/processing-quality-acceptance.md +0 -0
  48. {memleaf-0.2.89 → memleaf-0.2.90}/docs/public-query-integrity.md +0 -0
  49. {memleaf-0.2.89 → memleaf-0.2.90}/docs/query-performance.md +0 -0
  50. {memleaf-0.2.89 → memleaf-0.2.90}/docs/remember-incremental-route.md +0 -0
  51. {memleaf-0.2.89 → memleaf-0.2.90}/docs/runtime-state-retention.md +0 -0
  52. {memleaf-0.2.89 → memleaf-0.2.90}/docs/semantic-extraction-protocol.md +0 -0
  53. {memleaf-0.2.89 → memleaf-0.2.90}/docs/source-work-contract.md +0 -0
  54. {memleaf-0.2.89 → memleaf-0.2.90}/docs/v0.2.26-processing-status.md +0 -0
  55. {memleaf-0.2.89 → memleaf-0.2.90}/examples/README.md +0 -0
  56. {memleaf-0.2.89 → memleaf-0.2.90}/examples/basic_usage.py +0 -0
  57. {memleaf-0.2.89 → memleaf-0.2.90}/examples/incremental_acceptance.json +0 -0
  58. {memleaf-0.2.89 → memleaf-0.2.90}/examples/mcp_stdio.ndjson +0 -0
  59. {memleaf-0.2.89 → memleaf-0.2.90}/examples/query_benchmark.py +0 -0
  60. {memleaf-0.2.89 → memleaf-0.2.90}/install.ps1 +0 -0
  61. {memleaf-0.2.89 → memleaf-0.2.90}/install.sh +0 -0
  62. {memleaf-0.2.89 → memleaf-0.2.90}/setup.cfg +0 -0
  63. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/__main__.py +0 -0
  64. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/acceptance.py +0 -0
  65. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/__init__.py +0 -0
  66. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/antigravity.py +0 -0
  67. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/base.py +0 -0
  68. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/codex.py +0 -0
  69. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/hermes.py +0 -0
  70. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/admission.py +0 -0
  71. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/batch_review.py +0 -0
  72. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/budget.py +0 -0
  73. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/capture.py +0 -0
  74. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/cli.py +0 -0
  75. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/compaction.py +0 -0
  76. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/config.py +0 -0
  77. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/create_coordinator.py +0 -0
  78. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/credentials.py +0 -0
  79. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_budget.py +0 -0
  80. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_policy.py +0 -0
  81. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_structure.py +0 -0
  82. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_syntax.py +0 -0
  83. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/explicit_text_source.py +0 -0
  84. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/extraction_budget.py +0 -0
  85. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/extraction_capability.py +0 -0
  86. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/extraction_work_state.py +0 -0
  87. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/frontmatter.py +0 -0
  88. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/README.md +0 -0
  89. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/__init__.py +0 -0
  90. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  91. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/_provider.py +0 -0
  92. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/_shared.py +0 -0
  93. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  94. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  95. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_runtime.py +0 -0
  96. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_events.py +0 -0
  97. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_retention.py +0 -0
  98. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_runtime.py +0 -0
  99. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_turn_identity.py +0 -0
  100. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/inbox.py +0 -0
  101. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_commit.py +0 -0
  102. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_dates.py +0 -0
  103. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_execution.py +0 -0
  104. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_explicit.py +0 -0
  105. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_failed_recovery.py +0 -0
  106. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_journal.py +0 -0
  107. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_merge.py +0 -0
  108. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_native.py +0 -0
  109. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_partial.py +0 -0
  110. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_preview.py +0 -0
  111. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_prompts.py +0 -0
  112. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_protocol.py +0 -0
  113. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_recovery.py +0 -0
  114. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_run_state.py +0 -0
  115. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_runtime.py +0 -0
  116. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_scopes.py +0 -0
  117. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_selection.py +0 -0
  118. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_semantics.py +0 -0
  119. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/index.py +0 -0
  120. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/inspection.py +0 -0
  121. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/installer.py +0 -0
  122. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/legacy_update_journal.py +0 -0
  123. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/__init__.py +0 -0
  124. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/base.py +0 -0
  125. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/claude_compatible.py +0 -0
  126. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/gemini.py +0 -0
  127. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/openai_compatible.py +0 -0
  128. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/router.py +0 -0
  129. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/thinking.py +0 -0
  130. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/locking.py +0 -0
  131. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_commit.py +0 -0
  132. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_planner.py +0 -0
  133. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_retraction.py +0 -0
  134. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_update.py +0 -0
  135. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_writer.py +0 -0
  136. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/migration.py +0 -0
  137. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/model_capabilities.py +0 -0
  138. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/model_discovery.py +0 -0
  139. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/model_execution.py +0 -0
  140. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/models.py +0 -0
  141. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/native_index.py +0 -0
  142. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/native_registration.py +0 -0
  143. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/parallel_model.py +0 -0
  144. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/planning_candidates.py +0 -0
  145. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/planning_context.py +0 -0
  146. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_common.py +0 -0
  147. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_jobs.py +0 -0
  148. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_journal.py +0 -0
  149. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_owner.py +0 -0
  150. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/processing.py +0 -0
  151. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/processing_route.py +0 -0
  152. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/prompts.py +0 -0
  153. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/provenance.py +0 -0
  154. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/provider_compatibility.py +0 -0
  155. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/query_clock.py +0 -0
  156. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/query_progress.py +0 -0
  157. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/query_scan.py +0 -0
  158. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/receipt_codec.py +0 -0
  159. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/recording_policy.py +0 -0
  160. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/redaction.py +0 -0
  161. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/remember_route.py +0 -0
  162. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/retention.py +0 -0
  163. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/retrieval.py +0 -0
  164. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/runtime_retention.py +0 -0
  165. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/scope_maintenance.py +0 -0
  166. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/scope_state.py +0 -0
  167. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/semantic_maintenance.py +0 -0
  168. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/semantic_protocol.py +0 -0
  169. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/single_pass_memory_planner.py +0 -0
  170. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/single_pass_plan.py +0 -0
  171. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/source_policy.py +0 -0
  172. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/state_layout.py +0 -0
  173. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/subprocess_flags.py +0 -0
  174. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/summary_batch.py +0 -0
  175. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/target_reconciliation.py +0 -0
  176. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/turn_audit.py +0 -0
  177. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/turn_plan.py +0 -0
  178. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/update_coordinator.py +0 -0
  179. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/update_review.py +0 -0
  180. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/validation.py +0 -0
  181. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/vault.py +0 -0
  182. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/dependency_links.txt +0 -0
  183. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/entry_points.txt +0 -0
  184. {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -4,6 +4,27 @@ All notable changes to memleaf are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.90 — 2026-10-05
8
+
9
+ ### Expose withdrawal state in explicit historical retrieval
10
+
11
+ - Include matching retracted current heads when historical retrieval is
12
+ explicitly requested. Ordinary retrieval continues to exclude them.
13
+ - Project bounded lifecycle facts through search and paged MCP reads: the
14
+ historical snapshot flag, exact current identity link, invalidation reason,
15
+ and current validity from the same validated query scan. Keep missing,
16
+ unreadable, conflicting or ambiguous links unknown; support legacy links
17
+ without guessing from titles or content.
18
+ - Preserve archived validity and distinguish past withdrawal from later
19
+ restoration. Associate successful lifecycle-only reads with the captured
20
+ turn's comparison context without fabricating body characters.
21
+ - Reuse query scans and existing pagination, permissions and version checks.
22
+ No model stage, model call, tool or persistence format is added.
23
+ - Validation: 97 affected checks passed, plus an isolated real-conversation
24
+ replay through a source MCP process. One legacy test still expects the
25
+ previously removed waiting_on field and fails identically on the prior
26
+ release. Replay used zero model calls and left the formal Vault unchanged.
27
+
7
28
  ## 0.2.89 — 2026-10-04
8
29
 
9
30
  ### Complete comparison context and bound tool continuation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.89
3
+ Version: 0.2.90
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.89。**
26
+ > **版本:0.2.90。**
27
27
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
28
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
29
29
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
@@ -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.89.**
7
+ > **Version: 0.2.90.**
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. Confirmed duplicates can be merged in the existing processing requests with their sources and history preserved. Independent project and task lifecycles remain separate; Core does not merge matching titles.
10
10
  > New requests accept equivalent field representations. Unknown executors remain `null`; new or changed responsibility and deadlines require actual source evidence. Dependencies belong in the body; `waiting_on` is no longer extracted. Proposed actions and related fields undergo bounded semantic verification within a shared five-request limit per work item, stopping on success; failed verification blocks writes. Source time resolves relative dates without inventing factual dates.
@@ -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.89。**
7
+ > **版本:0.2.90。**
8
8
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
9
9
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
10
10
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
@@ -83,6 +83,26 @@ Ordinary `read`, `read_page`, `search`, `context`, and `list_todos` exclude retr
83
83
 
84
84
  The derived active index excludes retracted heads. History indexing remains available only through APIs that explicitly include history. Closed todos remain in `knowledge/`; status filtering, rather than age-based deletion, controls their normal presentation.
85
85
 
86
+ For explicit historical questions, `search_candidates(include_history=True)` and
87
+ MCP `search` also expose matching retracted current heads. Candidate entries and
88
+ `read_page`/MCP `read` with `include_history=True` carry a bounded `lifecycle`:
89
+
90
+ - `historical` distinguishes an archived snapshot from a current head.
91
+ - `active_memory_id`, when present, is the stored exact current-identity link.
92
+ - `invalidated_reason`, when present, describes why that snapshot was archived.
93
+ - `current_validity` comes from the validated current head in the same query
94
+ observation. It is `null` when the linked head is missing, unreadable or
95
+ ambiguous; conflicting or missing legacy links are not inferred from titles.
96
+
97
+ An archived snapshot's own `validity=valid` describes its past assertion. A past
98
+ `invalidated_reason=retracted` does not establish current withdrawal after a
99
+ later restoration. Use `lifecycle.current_validity` for that distinction. The
100
+ projection contains no current body and grants no edit authority. Ordinary
101
+ retrieval remains unchanged and excludes retracted assertions. Historical body
102
+ pages retain their existing explicit history permission and version checks.
103
+ Successful lifecycle-only reads also associate the exact ID with the captured
104
+ turn's comparison context; an empty assertion does not fabricate body characters.
105
+
86
106
  ## Compatibility boundary
87
107
 
88
108
  `closed_todo_retention_days` remains accepted so existing config files continue to load, but it no longer retires the stable current identity. Optional structured fields stored in frontmatter participate in the protected revision. The incremental model protocol supports UPDATE.patch.validity and an explicit restore with current content. The legacy planner still excludes retracted heads; its older protocol is not silently extended. Deterministic Python retraction and explicit update use the same current identity and protected revision semantics.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.89"
7
+ version = "0.2.90"
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.89"
3
+ __version__ = "0.2.90"
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
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.2.89
2
+ version: 0.2.90
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch
@@ -244,6 +244,8 @@ _TOOLS: tuple[dict[str, Any], ...] = (
244
244
  "MCP client may omit it only on the first page; memleaf returns a short-lived retrieval_id "
245
245
  "for pagination/read. Errors are failures, never no_match. Managed MCP search is "
246
246
  "directory-only; view=full is rejected. "
247
+ "For explicit history/withdrawal questions use include_history=true. Results include lifecycle: "
248
+ "historical snapshot, linked current ID/validity and invalidation reason; null current validity is unknown. "
247
249
  "Python search(view='full') remains the compatibility interface."
248
250
  ),
249
251
  "inputSchema": _object_schema(
@@ -303,9 +305,10 @@ _TOOLS: tuple[dict[str, Any], ...] = (
303
305
  "name": "read",
304
306
  "description": (
305
307
  "Read one memory by exact memory_id in pages. Each page is capped at 2000 body "
306
- "characters and contains only identifiers, scopes, body, and paging/version fields; "
308
+ "characters with bounded content, state, and paging/version fields; "
307
309
  "retrieval_id is required, and the retrieval turn must have a current FOUND search; "
308
310
  "NO_MATCH, ERROR, and DEGRADED turns are rejected. "
311
+ "Read historical or retracted records with include_history=true; lifecycle distinguishes past invalidation from current validity. "
309
312
  "Prefer returned body_continuation: pass only continue=true and retrieval_id to read the next unfinished body, with its ID/version/offset held by the server. "
310
313
  "On memory_version_changed, add restart=true to that continuation to restart the same bound memory at offset 0 with its new version. "
311
314
  "continue with next_offset only when has_more is true, passing the returned version "
@@ -693,6 +696,9 @@ def _read_page_result(value: Any) -> dict[str, Any] | None:
693
696
  result[name] = field
694
697
  if "due_anchor" in value:
695
698
  result["due_anchor"] = _jsonable(value.get("due_anchor"))
699
+ if "lifecycle" in value:
700
+ from .retrieval_lifecycle import project_lifecycle
701
+ result["lifecycle"] = project_lifecycle(value["lifecycle"])
696
702
  result.update(_query_metadata(value))
697
703
  if "read_accounting" in value:
698
704
  if not isinstance(value["read_accounting"], str) or value["read_accounting"] not in {"counted", "not_counted", "unavailable"}:
@@ -734,7 +740,7 @@ def _search_result(value: Any) -> dict[str, Any]:
734
740
  entries = value.get("results")
735
741
  if not isinstance(entries, list):
736
742
  raise ValueError("invalid search results")
737
- results: list[dict[str, str]] = []
743
+ results: list[dict[str, Any]] = []
738
744
  for entry in entries:
739
745
  if not isinstance(entry, Mapping):
740
746
  raise ValueError("invalid search entry")
@@ -744,7 +750,11 @@ def _search_result(value: Any) -> dict[str, Any]:
744
750
  raise ValueError("invalid search identifiers")
745
751
  # Search is intentionally narrower than the legacy directory helper:
746
752
  # do not leak per-memory scope/tags/aliases/keywords into candidates.
747
- results.append({"memory_id": memory_id, "title": title})
753
+ row = {"memory_id": memory_id, "title": title}
754
+ if "lifecycle" in entry:
755
+ from .retrieval_lifecycle import project_lifecycle
756
+ row["lifecycle"] = project_lifecycle(entry["lifecycle"])
757
+ results.append(row)
748
758
  if bool(results) != (value["status"] == "found"):
749
759
  raise ValueError("inconsistent search results")
750
760
  paging = _paging_fields(value)
@@ -762,7 +762,9 @@ def guarded_read(
762
762
  elif prior is not None:
763
763
  pending.pop(prior)
764
764
  entry["body_continuations"] = pending
765
- if body:
765
+ # A verified empty tombstone still supplies lifecycle facts and must
766
+ # remain available to the captured turn's comparison context.
767
+ if body or isinstance(result.get("lifecycle"), Mapping):
766
768
  if memory_id not in read_ids:
767
769
  read_ids.append(memory_id)
768
770
  entry["read_ids"] = read_ids
@@ -0,0 +1,60 @@
1
+ """Bounded lifecycle facts for explicitly requested historical retrieval."""
2
+ from collections.abc import Mapping
3
+ from typing import Any
4
+
5
+ from .query_scan import QueryScan, ScanRecord
6
+ from .vault import safe_component
7
+
8
+
9
+ def current_validities(snapshot: QueryScan) -> dict[str, str]:
10
+ # Only validated, unambiguous current records can establish current state.
11
+ unavailable = snapshot.ambiguous | {i.identity.casefold() for i in snapshot.issues if i.identity}
12
+ return {r.memory.memory_id: r.memory.validity
13
+ for r in snapshot.area("knowledge")
14
+ if r.memory.memory_id.casefold() not in unavailable}
15
+
16
+
17
+ def lifecycle(record: ScanRecord, current: Mapping[str, str]) -> dict[str, Any]:
18
+ historical = record.area == "history"
19
+ result = {"historical": historical,
20
+ "current_validity": None if historical else record.memory.validity}
21
+ if not historical:
22
+ return result
23
+ extra = record.memory.extra
24
+ links = [extra[k] for k in ("active_memory_id", "original_memory_id") if k in extra]
25
+ if links and all(isinstance(v, str) and len(v) <= 800 and v == links[0] for v in links):
26
+ try:
27
+ identity = safe_component(links[0], "active memory id")
28
+ except ValueError:
29
+ pass
30
+ else:
31
+ result.update(active_memory_id=identity, current_validity=current.get(identity))
32
+ reason = extra.get("invalidated_reason")
33
+ if isinstance(reason, str) and 0 < len(reason) <= 120:
34
+ result["invalidated_reason"] = reason
35
+ return result
36
+
37
+
38
+ def project_lifecycle(value: Any) -> dict[str, Any]:
39
+ """Do not pass arbitrary archive extras through the MCP boundary."""
40
+ if (not isinstance(value, Mapping)
41
+ or not {"historical", "current_validity"} <= set(value)
42
+ or set(value) - {"historical", "current_validity", "active_memory_id", "invalidated_reason"}
43
+ or type(value["historical"]) is not bool
44
+ or value["current_validity"] not in (None, "valid", "retracted")):
45
+ raise ValueError("invalid retrieval lifecycle")
46
+ result = dict(value)
47
+ if not value["historical"] and (value["current_validity"] is None
48
+ or set(value) != {"historical", "current_validity"}):
49
+ raise ValueError("invalid current lifecycle")
50
+ if value["historical"] and value["current_validity"] is not None and "active_memory_id" not in value:
51
+ raise ValueError("current validity requires an exact archive link")
52
+ if "active_memory_id" in value:
53
+ if not isinstance(value["active_memory_id"], str) or len(value["active_memory_id"]) > 800:
54
+ raise ValueError("invalid active memory id")
55
+ safe_component(value["active_memory_id"], "active memory id")
56
+ if "invalidated_reason" in value:
57
+ reason = value["invalidated_reason"]
58
+ if not isinstance(reason, str) or not 0 < len(reason) <= 120:
59
+ raise ValueError("invalid invalidation reason")
60
+ return result
@@ -58,6 +58,7 @@ from .scope_state import (
58
58
  )
59
59
  from .vault import Vault, safe_component
60
60
  from .query_scan import QueryScan, ScanRecord, scan_memories, ensure_scan_current
61
+ from .retrieval_lifecycle import current_validities, lifecycle
61
62
  from .query_progress import observe_progress
62
63
 
63
64
 
@@ -989,7 +990,9 @@ class Memleaf:
989
990
  revision=revision_digest(memory),
990
991
  structured_fields=memory.extra,
991
992
  record=record,
992
- ), **self._query_envelope(snapshot, scope=memory.scopes)}
993
+ ), **({"lifecycle": lifecycle(record, current_validities(snapshot))}
994
+ if include_history else {}),
995
+ **self._query_envelope(snapshot, scope=memory.scopes)}
993
996
 
994
997
  from .native_index import NativeIndexer
995
998
 
@@ -1157,8 +1160,8 @@ class Memleaf:
1157
1160
 
1158
1161
  @staticmethod
1159
1162
  def _candidate_directory(record: _Record) -> dict[str, Any]:
1160
- # The finalized retrieval contract deliberately keeps candidate
1161
- # entries to memory_id + title. Scope selection already happened
1163
+ # Ordinary candidate entries remain memory_id + title. Explicit
1164
+ # historical queries add lifecycle separately. Scope selection happened
1162
1165
  # before search via the bounded Scope Map and search input.
1163
1166
  display = directory_entry(record.memory)
1164
1167
  return {
@@ -1259,6 +1262,8 @@ class Memleaf:
1259
1262
  ) -> dict[str, Any]:
1260
1263
  """Search and return only a bounded directory of candidate memories."""
1261
1264
 
1265
+ if type(include_history) is not bool:
1266
+ raise ValueError("include_history must be a boolean")
1262
1267
  page_limit = self._page_limit(
1263
1268
  limit,
1264
1269
  MAX_SEARCH_CANDIDATE_ITEMS,
@@ -1289,6 +1294,7 @@ class Memleaf:
1289
1294
  limit=None,
1290
1295
  stable=True,
1291
1296
  strict_candidates=True,
1297
+ include_retracted=include_history,
1292
1298
  query_snapshot=snapshot,
1293
1299
  )
1294
1300
  records = [
@@ -1296,7 +1302,10 @@ class Memleaf:
1296
1302
  for record in records
1297
1303
  if candidate_matches_query(record.memory, query_value)
1298
1304
  ]
1299
- candidates = [self._candidate_directory(record) for record in records]
1305
+ current = current_validities(snapshot) if include_history else {}
1306
+ candidates = [{**self._candidate_directory(record),
1307
+ **({"lifecycle": lifecycle(record, current)} if include_history else {})}
1308
+ for record in records]
1300
1309
  fingerprint = self._search_fingerprint(
1301
1310
  records,
1302
1311
  query=query_value,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.89
3
+ Version: 0.2.90
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.89。**
26
+ > **版本:0.2.90。**
27
27
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
28
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
29
29
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
@@ -135,6 +135,7 @@ src/memleaf/remember_route.py
135
135
  src/memleaf/retention.py
136
136
  src/memleaf/retrieval.py
137
137
  src/memleaf/retrieval_gate.py
138
+ src/memleaf/retrieval_lifecycle.py
138
139
  src/memleaf/runtime_retention.py
139
140
  src/memleaf/scope_maintenance.py
140
141
  src/memleaf/scope_state.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes