memleaf 0.2.93__tar.gz → 0.2.95__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 (186) hide show
  1. {memleaf-0.2.93 → memleaf-0.2.95}/CHANGELOG.md +43 -0
  2. {memleaf-0.2.93/src/memleaf.egg-info → memleaf-0.2.95}/PKG-INFO +2 -2
  3. {memleaf-0.2.93 → memleaf-0.2.95}/README.en.md +1 -1
  4. {memleaf-0.2.93 → memleaf-0.2.95}/README.md +1 -1
  5. {memleaf-0.2.93 → memleaf-0.2.95}/docs/hermes-source-comparison-context.md +5 -0
  6. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-planner-preview.md +5 -3
  7. {memleaf-0.2.93 → memleaf-0.2.95}/pyproject.toml +1 -1
  8. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/__init__.py +1 -1
  9. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/_provider.py +54 -8
  10. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  11. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_prompts.py +2 -2
  12. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_protocol.py +18 -9
  13. {memleaf-0.2.93 → memleaf-0.2.95/src/memleaf.egg-info}/PKG-INFO +2 -2
  14. {memleaf-0.2.93 → memleaf-0.2.95}/LICENSE +0 -0
  15. {memleaf-0.2.93 → memleaf-0.2.95}/MANIFEST.in +0 -0
  16. {memleaf-0.2.93 → memleaf-0.2.95}/docs/acceptance-evidence-guide.md +0 -0
  17. {memleaf-0.2.93 → memleaf-0.2.95}/docs/automatic-processing-route.md +0 -0
  18. {memleaf-0.2.93 → memleaf-0.2.95}/docs/body-compaction.md +0 -0
  19. {memleaf-0.2.93 → memleaf-0.2.95}/docs/candidate-reconciliation.md +0 -0
  20. {memleaf-0.2.93 → memleaf-0.2.95}/docs/capture-budget-design.md +0 -0
  21. {memleaf-0.2.93 → memleaf-0.2.95}/docs/config-migrations.md +0 -0
  22. {memleaf-0.2.93 → memleaf-0.2.95}/docs/core-refactor.md +0 -0
  23. {memleaf-0.2.93 → memleaf-0.2.95}/docs/development-closeout.md +0 -0
  24. {memleaf-0.2.93 → memleaf-0.2.95}/docs/evidence-retention.md +0 -0
  25. {memleaf-0.2.93 → memleaf-0.2.95}/docs/explicit-memory-update.md +0 -0
  26. {memleaf-0.2.93 → memleaf-0.2.95}/docs/extraction-latency.md +0 -0
  27. {memleaf-0.2.93 → memleaf-0.2.95}/docs/failed-run-recovery.md +0 -0
  28. {memleaf-0.2.93 → memleaf-0.2.95}/docs/gate-evidence-boundary.md +0 -0
  29. {memleaf-0.2.93 → memleaf-0.2.95}/docs/general-processing.md +0 -0
  30. {memleaf-0.2.93 → memleaf-0.2.95}/docs/hermes-mcp-runtime.md +0 -0
  31. {memleaf-0.2.93 → memleaf-0.2.95}/docs/hermes-provider-compatibility.md +0 -0
  32. {memleaf-0.2.93 → memleaf-0.2.95}/docs/host-acceptance-runbook.md +0 -0
  33. {memleaf-0.2.93 → memleaf-0.2.95}/docs/immutable-release-assets.md +0 -0
  34. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-commit-contract.md +0 -0
  35. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-execution.md +0 -0
  36. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-native-comparison.md +0 -0
  37. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-partial-recovery.md +0 -0
  38. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-runtime-contract.md +0 -0
  39. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-scope-registration.md +0 -0
  40. {memleaf-0.2.93 → memleaf-0.2.95}/docs/incremental-selected-retention.md +0 -0
  41. {memleaf-0.2.93 → memleaf-0.2.95}/docs/installed-artifact-verification.md +0 -0
  42. {memleaf-0.2.93 → memleaf-0.2.95}/docs/memory-lifecycle-tools.md +0 -0
  43. {memleaf-0.2.93 → memleaf-0.2.95}/docs/memory-state-contract.md +0 -0
  44. {memleaf-0.2.93 → memleaf-0.2.95}/docs/migration-preflight.md +0 -0
  45. {memleaf-0.2.93 → memleaf-0.2.95}/docs/processing-quality-acceptance.md +0 -0
  46. {memleaf-0.2.93 → memleaf-0.2.95}/docs/public-query-integrity.md +0 -0
  47. {memleaf-0.2.93 → memleaf-0.2.95}/docs/query-performance.md +0 -0
  48. {memleaf-0.2.93 → memleaf-0.2.95}/docs/remember-incremental-route.md +0 -0
  49. {memleaf-0.2.93 → memleaf-0.2.95}/docs/runtime-state-retention.md +0 -0
  50. {memleaf-0.2.93 → memleaf-0.2.95}/docs/semantic-extraction-protocol.md +0 -0
  51. {memleaf-0.2.93 → memleaf-0.2.95}/docs/source-work-contract.md +0 -0
  52. {memleaf-0.2.93 → memleaf-0.2.95}/docs/v0.2.26-processing-status.md +0 -0
  53. {memleaf-0.2.93 → memleaf-0.2.95}/examples/README.md +0 -0
  54. {memleaf-0.2.93 → memleaf-0.2.95}/examples/basic_usage.py +0 -0
  55. {memleaf-0.2.93 → memleaf-0.2.95}/examples/incremental_acceptance.json +0 -0
  56. {memleaf-0.2.93 → memleaf-0.2.95}/examples/mcp_stdio.ndjson +0 -0
  57. {memleaf-0.2.93 → memleaf-0.2.95}/examples/query_benchmark.py +0 -0
  58. {memleaf-0.2.93 → memleaf-0.2.95}/install.ps1 +0 -0
  59. {memleaf-0.2.93 → memleaf-0.2.95}/install.sh +0 -0
  60. {memleaf-0.2.93 → memleaf-0.2.95}/setup.cfg +0 -0
  61. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/__main__.py +0 -0
  62. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/acceptance.py +0 -0
  63. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/adapters/__init__.py +0 -0
  64. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/adapters/antigravity.py +0 -0
  65. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/adapters/base.py +0 -0
  66. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/adapters/codex.py +0 -0
  67. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/adapters/hermes.py +0 -0
  68. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/admission.py +0 -0
  69. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/batch_review.py +0 -0
  70. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/budget.py +0 -0
  71. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/capture.py +0 -0
  72. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/cli.py +0 -0
  73. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/compaction.py +0 -0
  74. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/config.py +0 -0
  75. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/create_coordinator.py +0 -0
  76. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/credentials.py +0 -0
  77. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/evidence_budget.py +0 -0
  78. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/evidence_policy.py +0 -0
  79. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/evidence_structure.py +0 -0
  80. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/evidence_syntax.py +0 -0
  81. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/explicit_text_source.py +0 -0
  82. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/extraction_budget.py +0 -0
  83. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/extraction_capability.py +0 -0
  84. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/extraction_work_state.py +0 -0
  85. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/frontmatter.py +0 -0
  86. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/README.md +0 -0
  87. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/__init__.py +0 -0
  88. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  89. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/_shared.py +0 -0
  90. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  91. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  92. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/hermes_runtime.py +0 -0
  93. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/historical_context.py +0 -0
  94. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/host_events.py +0 -0
  95. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/host_retention.py +0 -0
  96. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/host_runtime.py +0 -0
  97. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/host_turn_identity.py +0 -0
  98. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/inbox.py +0 -0
  99. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_commit.py +0 -0
  100. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_dates.py +0 -0
  101. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_execution.py +0 -0
  102. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_explicit.py +0 -0
  103. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_failed_recovery.py +0 -0
  104. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_journal.py +0 -0
  105. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_merge.py +0 -0
  106. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_native.py +0 -0
  107. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_partial.py +0 -0
  108. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_preview.py +0 -0
  109. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_recovery.py +0 -0
  110. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_run_state.py +0 -0
  111. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_runtime.py +0 -0
  112. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_scopes.py +0 -0
  113. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_selection.py +0 -0
  114. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/incremental_semantics.py +0 -0
  115. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/index.py +0 -0
  116. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/inspection.py +0 -0
  117. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/installer.py +0 -0
  118. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/legacy_update_journal.py +0 -0
  119. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/lifecycle_maintenance.py +0 -0
  120. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/__init__.py +0 -0
  121. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/base.py +0 -0
  122. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/claude_compatible.py +0 -0
  123. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/gemini.py +0 -0
  124. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/openai_compatible.py +0 -0
  125. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/router.py +0 -0
  126. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/llm/thinking.py +0 -0
  127. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/locking.py +0 -0
  128. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/mcp_server.py +0 -0
  129. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/memory_commit.py +0 -0
  130. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/memory_planner.py +0 -0
  131. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/memory_retraction.py +0 -0
  132. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/memory_update.py +0 -0
  133. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/memory_writer.py +0 -0
  134. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/migration.py +0 -0
  135. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/model_capabilities.py +0 -0
  136. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/model_discovery.py +0 -0
  137. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/model_execution.py +0 -0
  138. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/models.py +0 -0
  139. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/native_index.py +0 -0
  140. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/native_registration.py +0 -0
  141. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/parallel_model.py +0 -0
  142. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/planning_candidates.py +0 -0
  143. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/planning_context.py +0 -0
  144. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/process_common.py +0 -0
  145. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/process_jobs.py +0 -0
  146. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/process_journal.py +0 -0
  147. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/process_owner.py +0 -0
  148. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/processing.py +0 -0
  149. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/processing_route.py +0 -0
  150. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/prompts.py +0 -0
  151. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/provenance.py +0 -0
  152. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/provider_compatibility.py +0 -0
  153. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/query_clock.py +0 -0
  154. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/query_progress.py +0 -0
  155. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/query_scan.py +0 -0
  156. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/receipt_codec.py +0 -0
  157. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/recording_policy.py +0 -0
  158. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/redaction.py +0 -0
  159. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/remember_route.py +0 -0
  160. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/retention.py +0 -0
  161. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/retrieval.py +0 -0
  162. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/retrieval_gate.py +0 -0
  163. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/retrieval_lifecycle.py +0 -0
  164. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/runtime_retention.py +0 -0
  165. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/scope_maintenance.py +0 -0
  166. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/scope_state.py +0 -0
  167. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/semantic_maintenance.py +0 -0
  168. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/semantic_protocol.py +0 -0
  169. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/service.py +0 -0
  170. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/single_pass_memory_planner.py +0 -0
  171. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/single_pass_plan.py +0 -0
  172. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/source_policy.py +0 -0
  173. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/state_layout.py +0 -0
  174. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/subprocess_flags.py +0 -0
  175. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/summary_batch.py +0 -0
  176. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/target_reconciliation.py +0 -0
  177. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/turn_audit.py +0 -0
  178. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/turn_plan.py +0 -0
  179. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/update_coordinator.py +0 -0
  180. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/update_review.py +0 -0
  181. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/validation.py +0 -0
  182. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf/vault.py +0 -0
  183. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf.egg-info/SOURCES.txt +0 -0
  184. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf.egg-info/dependency_links.txt +0 -0
  185. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf.egg-info/entry_points.txt +0 -0
  186. {memleaf-0.2.93 → memleaf-0.2.95}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -5,6 +5,49 @@ All notable changes to memleaf are documented here.
