memleaf 0.2.85__tar.gz → 0.2.87__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.85 → memleaf-0.2.87}/CHANGELOG.md +69 -0
  2. {memleaf-0.2.85/src/memleaf.egg-info → memleaf-0.2.87}/PKG-INFO +10 -6
  3. {memleaf-0.2.85 → memleaf-0.2.87}/README.en.md +7 -5
  4. {memleaf-0.2.85 → memleaf-0.2.87}/README.md +9 -5
  5. {memleaf-0.2.85 → memleaf-0.2.87}/examples/incremental_acceptance.json +9 -10
  6. {memleaf-0.2.85 → memleaf-0.2.87}/pyproject.toml +1 -1
  7. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/__init__.py +1 -1
  8. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/acceptance.py +2 -2
  9. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/adapters/hermes.py +52 -0
  10. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/compaction.py +1 -1
  11. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/extraction_budget.py +3 -0
  12. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/extraction_work_state.py +2 -2
  13. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/README.md +12 -1
  14. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/_provider.py +31 -10
  15. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  16. memleaf-0.2.87/src/memleaf/host_retention.py +183 -0
  17. memleaf-0.2.87/src/memleaf/host_turn_identity.py +33 -0
  18. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_commit.py +42 -4
  19. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_execution.py +96 -27
  20. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_explicit.py +20 -3
  21. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_failed_recovery.py +3 -1
  22. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_journal.py +3 -0
  23. memleaf-0.2.87/src/memleaf/incremental_merge.py +70 -0
  24. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_partial.py +6 -1
  25. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_preview.py +5 -28
  26. memleaf-0.2.87/src/memleaf/incremental_prompts.py +41 -0
  27. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_protocol.py +64 -7
  28. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_recovery.py +4 -1
  29. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_run_state.py +66 -4
  30. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_runtime.py +2 -2
  31. memleaf-0.2.87/src/memleaf/incremental_semantics.py +248 -0
  32. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/installer.py +3 -0
  33. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/mcp_server.py +71 -18
  34. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/memory_writer.py +27 -2
  35. memleaf-0.2.87/src/memleaf/planning_candidates.py +52 -0
  36. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/process_common.py +0 -1
  37. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/processing_route.py +37 -2
  38. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/prompts.py +1 -1
  39. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/query_progress.py +3 -1
  40. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/retrieval_gate.py +16 -1
  41. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/runtime_retention.py +66 -41
  42. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/semantic_maintenance.py +1 -1
  43. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/service.py +1 -2
  44. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/single_pass_plan.py +1 -1
  45. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/source_policy.py +7 -0
  46. {memleaf-0.2.85 → memleaf-0.2.87/src/memleaf.egg-info}/PKG-INFO +10 -6
  47. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf.egg-info/SOURCES.txt +5 -0
  48. memleaf-0.2.85/src/memleaf/incremental_prompts.py +0 -27
  49. {memleaf-0.2.85 → memleaf-0.2.87}/LICENSE +0 -0
  50. {memleaf-0.2.85 → memleaf-0.2.87}/MANIFEST.in +0 -0
  51. {memleaf-0.2.85 → memleaf-0.2.87}/docs/acceptance-evidence-guide.md +0 -0
  52. {memleaf-0.2.85 → memleaf-0.2.87}/docs/automatic-processing-route.md +0 -0
  53. {memleaf-0.2.85 → memleaf-0.2.87}/docs/body-compaction.md +0 -0
  54. {memleaf-0.2.85 → memleaf-0.2.87}/docs/candidate-reconciliation.md +0 -0
  55. {memleaf-0.2.85 → memleaf-0.2.87}/docs/capture-budget-design.md +0 -0
  56. {memleaf-0.2.85 → memleaf-0.2.87}/docs/config-migrations.md +0 -0
  57. {memleaf-0.2.85 → memleaf-0.2.87}/docs/core-refactor.md +0 -0
  58. {memleaf-0.2.85 → memleaf-0.2.87}/docs/development-closeout.md +0 -0
  59. {memleaf-0.2.85 → memleaf-0.2.87}/docs/evidence-retention.md +0 -0
  60. {memleaf-0.2.85 → memleaf-0.2.87}/docs/explicit-memory-update.md +0 -0
  61. {memleaf-0.2.85 → memleaf-0.2.87}/docs/extraction-latency.md +0 -0
  62. {memleaf-0.2.85 → memleaf-0.2.87}/docs/failed-run-recovery.md +0 -0
  63. {memleaf-0.2.85 → memleaf-0.2.87}/docs/gate-evidence-boundary.md +0 -0
  64. {memleaf-0.2.85 → memleaf-0.2.87}/docs/general-processing.md +0 -0
  65. {memleaf-0.2.85 → memleaf-0.2.87}/docs/hermes-mcp-runtime.md +0 -0
  66. {memleaf-0.2.85 → memleaf-0.2.87}/docs/hermes-provider-compatibility.md +0 -0
  67. {memleaf-0.2.85 → memleaf-0.2.87}/docs/hermes-source-comparison-context.md +0 -0
  68. {memleaf-0.2.85 → memleaf-0.2.87}/docs/host-acceptance-runbook.md +0 -0
  69. {memleaf-0.2.85 → memleaf-0.2.87}/docs/immutable-release-assets.md +0 -0
  70. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-commit-contract.md +0 -0
  71. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-execution.md +0 -0
  72. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-native-comparison.md +0 -0
  73. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-partial-recovery.md +0 -0
  74. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-planner-preview.md +0 -0
  75. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-runtime-contract.md +0 -0
  76. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-scope-registration.md +0 -0
  77. {memleaf-0.2.85 → memleaf-0.2.87}/docs/incremental-selected-retention.md +0 -0
  78. {memleaf-0.2.85 → memleaf-0.2.87}/docs/installed-artifact-verification.md +0 -0
  79. {memleaf-0.2.85 → memleaf-0.2.87}/docs/memory-lifecycle-tools.md +0 -0
  80. {memleaf-0.2.85 → memleaf-0.2.87}/docs/memory-state-contract.md +0 -0
  81. {memleaf-0.2.85 → memleaf-0.2.87}/docs/migration-preflight.md +0 -0
  82. {memleaf-0.2.85 → memleaf-0.2.87}/docs/processing-quality-acceptance.md +0 -0
  83. {memleaf-0.2.85 → memleaf-0.2.87}/docs/public-query-integrity.md +0 -0
  84. {memleaf-0.2.85 → memleaf-0.2.87}/docs/query-performance.md +0 -0
  85. {memleaf-0.2.85 → memleaf-0.2.87}/docs/remember-incremental-route.md +0 -0
  86. {memleaf-0.2.85 → memleaf-0.2.87}/docs/runtime-state-retention.md +0 -0
  87. {memleaf-0.2.85 → memleaf-0.2.87}/docs/semantic-extraction-protocol.md +0 -0
  88. {memleaf-0.2.85 → memleaf-0.2.87}/docs/source-work-contract.md +0 -0
  89. {memleaf-0.2.85 → memleaf-0.2.87}/docs/v0.2.26-processing-status.md +0 -0
  90. {memleaf-0.2.85 → memleaf-0.2.87}/examples/README.md +0 -0
  91. {memleaf-0.2.85 → memleaf-0.2.87}/examples/basic_usage.py +0 -0
  92. {memleaf-0.2.85 → memleaf-0.2.87}/examples/mcp_stdio.ndjson +0 -0
  93. {memleaf-0.2.85 → memleaf-0.2.87}/examples/query_benchmark.py +0 -0
  94. {memleaf-0.2.85 → memleaf-0.2.87}/install.ps1 +0 -0
  95. {memleaf-0.2.85 → memleaf-0.2.87}/install.sh +0 -0
  96. {memleaf-0.2.85 → memleaf-0.2.87}/setup.cfg +0 -0
  97. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/__main__.py +0 -0
  98. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/adapters/__init__.py +0 -0
  99. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/adapters/antigravity.py +0 -0
  100. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/adapters/base.py +0 -0
  101. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/adapters/codex.py +0 -0
  102. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/admission.py +0 -0
  103. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/batch_review.py +0 -0
  104. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/budget.py +0 -0
  105. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/capture.py +0 -0
  106. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/cli.py +0 -0
  107. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/config.py +0 -0
  108. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/create_coordinator.py +0 -0
  109. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/credentials.py +0 -0
  110. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/evidence_budget.py +0 -0
  111. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/evidence_policy.py +0 -0
  112. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/evidence_structure.py +0 -0
  113. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/evidence_syntax.py +0 -0
  114. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/explicit_text_source.py +0 -0
  115. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/extraction_capability.py +0 -0
  116. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/frontmatter.py +0 -0
  117. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/__init__.py +0 -0
  118. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  119. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/_shared.py +0 -0
  120. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  121. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  122. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/hermes_runtime.py +0 -0
  123. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/host_events.py +0 -0
  124. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/host_runtime.py +0 -0
  125. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/inbox.py +0 -0
  126. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_dates.py +0 -0
  127. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_native.py +0 -0
  128. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_scopes.py +0 -0
  129. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/incremental_selection.py +0 -0
  130. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/index.py +0 -0
  131. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/inspection.py +0 -0
  132. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/legacy_update_journal.py +0 -0
  133. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/__init__.py +0 -0
  134. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/base.py +0 -0
  135. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/claude_compatible.py +0 -0
  136. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/gemini.py +0 -0
  137. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/openai_compatible.py +0 -0
  138. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/router.py +0 -0
  139. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/llm/thinking.py +0 -0
  140. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/locking.py +0 -0
  141. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/memory_commit.py +0 -0
  142. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/memory_planner.py +0 -0
  143. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/memory_retraction.py +0 -0
  144. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/memory_update.py +0 -0
  145. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/migration.py +0 -0
  146. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/model_capabilities.py +0 -0
  147. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/model_discovery.py +0 -0
  148. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/model_execution.py +0 -0
  149. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/models.py +0 -0
  150. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/native_index.py +0 -0
  151. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/native_registration.py +0 -0
  152. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/parallel_model.py +0 -0
  153. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/planning_context.py +0 -0
  154. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/process_jobs.py +0 -0
  155. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/process_journal.py +0 -0
  156. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/process_owner.py +0 -0
  157. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/processing.py +0 -0
  158. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/provenance.py +0 -0
  159. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/provider_compatibility.py +0 -0
  160. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/query_clock.py +0 -0
  161. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/query_scan.py +0 -0
  162. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/receipt_codec.py +0 -0
  163. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/recording_policy.py +0 -0
  164. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/redaction.py +0 -0
  165. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/remember_route.py +0 -0
  166. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/retention.py +0 -0
  167. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/retrieval.py +0 -0
  168. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/scope_maintenance.py +0 -0
  169. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/scope_state.py +0 -0
  170. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/semantic_protocol.py +0 -0
  171. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/single_pass_memory_planner.py +0 -0
  172. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/state_layout.py +0 -0
  173. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/subprocess_flags.py +0 -0
  174. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/summary_batch.py +0 -0
  175. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/target_reconciliation.py +0 -0
  176. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/turn_audit.py +0 -0
  177. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/turn_plan.py +0 -0
  178. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/update_coordinator.py +0 -0
  179. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/update_review.py +0 -0
  180. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/validation.py +0 -0
  181. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf/vault.py +0 -0
  182. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf.egg-info/dependency_links.txt +0 -0
  183. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf.egg-info/entry_points.txt +0 -0
  184. {memleaf-0.2.85 → memleaf-0.2.87}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -4,6 +4,75 @@ All notable changes to memleaf are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.87 — 2026-10-04