5
5
  ## Unreleased
6
6
 
7
7
 
8
+ ## 0.2.95 — 2026-10-05
9
+
10
+ ### Preserve turn identity around internal Hermes continuations
11
+
12
+ - Exclude ephemeral transcript rows using the installed Hermes persistence
13
+ contract before checking sources and resolving capture identity. Internal
14
+ continuation nudges with role=user no longer hide the actual user admission.
15
+ Keep exact real-user boundaries, unsupported-host fallback, source checks and
16
+ duplicate-delivery handling; never identify scaffolding by business wording.
17
+ - Accept valid redundant responsibility annotations for unchanged existing
18
+ assignees in UPDATE. Preserve the original responsibility basis; unknown
19
+ assignees, conflicting clears, invalid citations and unauthorized changes
20
+ remain rejected. Bind the adjusted instructions to the protocol digest.
21
+ - Add no model stage, request or retry. Source callback and saved-candidate
22
+ replays used no external model calls. All 58 affected checks passed in the
23
+ native Hermes runtime. Three live Hermes acceptance turns updated, completed
24
+ and reopened the original two memories correctly: six Memleaf requests,
25
+ 24,758 observed tokens, and no unresolved post-fix retention intents.
26
+
27
+
28
+ ## 0.2.94 — 2026-10-05
29
+
30
+ ### Reopen existing memories without redundant model fields
31
+
32
+ - Treat an evidence-backed UPDATE with status=active as the reopen intent for a
33
+ closed memory. Keep legacy reopen=true compatible and reject known older or
34
+ same-observation updates; missing source ordering stays unknown. Apply the
35
+ same ordering rule to explicitly restoring retracted facts.
36
+ - Preserve an independently verified user's source time when the final assistant
37
+ display cannot be matched. Keep assistant metadata unknown and distinguish
38
+ verified user deliveries while keeping repeated callbacks idempotent. Never
39
+ infer timestamps, borrow an older matching user message, or weaken target,
40
+ permission, conflict or semantic-review checks.
41
+ - Remove the redundant reopen flag requirement from extraction instructions.
42
+ Add no model request, retry or stage; the existing request budget is unchanged.
43
+ - Validation: 267 applicable checks and four isolated frozen-fault replays passed
44
+ against the current contract. Eight pre-existing obsolete test expectations
45
+ were recorded separately. An isolated live-model acceptance reopened one
46
+ existing memory with the correct deadline and no source time or reopen flag:
47
+ two successful requests, 3,089 observed tokens. Installed Hermes acceptance
48
+ remains separate from source and package verification.
49
+
50
+
8
51
  ## 0.2.93 — 2026-10-05
9
52
 
10
53
  ### Keep useful body compaction when another candidate has no net savings
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.93
3
+ Version: 0.2.95
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.93。**
26
+ > **版本:0.2.95。**
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.93.**
7
+ > **Version: 0.2.95.**
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.93。**
7
+ > **版本:0.2.95。**
8
8
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
9
9
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
10
10
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
@@ -8,6 +8,11 @@ an event does not create a source time.
8
8
 
9
9
  The legacy callback checks the final assistant and the nearest user. It has no
10
10
  32-message lookback limit. It does not borrow a matching user from an older turn.
11
+ Before checking that boundary, it excludes ephemeral rows using the installed
12
+ Hermes persistence predicate. Internal continuation nudges are not user events,
13
+ even when their role is `user`. It never identifies them by wording or arbitrary
14
+ private flags. If the host predicate is unavailable, the strict legacy boundary
15
+ is retained. Source checks and turn association use the same filtered snapshot.
11
16
 
12
17
  Some Hermes versions persist a reply before adding their file-mutation verifier
13
18
  footer. The Provider accepts that display transformation only when the installed
@@ -150,9 +150,11 @@ scanned and auto-filled as deadlines. This does **not** prove that a model-selec
150
150
  ISO date was semantically a deadline: that remains a live-model acceptance item.