8
+
9
+ ### Shared source-bound maintenance with bounded model cost
10
+
11
+ - Allow up to five actual requests per incremental work item, stopping on
12
+ success. Extraction, format retries, review and recovery share the durable
13
+ allowance; older work retains its smaller creation-time limit.
14
+ - Bind conversational lifecycle updates to the captured host turn. MCP
15
+ `update_memory` no longer accepts model-supplied source times or directly
16
+ commits rewritten facts when host-bound. Preserve the trusted Python API.
17
+ - Retire `waiting_on` from extraction and model/read/list projections; maintain
18
+ dependencies in the current body. Legacy stored data remains readable.
19
+ - Merge confirmed duplicate memories through the existing processing requests.
20
+ Preserve combined facts, provenance and history; reject incompatible targets.
21
+ Commit the survivor before retracting duplicates, with dependency revisions
22
+ checked at the final writer boundary and recoverable frozen operations.
23
+ - Maintain shared facts across related project and task records using the
24
+ existing source-bound review. Independent lifecycles remain separate.
25
+ - Retract withdrawn facts/preferences through validity, retaining their history;
26
+ explicit replacement preferences remain valid. Task cancellation is not a
27
+ substitute for preference withdrawal.
28
+ - Reuse successful, verified full-turn host retention to settle automatic work
29
+ without another model request. Partial selected retention does not claim
30
+ complete automatic coverage. Record actual API token usage without extra
31
+ requests or counting cached replay usage again.
32
+ - Count known provenance overlaps once; mark counts for truncated histories as
33
+ upper bounds when their unseen overlap cannot be reconstructed.
34
+ - Validation: 483 affected regression and installation/acceptance checks passed;
35
+ nine isolated real DeepSeek scenarios and the installed Hermes background
36
+ callback with current source passed. Saved faulty time arguments were rejected
37
+ in an isolated zero-call replay. No production memory migration or historical
38
+ failed-run retry was performed.
39
+
40
+ ## 0.2.86 — 2026-10-04
41
+
42
+ ### Source-bound Hermes retention and general extraction compatibility
43
+
44
+ - Queue Hermes explicit retention against a stable host turn, then use the
45
+ actual captured user messages, IDs, timestamps and order. Register the native
46
+ `memleaf_remember` tool before Provider initialization and configure Hermes to
47
+ exclude the unbound standalone MCP `remember` tool without changing the public
48
+ MCP tool list. Queued retention is not reported as a completed save.
49
+ - Preserve bound intent identity across a unique declared compression lineage.
50
+ Atomically admit one run per intent and check recording authorization across
51
+ the whole lineage before model dispatch, commit and frozen-work recovery.
52
+ Unrelated sessions, stale live tokens and ambiguous lineage remain rejected.
53
+ - Normalize equivalent flat/nested field representations with the same compiler
54
+ and review rules; reject conflicts, retain unknown fields as unknown, and store
55
+ retraction explanations separately from the empty retracted body.
56
+ - Add a bounded semantic veto for proposed actions, responsibility and deadlines.
57
+ It can only reject unsupported proposals or remove unverified fields, never
58
+ invent facts or authorization. Extraction and verification share the existing
59
+ two-request limit; malformed verification blocks writes.
60
+ - Rank existing comparison candidates using generic normalized terms and their
61
+ rarity. Keep scope, revision and candidate bounds; explicit write receipts
62
+ prioritize comparison without skipping independent new information or forcing
63
+ similar-title merges.
64
+ - Maintain proven completed runtime records before admitting new work, using
65
+ existing lossless compaction under the writer lock. Preserve unresolved runs
66
+ and spent allowances. Return specific capacity errors and explicit partial or
67
+ stale-query guidance instead of implying all work completed.
68
+ - Fix history source statistics double counting while retaining exact legacy
69
+ recovery compatibility. No historical memory or failed-run migration occurs.
70
+ - Validation: 587 affected checks, 32 checks in the actual Hermes interpreter,
71
+ 64 isolated real-model turns and 58 developer semantic checks passed. Native
72
+ Hermes MemoryManager/stdio MCP tests covered source times, compression,
73
+ cross-midnight updates and repeated callbacks. Full Desktop acceptance still
74
+ requires installation and a Hermes restart.
75
+
7
76
  ## 0.2.85 — 2026-10-03
8
77
 
9
78
  ### Incremental field protocol
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.85
3
+ Version: 0.2.87
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.85。**
26
+ > **版本:0.2.87。**
27
27
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
- > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。Core 不按标题强行合并。
29
- > 新请求使用 `field-reviewed-v1`:CREATE/UPDATE 显式判断结构化期限;未知执行人填 `null`,新增或变更非空负责人必须引用来源原文。Core 校验字段和来源,模型判断业务含义;非法行进入现有 partial 处理。来源时间用于解析相对日期,不补造事实日期。
28
+ > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
29
+ > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
30
30
  > 待办 `due_date` 只表示待办动作本身明确声明的截止日;属于待办主题或预期结果的日期不会被误作截止日。项目归属由模型按语义判断,不要求项目名在证据里逐字出现。复核阶段会保留首轮已选定的更新目标,避免“状态变更”被降级成新增记忆。Markdown 仍是唯一事实源。
31
31
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
32
32
 
@@ -166,12 +166,14 @@ python -m pip install -U memleaf && python -m memleaf install
166
166
  3. 发现并保存可用的聊天模型路由;Hermes CLI 返回的脱敏凭证不会被当成真实 API key,而会继续尝试环境变量和 Hermes `.env`;已有有效 memleaf 路由会保留;