151
151
 
152
152
  `field_basis` is self-contained source metadata, never eN or a list offset.
153
- Provably older conflicting field changes are rejected. Explicit reopen/restore
154
- requires a later comparable basis; legacy heads lacking it produce a visible
155
- unverified-time issue rather than a fabricated business time. Unknown completion
153
+ Provably older conflicting field changes are rejected. Reopening uses UPDATE
154
+ with `patch.status=active`; the legacy `reopen:true` flag remains optional.
155
+ Reopen/restore reject a known older or identical observation. Missing optional
156
+ source time/order remains unknown and does not block the proposed transition;
157
+ the normal source review checks whether the change is supported. Unknown completion
156
158
  time is not replaced with observation time. Future effective state changes are
157
159
  blocked, while a fact describing a plan can still be proposed. No timer executes
158
160
  future plans. This is bounded observation-order checking, not a bitemporal database.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.93"
7
+ version = "0.2.95"
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.93"
3
+ __version__ = "0.2.95"
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
@@ -29,6 +29,23 @@ def _capture_assistant_text(value: str) -> str:
29
29
  flags=re.IGNORECASE)
30
30
 
31
31
 
32
+ def _source_messages(messages: Any) -> Any:
33
+ """Use the host's durable transcript boundary, not its internal nudges.
34
+
35
+ Hermes may retain synthetic user/assistant rows while continuing after an
36
+ empty response. Its persistence contract excludes those rows. Follow that
37
+ exact host predicate rather than recognizing wording or scanning past a
38
+ real user. Hosts without this predicate retain the strict legacy boundary.
39
+ """
40
+ if not isinstance(messages, list):
41
+ return messages
42
+ try:
43
+ from agent.session_persistence import _is_ephemeral_scaffolding
44
+ return [row for row in messages if not _is_ephemeral_scaffolding(row)]
45
+ except (ImportError, AttributeError, TypeError, ValueError):
46
+ return messages
47
+
48
+
32
49
  def _assistant_source_matches(messages: Any, content: str) -> bool:
33
50
  """Accept exact text or an exactly reconstructed native verifier footer.
34
51
 
@@ -135,6 +152,18 @@ def _sync_delivery_digest(messages: Any, users: Any, user_content: str, assistan
135
152
  identities = []
136
153
  break
137
154
  identities.append((uid, timestamp))
155
+ elif (isinstance(messages, list) and messages
156
+ and isinstance(messages[-1], Mapping) and messages[-1].get("role") == "assistant"
157
+ and not messages[-1].get("tool_calls")):
158
+ user = next((row for row in reversed(messages[:-1])
159
+ if isinstance(row, Mapping) and row.get("role") == "user"), None)
160
+ if user is not None and user.get("content") == user_content:
161
+ uid = user.get("message_uid") or user.get("message_id")
162
+ timestamp = _independent_source_time(user, "user").get("user", {}).get("source_time")
163
+ if uid or timestamp:
164
+ # Only the verified user identifies this delivery; do not
165
+ # bind the host's transformed reply to an assistant source.
166
+ identities.append((uid, timestamp))
138
167
  payload = f"{user_content}\x00{_capture_assistant_text(assistant_content)}"
139
168
  if identities:
140
169
  payload += "\x00" + json.dumps(identities, ensure_ascii=False, separators=(",", ":"))
@@ -151,13 +180,17 @@ def _sync_source_metadata(messages: Any, user_content: str, assistant_content: s
151
180
  from datetime import datetime
152
181
  from math import isfinite
153
182
 
183
+ messages = _source_messages(messages)
154
184
  if not isinstance(messages, list) or not messages:
155
185
  logger.info("memleaf source-metadata reason=messages_missing")
156
186
  return {}
157
187
  assistant = messages[-1]
158
- if not _assistant_source_matches(messages, assistant_content):
188
+ assistant_matches = _assistant_source_matches(messages, assistant_content)
189
+ if not assistant_matches:
159
190
  logger.info("memleaf source-metadata reason=assistant_mismatch")
160
- return {}
191
+ if (not isinstance(assistant, Mapping) or assistant.get("role") != "assistant"
192
+ or assistant.get("tool_calls")):
193
+ return {}
161
194
  # Tool rounds do not define a turn boundary. Stop at the nearest user,
162
195
  # even if its text mismatches; never borrow an older matching message.
163
196
  preceding = reversed(messages)
@@ -166,10 +199,15 @@ def _sync_source_metadata(messages: Any, user_content: str, assistant_content: s
166
199
  if isinstance(item, Mapping) and item.get("role") == "user"), None)
167
200
  if user is None:
168
201
  logger.info("memleaf source-metadata reason=user_missing")
169
- return _independent_assistant_time(assistant, assistant_content)
202
+ return _independent_assistant_time(assistant, assistant_content) if assistant_matches else {}
170
203
  if user.get("content") != user_content:
171
204
  logger.info("memleaf source-metadata reason=user_mismatch")
172
- return _independent_assistant_time(assistant, assistant_content)
205
+ return _independent_assistant_time(assistant, assistant_content) if assistant_matches else {}
206
+ if not assistant_matches:
207
+ # Host warnings/display transformations cannot invalidate an exact
208
+ # current user source. Keep its time independently, without binding
209
+ # the unverified assistant or inventing paired identities/order.
210
+ return _independent_source_time(user, "user")
173
211
  result: dict[str, dict[str, Any]] = {}
174
212
  for role, item in (("user", user), ("assistant", assistant)):
175
213
  row: dict[str, Any] = {}
@@ -213,11 +251,15 @@ def _sync_source_metadata(messages: Any, user_content: str, assistant_content: s
213
251
 
214
252
 
215
253
  def _independent_assistant_time(assistant: Mapping[str, Any], content: str) -> dict[str, dict[str, Any]]:
254
+ return _independent_source_time(assistant, "assistant")
255
+
256
+
257
+ def _independent_source_time(message: Mapping[str, Any], role: str) -> dict[str, dict[str, Any]]:
216
258
  from datetime import datetime
217
259
  from math import isfinite
218
- value = assistant.get("source_time")
260
+ value = message.get("source_time")
219
261
  if value is None:
220
- value = assistant.get("timestamp")
262
+ value = message.get("timestamp")
221
263
  parsed = None
222
264
  try:
223
265
  if type(value) in (int, float) and isfinite(value) and 0 <= value < 253402300800:
@@ -225,7 +267,7 @@ def _independent_assistant_time(assistant: Mapping[str, Any], content: str) -> d
225
267
  elif isinstance(value, str) and len(value) <= 80:
226
268
  parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
227
269
  if parsed is not None and parsed.tzinfo is not None and parsed.utcoffset() is not None:
228
- return {"assistant": {"source_time": parsed.isoformat()}}
270
+ return {role: {"source_time": parsed.isoformat()}}
229
271
  except (ValueError, OSError, OverflowError):
230
272
  pass
231
273
  return {}
@@ -1152,6 +1194,7 @@ class MemleafMemoryProvider(MemoryProvider):
1152
1194
  user_content: str,
1153
1195
  assistant_content: str,
1154
1196
  delivery_digest: Optional[str] = None,
1197
+ source_fallback: bool = False,
1155
1198
  ) -> str:
1156
1199
  pair_digest = delivery_digest or sha256(f"{user_content}\x00{assistant_content}".encode("utf-8")).hexdigest()[:16]
1157
1200
  pair_key = (session_id, pair_digest)
@@ -1160,6 +1203,8 @@ class MemleafMemoryProvider(MemoryProvider):
1160
1203
  self._turn_ids_by_pair.move_to_end(pair_key)
1161
1204
  return existing
1162
1205
  resolved = _turn_id(turn_number, user_content, assistant_content)
1206
+ if source_fallback and delivery_digest and resolved.startswith("turn-fallback-"):
1207
+ resolved = "turn-fallback-" + delivery_digest
1163
1208
  if existing is not None and existing == resolved:
1164
1209
  self._turn_ids_by_pair.move_to_end(pair_key)
1165
1210
  return existing
@@ -1912,7 +1957,7 @@ class MemleafMemoryProvider(MemoryProvider):
1912
1957
  # Snapshot the list at invocation; subsequent tool/turn appends cannot
1913
1958
  # change which source rows this callback verifies.
1914
1959
  import copy
1915
- messages = copy.deepcopy(messages)
1960
+ messages = _source_messages(copy.deepcopy(messages))
1916
1961
  # Hermes serializes provider sync work, but this lock also protects
1917
1962
  # direct/plugin-level concurrent calls and makes process one-shot per
1918
1963
  # captured turn within this provider instance.
@@ -1965,6 +2010,7 @@ class MemleafMemoryProvider(MemoryProvider):
1965
2010
  user_content,
1966
2011
  captured_assistant_content,
1967
2012
  delivery_digest=delivery_digest,
2013
+ source_fallback=not _assistant_source_matches(messages, raw_assistant_content),
1968
2014
  )
1969
2015
  source_metadata = ({} if host_events is not None or correction_users else
1970
2016
  _sync_source_metadata(messages, user_content, raw_assistant_content))
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.2.93
2
+ version: 0.2.95
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch
@@ -27,11 +27,11 @@ NO_MEMORY:仅 action;自动模式整轮无值得维护/新增的内容时独
27
27
  字段:
28
28
  title 标识事项,不放交期/发生日/进度,改存正文与结构字段;旧标题交期一并清理。名称固有年度等身份日期保留。
29
29
  type=fact|todo|preference|project|event|identity|other。有独立完成条件的持续行动设 actionable:true(todo 隐含);事实类别与行动属性可分开表达。
30
- status=active|completed|cancelled;完成/取消/交接同时纠正冲突的 title/body;独立未完行动不能被兄弟项完成吞掉。重新开启 UPDATE 使用 reopen:true 和 patch.status=active,需要较新明确依据。
30
+ status=active|completed|cancelled;完成/取消/交接同时纠正冲突的 title/body;独立未完行动不能被兄弟项完成吞掉。重开原 target:UPDATE patch.status=active,须有 new 依据。
31
31
  assignee 是实际执行人,只有明确由用户本人执行才 user,其他执行人用明确名称,未知为 null。用户记录/转发/协调不证明本人执行。等待依赖、卡点仅在正文维护,按最新事实替换过时内容,不生成等待字段。
32
32
  scope 采用输入引用;通用为 global,归属未知为 unscoped;只有 allow_new_scopes 才可提出 project:新名称。不把 scopes 当搜索提示,不扩大 write_scopes。
33
33
  patch 只含 title/body/scope/status/actionable/assignee/deadline/validity;type 沿用目标。
34
- 新增/变更非空 assignee 必须提供 responsibility_basis:{"assignee":{"ref":"e1","text":"确立执行人的原文"}};放行级或对应 memory/patch 内均可,不能冲突。ref 必须在 evidence,text 是实际原文;新增用户执行责任必须引用 user 原文,assistant 不能赋予用户新义务。原文必须确立这个实际行动的责任,引用另一件事的请求不算。沿用不需重复引用。
34
+ 新增/变更非空 assignee 必须提供 responsibility_basis:{"assignee":{"ref":"e1","text":"确立执行人的原文"}};放行级或对应 memory/patch 内均可,不能冲突。ref 必须在 evidence,text 是实际原文;新增用户执行责任必须引用 user 原文,assistant 不能赋予用户新义务。原文必须确立这个实际行动的责任,引用另一件事的请求不算。沿用无需重复引用;冗余引用仍须有效,但不刷新旧依据。
35
35
 
36
36
  日期:有明确期限时放 memory.deadline 或 patch.deadline:{"ref":"e1","text":"期限原文"},不能只留正文。没有期限变更时省略 deadline。负责人交接、等待验收、尚未完成不能取消旧期限。取消期限用 {"ref":"e1","clear":true,"text":"取消期限原文"};new 未取消时保留原期限。source_time 只用于解释相对日,不补造发生日;每个相对日按其自己的消息时间解析。无来源时间保持未知,不借处理时间。正文日期与期限、材料日期、发生日期区分;无日期也能记完成事实。CREATE/UPDATE 可给 effective:{"ref":"e1","text":"生效时间原文"}。
37
37
 
@@ -559,7 +559,12 @@ def _review_fields(row: Mapping[str, Any], fields: Mapping[str, Any], state: Map
559
559
  if needed - set(offered):
560
560
  raise ValueError("missing_responsibility_basis")
561
561
  for key, selection in offered.items():
562
- if key not in fields or fields[key] is None:
562
+ # An unchanged owner may be omitted from UPDATE while its annotation
563
+ # is repeated. Validate that annotation without changing the owner or
564
+ # advancing its field basis. CREATE and explicit clears cannot borrow
565
+ # an old owner to satisfy a supplied annotation.
566
+ value = fields.get(key) if key in fields else old.get(key)
567
+ if value is None:
563
568
  raise ValueError("invalid_responsibility_basis")
564
569
  if not isinstance(selection, dict) or set(selection) != {"ref", "text"}:
565
570
  raise ValueError("invalid_responsibility_basis")
@@ -568,9 +573,10 @@ def _review_fields(row: Mapping[str, Any], fields: Mapping[str, Any], state: Map
568
573
  if (ref not in refs or not reading_text(text).strip()
569
574
  or reading_text(text) not in reading_text(evidence[ref]["text"])):
570
575
  raise ValueError("unproven_responsibility_basis")
571
- if key == "assignee" and fields[key] == "user" and evidence[ref]["role"] != "user":
576
+ if key == "assignee" and value == "user" and evidence[ref]["role"] != "user":
572
577
  raise ValueError("unconfirmed_user_responsibility")
573
- bases[key] = source_basis(evidence[ref])
578
+ if key in fields:
579
+ bases[key] = source_basis(evidence[ref])
574
580
  return bases
575
581
 
576
582
 
@@ -717,6 +723,8 @@ def _parse_row(row: Any, state: Mapping[str, Any]) -> dict[str, Any]:
717
723
  def _order(new: Mapping[str, Any], old: Mapping[str, Any]) -> int | None:
718
724
  # These fields prove observation order, not inferred business effectiveness.
719
725
  same_stream = all(new.get(k) is not None and new.get(k) == old.get(k) for k in ("source", "session_id"))
726
+ if same_stream and new.get("event_key") and new.get("event_key") == old.get("event_key"):
727
+ return 0 # The same observation cannot revive its own closed state.
720
728
  if same_stream and type(new.get("source_sequence")) is int and type(old.get("source_sequence")) is int:
721
729
  a, b = new["source_sequence"], old["source_sequence"]
722
730
  return (a > b) - (a < b)
@@ -768,15 +776,16 @@ def _compile_group(rows: list[dict[str, Any]], state: Mapping[str, Any]) -> dict
768
776
  if boundary is not None and (set(chosen) - set(boundary) or (target and set(old["scopes"]) - set(boundary))):
769
777
  raise ValueError("blocked_scope")
770
778
  if old.get("status") in {"completed", "cancelled"} and fields.get("status") == "active":
771
- if not any(row.get("reopen") for row in rows):
772
- raise ValueError("explicit_reopen_required")
773
- if _order(bases.get("status", {}), old.get("field_basis", {}).get("status", {})) != 1:
774
- raise ValueError("unverified_reopen_time")
779
+ # The explicit patch already expresses reopening. A model flag cannot
780
+ # prove source support; the existing semantic review checks the change.
781
+ # Missing optional time/order stays unknown, not an automatic veto.
782
+ if _order(bases.get("status", {}), old.get("field_basis", {}).get("status", {})) in (-1, 0):
783
+ raise ValueError("stale_observation")
775
784
  if old.get("validity") == "retracted":
776
785
  if fields.get("validity") != "valid" or not fields.get("body"):
777
786
  raise ValueError("explicit_restore_required")
778
- if _order(bases.get("validity", {}), old.get("field_basis", {}).get("validity", {})) != 1:
779
- raise ValueError("unverified_restore_time")
787
+ if _order(bases.get("validity", {}), old.get("field_basis", {}).get("validity", {})) in (-1, 0):
788
+ raise ValueError("stale_observation")
780
789
  if fields.get("validity") == "retracted":
781
790
  # Retraction is an explicit lifecycle operation. An explanatory body
782
791
  # belongs to the audit metadata, never to the active assertion. Retain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.93
3
+ Version: 0.2.95
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.93。**
26
+ > **版本:0.2.95。**
27
27
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
28
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
29
29
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
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