167
167
  4. 激活 `memory.provider=memleaf`;
168
168
  5. 通过 Hermes 官方 CLI 配置 memleaf MCP;
169
- 6. 配置 MCP lazy/idle 生命周期;
169
+ 6. 配置 MCP lazy/idle 生命周期,以及绑定真实用户消息的 Hermes 原生保存工具;
170
170
  7. 验证 MCP Server 能发现当前版本声明的全部工具;
171
171
  8. 写入本地 Agent 状态索引。
172
172
 
173
173
  完成后重启 Hermes。
174
174
 
175
+ Hermes 显式保存使用原生 `memleaf_remember`:先绑定本轮并排队,轮次完成后再以真实用户消息提炼,排队不表示已保存。安装器会在 Hermes 中屏蔽独立 MCP `remember`,保留其他工具选择;Core/Python API 和其他客户端的 `remember` 继续可用。仅升级 pip 包不足以更新 Provider 和这项工具配置。
176
+
175
177
  如果没有检测到 Hermes、无法取得完整模型路由、Provider 激活失败或 MCP 工具发现验证失败,安装会明确返回失败,不会把未完成的接入报告为成功。
176
178
 
177
179
  仓库中的 `install.sh` 保留给源码开发、离线源码安装和故障排查;普通 PyPI 用户不需要执行它。
@@ -283,7 +285,7 @@ python -m memleaf.mcp_server --vault "$HOME/.memleaf"
283
285
  | `process_status` | 只读查询异步处理 job 状态 |
284
286
  | `recover_failed_run` | 预览并显式恢复终态传输失败,保留原请求预算 |
285
287
  | `remember` | 用户明确要求时创建或更新记忆 |
286
- | `update_memory` | 携带当前 revision 办结、取消或重新激活任务,保留正文与历史 |
288
+ | `update_memory` | 携带当前 revision 维护任务或撤回事实/偏好;宿主绑定的调用排队处理真实消息 |
287
289
  | `forget_memory` | 用户明确要求遗忘时,携带 `confirm_delete=true` 按精确 ID 永久删除 |
288
290
  | `forget_about` | 携带 `confirm_delete=true` 遗忘明确主题;有歧义时只返回候选 |
289
291
  | `rebuild_index` | 重建可重建的本地派生索引 |
@@ -359,6 +361,8 @@ python -m memleaf.mcp_server --vault /path/to/your/vault \
359
361
 
360
362
  捕获、索引、目录检索和读取可离线运行;`process()`、`remember()` 和 `compact()` 需要可用的模型能力。
361
363
 
364
+ 包含行动或责任、期限变更的增量候选通常会追加一次语义复核;每份增量工作最多五次请求,格式修复与恢复也计入同一持久化额度,成功即停止。
365
+
362
366
  `llm.mode` 支持:
363
367
 
364
368
  - `auto`:优先使用显式注入的 host backend,失败后回退到完整 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.85.**
7
+ > **Version: 0.2.87.**
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
- > 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.
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
+ > 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.
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
 
@@ -147,12 +147,14 @@ Both installation paths automatically:
147
147
  3. Discover and save a callable chat-model route. Redacted credentials returned by the Hermes CLI are never treated as real API keys; discovery falls through to environment variables and Hermes `.env`, while preserving an existing valid memleaf route.
148
148
  4. Activate `memory.provider=memleaf`.
149
149
  5. Configure the memleaf MCP entry through Hermes' official CLI.
150
- 6. Configure MCP lazy/idle lifecycle settings.
150
+ 6. Configure MCP lazy/idle lifecycle settings and the source-bound native Hermes retention tool.
151
151
  7. Verify that the MCP server exposes all tools declared by the installed version.
152
152
  8. Record the local Agent integration status.
153
153
 
154
154
  Restart Hermes after installation.
155
155
 
156
+ Hermes uses native `memleaf_remember` to queue a request bound to the current turn, then retain the actual user messages after the turn completes. Queued does not mean saved. The installer excludes standalone MCP `remember` in Hermes while preserving other tool choices; the Core/Python API and other clients keep `remember`. Upgrading only the pip package does not refresh the Provider or this tool configuration.
157
+
156
158
  If Hermes cannot be detected, no complete model route can be configured, Provider activation fails, or MCP tool-discovery verification fails, the installer returns an explicit failure rather than reporting an incomplete integration as successful.
157
159
 
158
160
  The repository `install.sh` remains for source development, offline source installation, and troubleshooting. Normal PyPI users do not need to run it.
@@ -264,7 +266,7 @@ The server exposes the following tools:
264
266
  | `process_status` | Read the status of a background processing job |
265
267
  | `recover_failed_run` | Preview and explicitly recover terminal transport failures within the original request budget |
266
268
  | `remember` | Create or update memory after an explicit request |
267
- | `update_memory` | Complete, cancel or reopen a task using its current revision; retain body and history |
269
+ | `update_memory` | Maintain tasks or retract facts/preferences using their current revision; host-bound calls queue actual source messages |
268
270
  | `forget_memory` | Permanently delete an exact ID after explicit user forgetting, with `confirm_delete=true` |
269
271
  | `forget_about` | Forget an unambiguous topic with `confirm_delete=true`; return candidates when ambiguous |
270
272
  | `rebuild_index` | Rebuild local derived indexes |
@@ -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.85。**
7
+ > **版本:0.2.87。**
8
8
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
9
- > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。Core 不按标题强行合并。
10
- > 新请求使用 `field-reviewed-v1`:CREATE/UPDATE 显式判断结构化期限;未知执行人填 `null`,新增或变更非空负责人必须引用来源原文。Core 校验字段和来源,模型判断业务含义;非法行进入现有 partial 处理。来源时间用于解析相对日期,不补造事实日期。
9
+ > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
10
+ > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
11
11
  > 待办 `due_date` 只表示待办动作本身明确声明的截止日;属于待办主题或预期结果的日期不会被误作截止日。项目归属由模型按语义判断,不要求项目名在证据里逐字出现。复核阶段会保留首轮已选定的更新目标,避免“状态变更”被降级成新增记忆。Markdown 仍是唯一事实源。
12
12
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
13
13
 
@@ -147,12 +147,14 @@ python -m pip install -U memleaf && python -m memleaf install
147
147
  3. 发现并保存可用的聊天模型路由;Hermes CLI 返回的脱敏凭证不会被当成真实 API key,而会继续尝试环境变量和 Hermes `.env`;已有有效 memleaf 路由会保留;
148
148
  4. 激活 `memory.provider=memleaf`;
149
149
  5. 通过 Hermes 官方 CLI 配置 memleaf MCP;
150
- 6. 配置 MCP lazy/idle 生命周期;
150
+ 6. 配置 MCP lazy/idle 生命周期,以及绑定真实用户消息的 Hermes 原生保存工具;
151
151
  7. 验证 MCP Server 能发现当前版本声明的全部工具;
152
152
  8. 写入本地 Agent 状态索引。
153
153
 
154
154
  完成后重启 Hermes。
155
155
 
156
+ Hermes 显式保存使用原生 `memleaf_remember`:先绑定本轮并排队,轮次完成后再以真实用户消息提炼,排队不表示已保存。安装器会在 Hermes 中屏蔽独立 MCP `remember`,保留其他工具选择;Core/Python API 和其他客户端的 `remember` 继续可用。仅升级 pip 包不足以更新 Provider 和这项工具配置。
157
+
156
158
  如果没有检测到 Hermes、无法取得完整模型路由、Provider 激活失败或 MCP 工具发现验证失败,安装会明确返回失败,不会把未完成的接入报告为成功。
157
159
 
158
160
  仓库中的 `install.sh` 保留给源码开发、离线源码安装和故障排查;普通 PyPI 用户不需要执行它。
@@ -264,7 +266,7 @@ python -m memleaf.mcp_server --vault "$HOME/.memleaf"
264
266
  | `process_status` | 只读查询异步处理 job 状态 |
265
267
  | `recover_failed_run` | 预览并显式恢复终态传输失败,保留原请求预算 |
266
268
  | `remember` | 用户明确要求时创建或更新记忆 |
267
- | `update_memory` | 携带当前 revision 办结、取消或重新激活任务,保留正文与历史 |
269
+ | `update_memory` | 携带当前 revision 维护任务或撤回事实/偏好;宿主绑定的调用排队处理真实消息 |
268
270
  | `forget_memory` | 用户明确要求遗忘时,携带 `confirm_delete=true` 按精确 ID 永久删除 |
269
271
  | `forget_about` | 携带 `confirm_delete=true` 遗忘明确主题;有歧义时只返回候选 |
270
272
  | `rebuild_index` | 重建可重建的本地派生索引 |
@@ -340,6 +342,8 @@ python -m memleaf.mcp_server --vault /path/to/your/vault \
340
342
 
341
343
  捕获、索引、目录检索和读取可离线运行;`process()`、`remember()` 和 `compact()` 需要可用的模型能力。
342
344
 
345
+ 包含行动或责任、期限变更的增量候选通常会追加一次语义复核;每份增量工作最多五次请求,格式修复与恢复也计入同一持久化额度,成功即停止。
346
+
343
347
  `llm.mode` 支持:
344
348
 
345
349
  - `auto`:优先使用显式注入的 host backend,失败后回退到完整 API 路由;
@@ -31,7 +31,7 @@
31
31
  "expected": {
32
32
  "execution_status": "completed",
33
33
  "coverage_status": "complete",
34
- "max_calls": 1,
34
+ "max_calls": 2,
35
35
  "count": 1,
36
36
  "required": [
37
37
  {
@@ -215,14 +215,13 @@
215
215
  "expected": {
216
216
  "execution_status": "completed",
217
217
  "coverage_status": "complete",
218
- "max_calls": 1,
218
+ "max_calls": 2,
219
219
  "count": 1,
220
220
  "required": [
221
221
  {
222
222
  "memory_id": "mem-report",
223
223
  "status": "active",
224
224
  "assignee": "林工",
225
- "waiting_on": "客户验收",
226
225
  "due_date": "2026-09-25"
227
226
  }
228
227
  ]
@@ -330,7 +329,7 @@
330
329
  "expected": {
331
330
  "execution_status": "completed",
332
331
  "coverage_status": "complete",
333
- "max_calls": 1,
332
+ "max_calls": 2,
334
333
  "required": [
335
334
  {
336
335
  "memory_id": "mem-report",
@@ -421,7 +420,7 @@
421
420
  "expected": {
422
421
  "execution_status": "completed",
423
422
  "coverage_status": "complete",
424
- "max_calls": 1,
423
+ "max_calls": 2,
425
424
  "count": 2,
426
425
  "required": [
427
426
  {
@@ -471,7 +470,7 @@
471
470
  "expected": {
472
471
  "execution_status": "completed",
473
472
  "coverage_status": "complete",
474
- "max_calls": 1,
473
+ "max_calls": 2,
475
474
  "count": 1,
476
475
  "required": [
477
476
  {
@@ -524,7 +523,7 @@
524
523
  "expected": {
525
524
  "execution_status": "completed",
526
525
  "coverage_status": "complete",
527
- "max_calls": 1,
526
+ "max_calls": 2,
528
527
  "count": 1,
529
528
  "required": [
530
529
  {
@@ -564,7 +563,7 @@
564
563
  "expected": {
565
564
  "execution_status": "completed",
566
565
  "coverage_status": "complete",
567
- "max_calls": 1,
566
+ "max_calls": 2,
568
567
  "count": 1,
569
568
  "required": [
570
569
  {
@@ -665,7 +664,7 @@
665
664
  "expected": {
666
665
  "execution_status": "completed",
667
666
  "coverage_status": "complete",
668
- "max_calls": 1,
667
+ "max_calls": 2,
669
668
  "count": 2,
670
669
  "required": [
671
670
  {
@@ -732,7 +731,7 @@
732
731
  "expected": {
733
732
  "execution_status": "completed",
734
733
  "coverage_status": "complete",
735
- "max_calls": 1,
734
+ "max_calls": 2,
736
735
  "count": 1,
737
736
  "required": [
738
737
  {
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.85"
7
+ version = "0.2.87"
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.85"
3
+ __version__ = "0.2.87"
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
@@ -196,7 +196,7 @@ def plan_suite(suite: dict, *, repeats: int = 5) -> dict:
196
196
  turns = sum(len(c["turns"]) for c in suite["cases"])
197
197
  return {"version": VERSION, "suite_id": suite["suite_id"], "suite_sha256": _hash(_canonical(suite)),
198
198
  "cases": len(suite["cases"]), "turns_per_repeat": turns, "repeats": repeats,
199
- "normal_request_upper_bound": turns * repeats,
199
+ "normal_request_upper_bound": turns * repeats * 2,
200
200
  "recovery_request_upper_bound": turns * repeats * 2,
201
201
  "model_calls": 0, "mode": "plan", "semantic_status": "not_run",
202
202
  "switch_authorized": False, "prompt_sha256": _hash(INCREMENTAL_SYSTEM.encode("utf-8")),
@@ -327,7 +327,7 @@ def _checks(expected: dict, records: list[dict], observations: list[dict], calls
327
327
  else: # forbidden
328
328
  good = all(not any(matches(r, forbidden) for r in records) for forbidden in value)
329
329
  results.append({"check": key, "passed": good})
330
- results.append({"check": "per_work_request_cap", "passed": result.get("reserved_requests", 0) <= 2})
330
+ results.append({"check": "per_work_request_cap", "passed": result.get("reserved_requests", 0) <= result["request_limit"]})
331
331
  return results
332
332
 
333
333
 
@@ -420,6 +420,58 @@ class HermesAdapter:
420
420
  return False
421
421
  return True
422
422
 
423
+ def configure_retention_tools(self, detection=None):
424
+ """Keep one host-bound retention entry, preserving unrelated tool choices."""
425
+ import fnmatch
426
+ detection = detection or self.detect()
427
+ if detection.confidence != "high" or not detection.executable:
428
+ return False
429
+ key = "mcp_servers.memleaf.tools"
430
+ command = [detection.executable, "config", "get", key, "--json"]
431
+ try:
432
+ result = run_argv(self.runner, command, env=self.env)
433
+ if result.returncode == 0:
434
+ filters = json.loads(result.stdout)
435
+ if not isinstance(filters, dict):
436
+ return False
437
+ elif "Config key not set: " + key in result.stdout + result.stderr:
438
+ filters = {}
439
+ else:
440
+ return False
441
+ updated = dict(filters)
442
+ from ..mcp_server import _TOOLS
443
+ def patterns(value):
444
+ if isinstance(value, str):
445
+ return [value]
446
+ if isinstance(value, list) and all(isinstance(x, str) for x in value):
447
+ return value
448
+ raise ValueError("invalid tool filter")
449
+ if "include" in updated:
450
+ selected = patterns(updated["include"])
451
+ managed = updated.pop("memleaf_bound_retention", None)
452
+ if (isinstance(managed, dict) and set(managed) == {"patterns", "applied"}
453
+ and managed["applied"] == selected):
454
+ selected = patterns(managed["patterns"])
455
+ known = [tool["name"] for tool in _TOOLS if tool["name"] != "remember"
456
+ and any(fnmatch.fnmatchcase(tool["name"], pattern) for pattern in selected)]
457
+ # Retain explicit future names and original wildcard intent so
458
+ # reinstalling a newer declaration set does not freeze it.
459
+ literal = [name for name in selected if name != "remember" and not any(c in name for c in "*?[")]
460
+ updated["include"] = list(dict.fromkeys([*known, *literal]))
461
+ updated["memleaf_bound_retention"] = {"patterns": selected, "applied": updated["include"]}
462
+ else:
463
+ updated["exclude"] = list(dict.fromkeys([*patterns(updated.get("exclude", [])), "remember"]))
464
+ if updated == filters:
465
+ return True
466
+ written = run_argv(self.runner, [detection.executable, "config", "set", key,
467
+ json.dumps(updated, ensure_ascii=False)], env=self.env)
468
+ if written.returncode != 0:
469
+ return False
470
+ confirmed = run_argv(self.runner, command, env=self.env)
471
+ return confirmed.returncode == 0 and json.loads(confirmed.stdout) == updated
472
+ except (OSError, ValueError, TypeError):
473
+ return False
474
+
423
475
  def test_mcp(
424
476
  self,
425
477
  detection: Detection | None = None,
@@ -427,7 +427,7 @@ class Compactor:
427
427
  value = _content_mapping(candidate.memory)
428
428
  value["memory_id"] = candidate.memory.memory_id
429
429
  value["validity"] = candidate.memory.validity
430
- for key in ("assignee", "waiting_on", "due_text"):
430
+ for key in ("assignee", "due_text"):
431
431
  if key in candidate.memory.extra:
432
432
  value[key] = deepcopy(candidate.memory.extra[key])
433
433
  return value
@@ -14,6 +14,8 @@ from .llm import ModelError
14
14
 
15
15
 
16
16
  MAX_MODEL_REQUESTS = 3
17
+ # Incremental drafting, format repair and review share one durable allowance.
18
+ MAX_INCREMENTAL_REQUESTS = 5
17
19
  TARGET_TOTAL_SECONDS = 10.0
18
20
 
19
21
 
@@ -205,6 +207,7 @@ def budget_single_pass_backend(
205
207
 
206
208
  __all__ = [
207
209
  "MAX_MODEL_REQUESTS",
210
+ "MAX_INCREMENTAL_REQUESTS",
208
211
  "TARGET_TOTAL_SECONDS",
209
212
  "ExtractionTiming",
210
213
  "aggregate_extraction_metrics",
@@ -17,7 +17,7 @@ from pathlib import Path
17
17
  from typing import Any, Mapping
18
18
 
19
19
  from .locking import atomic_write_json, read_json
20
- from .extraction_budget import MAX_MODEL_REQUESTS
20
+ from .extraction_budget import MAX_MODEL_REQUESTS, MAX_INCREMENTAL_REQUESTS
21
21
  from .validation import parse_strict_json
22
22
 
23
23
 
@@ -307,7 +307,7 @@ def reserve_model_request(
307
307
  raise ExtractionWorkStateError("invalid extraction work id")
308
308
  if not _valid_identifier(turn_id, maximum=800):
309
309
  raise ExtractionWorkStateError("invalid extraction turn id")
310
- if type(request_limit) is not int or not 1 <= request_limit <= MAX_MODEL_REQUESTS:
310
+ if type(request_limit) is not int or not 1 <= request_limit <= max(MAX_MODEL_REQUESTS, MAX_INCREMENTAL_REQUESTS):
311
311
  raise ExtractionWorkStateError("invalid extraction request limit")
312
312
  if type(legacy_source_unchanged) is not bool:
313
313
  raise ExtractionWorkStateError("invalid legacy source compatibility flag")
@@ -10,9 +10,20 @@ Automatic capture and processing use only the visible user and assistant text.
10
10
  Tool calls and results, email or attachment bodies, and other hidden payloads are
11
11
  not automatic memory input.
12
12
  Hermes uses MCP `search` and `read` with the current `retrieval_id` for recall.
13
- MCP also supports explicit `remember`, `forget`, and maintenance operations.
13
+ The native `memleaf_remember` tool queues explicit retention bound to the current
14
+ turn; the completed capture supplies the original user messages and source times.
15
+ A queued response is not a saved memory. The installer excludes standalone MCP
16
+ `remember` in Hermes while preserving other configured tool choices. MCP still
17
+ supports `remember` for other clients, plus `forget` and maintenance operations.
14
18
  This is a Soft Gate, not a guarantee that every answer has performed retrieval.
15
19
 
20
+ The bound native route also handles conversational corrections, completion,
21
+ cancellation, reopening and withdrawal. A successful full-turn retention receipt
22
+ settles automatic extraction without another model request. Partial selections
23
+ remain separate. MCP lifecycle updates do not accept model-supplied source times;
24
+ trusted Python callers keep their explicit metadata API. Restart Hermes after
25
+ upgrading to load the current Provider.
26
+
16
27
  Hermes discovers this directory when it is installed as:
17
28
 
18
29
  ```text
@@ -1380,6 +1380,7 @@ class MemleafMemoryProvider(MemoryProvider):
1380
1380
  "current turn's retrieval_id exactly as supplied; a missing or mismatched "
1381
1381
  "token is a read failure, never a reason to fall back to a file tool. "
1382
1382
  "Read more only if needed; for ordinary relevance queries, do not read all entries to filter unrelated items. "
1383
+ "When pipeline_status is pending/unknown, memory can lag recent conversation: qualify current-state claims. For partial or completed_with_unresolved writes, report only the individually committed changes, and say which remain unresolved; never claim the entire request succeeded. "
1383
1384
  "When the user asks for current "
1384
1385
  "todos, all unfinished work, urgent work, or work due in a time range, call memleaf MCP "
1385
1386
  "list_todos instead of relevance search; omit scope for a global query, follow every "
@@ -1390,7 +1391,7 @@ class MemleafMemoryProvider(MemoryProvider):
1390
1391
  "turns are durably captured into the local memleaf inbox. Automatic "
1391
1392
  "capture → process runs after your final answer and is authoritative: "
1392
1393
  "a capture only proves that the inbox received the turn. Until a "
1393
- "successful explicit remember or update tool result is visible, never "
1394
+ "explicit tool result proves execution_status=completed and complete coverage, never "
1394
1395
  "say that the turn was saved to permanent memory, persisted, or "
1395
1396
  "记好了/已落库. For automatic handling, say only that you will process "
1396
1397
  "it through the workflow or that it is recorded in the current "
@@ -1402,7 +1403,8 @@ class MemleafMemoryProvider(MemoryProvider):
1402
1403
  "file writes to simulate success, and do not infer automatic success merely because "
1403
1404
  "active or history files exist. Automatic recall is a directory of "
1404
1405
  "scope identifiers, hierarchy, and aliases only; it never contains "
1405
- "memory IDs, titles, or bodies. Use deliberate remember/forget tools "
1406
+ "memory IDs, titles, or bodies. For explicit retention or corrections, task completion/cancellation/reopening, and withdrawing a fact/preference, use memleaf_remember with the current retrieval_id. It processes the actual complete turn and preserves audit history. Do not perform a conversational lifecycle edit through an unbound MCP update or infer message time from a deadline. Pending means not yet saved. Do not submit assistant-written facts through legacy MCP remember. "
1407
+ "Use deliberate remember/forget tools "
1406
1408
  "only when the user explicitly asks for that operation. Automatic "
1407
1409
  "capture and processing use only visible user and assistant text; "
1408
1410
  "tool calls/results, email or attachment bodies, and other hidden "
@@ -2027,6 +2029,10 @@ class MemleafMemoryProvider(MemoryProvider):
2027
2029
  retrieval_turn_id=retrieval_turn_id if role == "assistant" else None,
2028
2030
  ):
2029
2031
  return
2032
+ if self._gate_enabled:
2033
+ self._call("remember_turn", {"phase": "complete", "source": "hermes",
2034
+ "session_id": effective_session, "turn_id": turn_id},
2035
+ stage="remember_turn", session_id=effective_session, turn_id=turn_id)
2030
2036
  if not self._auto_process:
2031
2037
  return
2032
2038
  if not lineage_ready:
@@ -2054,7 +2060,7 @@ class MemleafMemoryProvider(MemoryProvider):
2054
2060
  if not tool_name.startswith(prefix):
2055
2061
  return None
2056
2062
  name = tool_name[len(prefix):]
2057
- if name not in {"search", "read", "list_todos", "scope_catalog", "remember"}:
2063
+ if name not in {"search", "read", "list_todos", "scope_catalog", "remember", "update_memory"}:
2058
2064
  return None
2059
2065
  turn_id = _host_turn_id(turn_context)
2060
2066
  session = turn_context.get("session_id") if isinstance(turn_context, Mapping) else None
@@ -2072,9 +2078,9 @@ class MemleafMemoryProvider(MemoryProvider):
2072
2078
  bound.update(source="hermes", session_id=session, turn_id=turn_id,
2073
2079
  intent_id="host-op-" + sha256((turn_id + "/" + tool_call_id + "/" + name).encode()).hexdigest())
2074
2080
  bound.pop("event_id", None)
2075
- # No source timestamp is assigned to an assistant-written composite
2076
- # explicit request; only the submitted text itself is its evidence.
2077
- bound.pop("source_time", None)
2081
+ # New hosts route retention to the source-bound lifecycle tool.
2082
+ # Never authorize the model's rewritten text as user evidence.
2083
+ raise ValueError("memory_host_retention_requires_bound_tool")
2078
2084
  elif name == "scope_catalog":
2079
2085
  bound.update(source="hermes", session_id=session, turn_id=turn_id)
2080
2086
  else:
@@ -2092,10 +2098,25 @@ class MemleafMemoryProvider(MemoryProvider):
2092
2098
  return bound
2093
2099
 
2094
2100
  def get_tool_schemas(self) -> List[Dict[str, Any]]:
2095
- # memleaf-mcp remains configured separately for deliberate search,
2096
- # remember, and forget operations. The native provider owns automatic
2097
- # recall/capture only, avoiding duplicate tool names in Hermes.
2098
- return []
2101
+ # Hermes registers schemas before initialize_all. Execution remains
2102
+ # gated by the initialized host identity and write policy.
2103
+ if not self._write_enabled:
2104
+ return []
2105
+ return [{"name": "memleaf_remember", "description": "Use for an explicit user request to remember or maintain existing memory: corrections, task completion/cancellation/reopening, or withdrawal of a fact/preference. Bind the actual complete user turn; never submit rewritten facts, dates or scopes. Returns pending until the turn is captured and processed.",
2106
+ "parameters": {"type": "object", "properties": {"retrieval_id": {"type": "string"}},
2107
+ "required": ["retrieval_id"], "additionalProperties": False}}]
2108
+
2109
+ def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs: Any) -> str:
2110
+ if (tool_name != "memleaf_remember" or not self._write_enabled or not self._gate_enabled
2111
+ or not isinstance(args, dict) or set(args) != {"retrieval_id"}):
2112
+ return json.dumps({"error": {"code": "invalid_host_retention_request"}})
2113
+ session = self._canonical_session_id(self._session_id)
2114
+ if not isinstance(args["retrieval_id"], str) or args["retrieval_id"] != self._current_gate_id(session):
2115
+ return json.dumps({"error": {"code": "host_retention_turn_mismatch"}, "saved": False})
2116
+ result = self._call("remember_turn", {"phase": "queue", "retrieval_id": args["retrieval_id"]}, stage="remember_turn")
2117
+ if result is _CALL_FAILED:
2118
+ return json.dumps({"error": {"code": "host_retention_not_queued"}, "saved": False})
2119
+ return json.dumps(result, ensure_ascii=False)
2099
2120
 
2100
2121
  def shutdown(self) -> None:
2101
2122
  with self._sync_lock:
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.2.85
2
+ version: 0.2.87
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch