memleaf 1.0.1__tar.gz → 1.0.2__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 (204) hide show
  1. {memleaf-1.0.1 → memleaf-1.0.2}/CHANGELOG.md +24 -0
  2. {memleaf-1.0.1/src/memleaf.egg-info → memleaf-1.0.2}/PKG-INFO +2 -2
  3. {memleaf-1.0.1 → memleaf-1.0.2}/README.en.md +1 -1
  4. {memleaf-1.0.1 → memleaf-1.0.2}/README.md +1 -1
  5. {memleaf-1.0.1 → memleaf-1.0.2}/docs/stability.md +16 -2
  6. {memleaf-1.0.1 → memleaf-1.0.2}/pyproject.toml +1 -1
  7. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/__init__.py +1 -1
  8. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/base.py +20 -0
  9. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/codex.py +6 -2
  10. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/frontmatter.py +7 -3
  11. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  12. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_events.py +5 -1
  13. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2.py +91 -9
  14. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_owner.py +10 -1
  15. {memleaf-1.0.1 → memleaf-1.0.2/src/memleaf.egg-info}/PKG-INFO +2 -2
  16. {memleaf-1.0.1 → memleaf-1.0.2}/LICENSE +0 -0
  17. {memleaf-1.0.1 → memleaf-1.0.2}/MANIFEST.in +0 -0
  18. {memleaf-1.0.1 → memleaf-1.0.2}/docs/acceptance-evidence-guide.md +0 -0
  19. {memleaf-1.0.1 → memleaf-1.0.2}/docs/automatic-processing-route.md +0 -0
  20. {memleaf-1.0.1 → memleaf-1.0.2}/docs/body-compaction.md +0 -0
  21. {memleaf-1.0.1 → memleaf-1.0.2}/docs/candidate-reconciliation.md +0 -0
  22. {memleaf-1.0.1 → memleaf-1.0.2}/docs/capture-budget-design.md +0 -0
  23. {memleaf-1.0.1 → memleaf-1.0.2}/docs/config-migrations.md +0 -0
  24. {memleaf-1.0.1 → memleaf-1.0.2}/docs/core-refactor.md +0 -0
  25. {memleaf-1.0.1 → memleaf-1.0.2}/docs/development-closeout.md +0 -0
  26. {memleaf-1.0.1 → memleaf-1.0.2}/docs/evidence-retention.md +0 -0
  27. {memleaf-1.0.1 → memleaf-1.0.2}/docs/explicit-memory-update.md +0 -0
  28. {memleaf-1.0.1 → memleaf-1.0.2}/docs/extraction-latency.md +0 -0
  29. {memleaf-1.0.1 → memleaf-1.0.2}/docs/failed-run-recovery.md +0 -0
  30. {memleaf-1.0.1 → memleaf-1.0.2}/docs/gate-evidence-boundary.md +0 -0
  31. {memleaf-1.0.1 → memleaf-1.0.2}/docs/general-processing.md +0 -0
  32. {memleaf-1.0.1 → memleaf-1.0.2}/docs/hermes-mcp-runtime.md +0 -0
  33. {memleaf-1.0.1 → memleaf-1.0.2}/docs/hermes-provider-compatibility.md +0 -0
  34. {memleaf-1.0.1 → memleaf-1.0.2}/docs/hermes-source-comparison-context.md +0 -0
  35. {memleaf-1.0.1 → memleaf-1.0.2}/docs/host-acceptance-runbook.md +0 -0
  36. {memleaf-1.0.1 → memleaf-1.0.2}/docs/host-v2-contract.md +0 -0
  37. {memleaf-1.0.1 → memleaf-1.0.2}/docs/immutable-release-assets.md +0 -0
  38. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-commit-contract.md +0 -0
  39. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-execution.md +0 -0
  40. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-native-comparison.md +0 -0
  41. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-partial-recovery.md +0 -0
  42. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-planner-preview.md +0 -0
  43. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-runtime-contract.md +0 -0
  44. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-scope-registration.md +0 -0
  45. {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-selected-retention.md +0 -0
  46. {memleaf-1.0.1 → memleaf-1.0.2}/docs/installed-artifact-verification.md +0 -0
  47. {memleaf-1.0.1 → memleaf-1.0.2}/docs/memory-lifecycle-tools.md +0 -0
  48. {memleaf-1.0.1 → memleaf-1.0.2}/docs/memory-state-contract.md +0 -0
  49. {memleaf-1.0.1 → memleaf-1.0.2}/docs/migration-preflight.md +0 -0
  50. {memleaf-1.0.1 → memleaf-1.0.2}/docs/processing-quality-acceptance.md +0 -0
  51. {memleaf-1.0.1 → memleaf-1.0.2}/docs/public-query-integrity.md +0 -0
  52. {memleaf-1.0.1 → memleaf-1.0.2}/docs/query-performance.md +0 -0
  53. {memleaf-1.0.1 → memleaf-1.0.2}/docs/remember-incremental-route.md +0 -0
  54. {memleaf-1.0.1 → memleaf-1.0.2}/docs/runtime-state-retention.md +0 -0
  55. {memleaf-1.0.1 → memleaf-1.0.2}/docs/semantic-extraction-protocol.md +0 -0
  56. {memleaf-1.0.1 → memleaf-1.0.2}/docs/source-work-contract.md +0 -0
  57. {memleaf-1.0.1 → memleaf-1.0.2}/docs/v0.2.26-processing-status.md +0 -0
  58. {memleaf-1.0.1 → memleaf-1.0.2}/examples/README.md +0 -0
  59. {memleaf-1.0.1 → memleaf-1.0.2}/examples/basic_usage.py +0 -0
  60. {memleaf-1.0.1 → memleaf-1.0.2}/examples/mcp_stdio.ndjson +0 -0
  61. {memleaf-1.0.1 → memleaf-1.0.2}/install.ps1 +0 -0
  62. {memleaf-1.0.1 → memleaf-1.0.2}/install.sh +0 -0
  63. {memleaf-1.0.1 → memleaf-1.0.2}/setup.cfg +0 -0
  64. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/__main__.py +0 -0
  65. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/__init__.py +0 -0
  66. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/antigravity.py +0 -0
  67. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/hermes.py +0 -0
  68. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/admission.py +0 -0
  69. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/batch_review.py +0 -0
  70. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/body_preservation.py +0 -0
  71. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/budget.py +0 -0
  72. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/capture.py +0 -0
  73. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/cli.py +0 -0
  74. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/compaction.py +0 -0
  75. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/config.py +0 -0
  76. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/create_coordinator.py +0 -0
  77. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/credentials.py +0 -0
  78. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_budget.py +0 -0
  79. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_policy.py +0 -0
  80. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_structure.py +0 -0
  81. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_syntax.py +0 -0
  82. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/explicit_text_source.py +0 -0
  83. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/extraction_budget.py +0 -0
  84. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/extraction_capability.py +0 -0
  85. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/extraction_work_state.py +0 -0
  86. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/README.md +0 -0
  87. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/__init__.py +0 -0
  88. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  89. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/_provider.py +0 -0
  90. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/_shared.py +0 -0
  91. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  92. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  93. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_runtime.py +0 -0
  94. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/historical_context.py +0 -0
  95. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_retention.py +0 -0
  96. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_runtime.py +0 -0
  97. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_turn_identity.py +0 -0
  98. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_auth.py +0 -0
  99. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_cli.py +0 -0
  100. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_common.py +0 -0
  101. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/schemas/admin-records.schema.json +0 -0
  102. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/schemas/memleaf-host-v2.schema.json +0 -0
  103. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/schemas/read-integrity.schema.json +0 -0
  104. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/cancel_memory.json +0 -0
  105. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/forget_memory.json +0 -0
  106. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/memory_capabilities.json +0 -0
  107. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/memory_work.json +0 -0
  108. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/prepare_memory.json +0 -0
  109. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/resume_memory.json +0 -0
  110. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/submit_memory.json +0 -0
  111. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_mcp.py +0 -0
  112. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_proposal.py +0 -0
  113. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_schema.py +0 -0
  114. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/inbox.py +0 -0
  115. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_commit.py +0 -0
  116. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_dates.py +0 -0
  117. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_execution.py +0 -0
  118. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_explicit.py +0 -0
  119. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_failed_recovery.py +0 -0
  120. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_journal.py +0 -0
  121. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_merge.py +0 -0
  122. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_native.py +0 -0
  123. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_partial.py +0 -0
  124. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_preview.py +0 -0
  125. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_prompts.py +0 -0
  126. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_protocol.py +0 -0
  127. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_recovery.py +0 -0
  128. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_run_state.py +0 -0
  129. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_runtime.py +0 -0
  130. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_scopes.py +0 -0
  131. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_selection.py +0 -0
  132. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_semantics.py +0 -0
  133. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/index.py +0 -0
  134. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/inspection.py +0 -0
  135. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/installer.py +0 -0
  136. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/legacy_update_journal.py +0 -0
  137. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/lifecycle_maintenance.py +0 -0
  138. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/__init__.py +0 -0
  139. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/base.py +0 -0
  140. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/claude_compatible.py +0 -0
  141. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/gemini.py +0 -0
  142. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/openai_compatible.py +0 -0
  143. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/router.py +0 -0
  144. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/thinking.py +0 -0
  145. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/locking.py +0 -0
  146. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/mcp_server.py +0 -0
  147. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_commit.py +0 -0
  148. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_planner.py +0 -0
  149. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_retraction.py +0 -0
  150. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_update.py +0 -0
  151. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_writer.py +0 -0
  152. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/migration.py +0 -0
  153. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/model_capabilities.py +0 -0
  154. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/model_discovery.py +0 -0
  155. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/model_execution.py +0 -0
  156. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/models.py +0 -0
  157. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/native_index.py +0 -0
  158. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/native_registration.py +0 -0
  159. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/parallel_model.py +0 -0
  160. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/planning_candidates.py +0 -0
  161. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/planning_context.py +0 -0
  162. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_common.py +0 -0
  163. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_jobs.py +0 -0
  164. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_journal.py +0 -0
  165. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_owner.py +0 -0
  166. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/processing.py +0 -0
  167. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/processing_route.py +0 -0
  168. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/prompts.py +0 -0
  169. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/provenance.py +0 -0
  170. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/provider_compatibility.py +0 -0
  171. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/query_clock.py +0 -0
  172. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/query_progress.py +0 -0
  173. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/query_scan.py +0 -0
  174. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/receipt_codec.py +0 -0
  175. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/recording_policy.py +0 -0
  176. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/redaction.py +0 -0
  177. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/remember_route.py +0 -0
  178. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retention.py +0 -0
  179. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retrieval.py +0 -0
  180. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retrieval_gate.py +0 -0
  181. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retrieval_lifecycle.py +0 -0
  182. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/runtime_retention.py +0 -0
  183. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/scope_maintenance.py +0 -0
  184. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/scope_state.py +0 -0
  185. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/semantic_maintenance.py +0 -0
  186. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/semantic_protocol.py +0 -0
  187. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/service.py +0 -0
  188. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/single_pass_memory_planner.py +0 -0
  189. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/single_pass_plan.py +0 -0
  190. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/source_policy.py +0 -0
  191. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/state_layout.py +0 -0
  192. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/subprocess_flags.py +0 -0
  193. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/summary_batch.py +0 -0
  194. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/target_reconciliation.py +0 -0
  195. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/turn_audit.py +0 -0
  196. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/turn_plan.py +0 -0
  197. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/update_coordinator.py +0 -0
  198. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/update_review.py +0 -0
  199. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/validation.py +0 -0
  200. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/vault.py +0 -0
  201. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/SOURCES.txt +0 -0
  202. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/dependency_links.txt +0 -0
  203. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/entry_points.txt +0 -0
  204. {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -5,6 +5,30 @@ All notable changes to memleaf are documented here.
5
5
  ## Unreleased
6
6
 
7
7
 
8
+ ## 1.0.2 — 2026-10-08
9
+
10
+ ### Reliable host recovery and Codex todo retrieval
11
+
12
+ - Freeze host write groups only after every member is serialized and validated.
13
+ Reject empty/incomplete recovery groups and impossible legacy saved receipts;
14
+ verify actual heads and history before settlement, including Owner accounting.
15
+ - Preserve multiline evidence in Markdown metadata through escaped quoted
16
+ strings, with lossless LF/CR/CRLF and Unicode line-separator round trips.
17
+ - Include `list_todos` in Codex retrieval Hooks and accept its current directory
18
+ fields. Reinstallation safely upgrades known older matchers with backup;
19
+ unrelated or shared third-party Hook groups are preserved.
20
+ - Validate the installed host workflow with multiline evidence in the existing
21
+ cross-platform wheel/source-distribution matrix. All 24 added regressions and
22
+ 152 existing host checks passed locally, including real stdio/Hook subprocesses
23
+ and isolated installed-package checks. One additional historical assertion
24
+ failed identically on the original 1.0.1 baseline and remains outside this fix.
25
+ - No model call or retry was added. This patch used deterministic synthetic
26
+ acceptance; the earlier 1.0.1 macOS live-client evidence remains historical,
27
+ and no new real-model or production-Vault acceptance is claimed.
28
+ - Existing Codex model-profile installations need the installer run again to
29
+ update their known old Hook matchers, followed by the existing Hook review and
30
+ trust flow. Updating the Python package alone does not rewrite host settings.
31
+
8
32
  ## 1.0.1 — 2026-10-08
9
33
 
10
34
  ### Optional host-model MCP workflow for Codex
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -24,7 +24,7 @@ Dynamic: license-file
24
24
 
25
25
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
26
26
 
27
- > **稳定版:1.0.1。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
27
+ > **稳定版:1.0.2。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
28
28
  > **Codex 可通过 `--profile host` 使用当前宿主模型完成记忆闭环,无需另配 Memleaf 模型 API Key。** 本地 Owner 仍需配置客户端身份、读写权限及来源信任;客户端主动调用工具,不代表每轮自动记忆。
29
29
  > host profile 支持独立于内容类型的 `actionable` 和结构化 `waiting_on`,每份 work 默认最多三次被接受的 submit;Memleaf 负责确定性校验、落盘与恢复。
30
30
  > 默认 `model` profile 和 Hermes 原生接入继续使用独立 Model Route,提炼与语义复核共用每份工作的最多五次模型请求。旧接口、现有记忆和默认入口保留,升级无需正式 Vault 迁移。Antigravity(反重力)当前不支持。
@@ -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
- > **Stable release: 1.0.1.** Hermes and Codex are supported; see the [stability policy](docs/stability.md) for compatibility scope.
7
+ > **Stable release: 1.0.2.** Hermes and Codex are supported; see the [stability policy](docs/stability.md) for compatibility scope.
8
8
  > **Codex can complete the memory workflow with its current host model through `--profile host`, without a separate Memleaf model API key.** A local Owner still configures client identity, permissions and source trust. Clients invoke the tools deliberately; this does not enable automatic memory on every turn.
9
9
  > The host profile supports `actionable` independently of content type and structured `waiting_on`, with at most three accepted submits per work by default. Memleaf validates, persists and recovers deterministically.
10
10
  > The default `model` profile and native Hermes integration retain their independent Model Route and shared five-request extraction/review budget. Existing interfaces, memories and default entry points remain; no formal Vault migration is required. Antigravity is not supported.
@@ -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
- > **稳定版:1.0.1。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
7
+ > **稳定版:1.0.2。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
8
8
  > **Codex 可通过 `--profile host` 使用当前宿主模型完成记忆闭环,无需另配 Memleaf 模型 API Key。** 本地 Owner 仍需配置客户端身份、读写权限及来源信任;客户端主动调用工具,不代表每轮自动记忆。
9
9
  > host profile 支持独立于内容类型的 `actionable` 和结构化 `waiting_on`,每份 work 默认最多三次被接受的 submit;Memleaf 负责确定性校验、落盘与恢复。
10
10
  > 默认 `model` profile 和 Hermes 原生接入继续使用独立 Model Route,提炼与语义复核共用每份工作的最多五次模型请求。旧接口、现有记忆和默认入口保留,升级无需正式 Vault 迁移。Antigravity(反重力)当前不支持。
@@ -30,15 +30,24 @@ scope. An internal protocol change may fence pending model responses and require
30
30
  an explicit recovery action; it must not silently reinterpret saved work, erase
31
31
  unresolved outcomes or reset consumed request budgets.
32
32
 
33
- ## Upgrade to 1.0.1
33
+ ## Upgrade to 1.0.2
34
34
 
35
- The 1.0.1 release preserves the 1.0.0 public interfaces, existing Model Routes,
35
+ The 1.0.2 release preserves the 1.0.0 public interfaces, existing Model Routes,
36
36
  default entry points and Markdown/history formats. The new host-model MCP route
37
37
  is optional and selected with `--profile host`; upgrading does not convert an
38
38
  existing Codex or Hermes connection to it. No formal Vault migration is required.
39
39
  The host contract identifier remains `memleaf-host-v2.0-rc1`; protocol identity
40
40
  and package release numbers are separate.
41
41
 
42
+ Version 1.0.2 fixes incomplete host freezing/recovery, multiline evidence
43
+ serialization and Codex todo retrieval Hooks. Complete older frozen groups
44
+ remain recoverable; incomplete groups and impossible saved receipts fail closed
45
+ without inventing writes or refunding processing budgets. Existing model-profile
46
+ Codex users should rerun the installer to update known old Hook matchers, then
47
+ follow Codex's Hook review/trust flow. Shared third-party Hook groups are preserved
48
+ and reported for explicit resolution. A package upgrade alone does not alter
49
+ host configuration.
50
+
42
51
  The host route uses the running Agent model and does not require a separate
43
52
  Memleaf model API key. A local Owner still supplies client credentials,
44
53
  read/write scopes, permissions and acceptable source trust. `host-grant` emits
@@ -96,3 +105,8 @@ independent Memleaf model calls; host requests and token usage remain separate.
96
105
  Corrections and explicit Owner-approved budgets were part of acceptance. This
97
106
  evidence does not certify every model/client or live Windows/Linux model use;
98
107
  native installation and package CI are distinct from real-model acceptance.
108
+
109
+ For 1.0.2, 24 new deterministic regressions and all 152 existing host checks
110
+ passed, including isolated installed-package and real stdio/Hook subprocess
111
+ checks. No new real Codex/Hermes model session or production-Vault migration was
112
+ performed for this patch; those scopes remain distinct from native package CI.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "1.0.1"
7
+ version = "1.0.2"
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__ = "1.0.1"
3
+ __version__ = "1.0.2"
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
@@ -592,6 +592,7 @@ def merge_hook_config(
592
592
  *,
593
593
  container_key: str | None = None,
594
594
  dry_run: bool = False,
595
+ matcher_upgrades: Mapping[str, Mapping[str, str]] | None = None,
595
596
  ) -> HookMergeResult:
596
597
  """Merge command handlers into a host hook JSON file.
597
598
 
@@ -599,6 +600,8 @@ def merge_hook_config(
599
600
  a top-level ``hooks`` object and passes ``container_key="hooks"``.
600
601
  Existing handlers are left untouched; a matching memleaf command is
601
602
  idempotent and a different memleaf command is treated as a conflict.
603
+ An adapter may explicitly migrate known old matchers on groups owned
604
+ entirely by the matching command; unrelated groups stay unchanged.
602
605
  """
603
606
 
604
607
  target = Path(path)
@@ -659,6 +662,23 @@ def merge_hook_config(
659
662
  command = requested_handler.get("command")
660
663
  if not isinstance(command, str) or not command:
661
664
  return HookMergeResult("diagnostic", "hook command is invalid; unchanged")
665
+ # Upgrade only an explicitly recognized old matcher belonging
666
+ # entirely to this command. Never rewrite a shared host group
667
+ # whose matcher also controls unrelated hooks.
668
+ upgrades = (matcher_upgrades or {}).get(event, {})
669
+ for index, item in enumerate(existing_items):
670
+ if not isinstance(item, Mapping) or not isinstance(item.get("matcher"), str):
671
+ continue
672
+ replacement = upgrades.get(item["matcher"])
673
+ hooks = item.get("hooks") if container_key == "hooks" else None
674
+ if replacement is None or not isinstance(hooks, list) or not hooks:
675
+ continue
676
+ if not any(isinstance(hook, Mapping) and hook.get("command") == command for hook in hooks):
677
+ continue
678
+ if not all(isinstance(hook, Mapping) and hook.get("command") == command for hook in hooks):
679
+ return HookMergeResult("diagnostic", "existing memleaf matcher shares unrelated hooks; unchanged")
680
+ existing_items[index] = {**item, "matcher": replacement}
681
+ updated = True
662
682
  matching, conflict = _hook_command_state(existing_items, command, event, container_key)
663
683
  if conflict:
664
684
  return HookMergeResult("diagnostic", "existing memleaf hook conflicts; unchanged")
@@ -484,6 +484,10 @@ def _configure_codex_hooks(
484
484
  definition,
485
485
  container_key="hooks",
486
486
  dry_run=dry_run,
487
+ matcher_upgrades={
488
+ "PreToolUse": {r"^mcp__memleaf__(search|read)$": r"^mcp__memleaf__(search|list_todos|read)$"},
489
+ "PostToolUse": {r"^mcp__memleaf__search$": r"^mcp__memleaf__(search|list_todos)$"},
490
+ },
487
491
  )
488
492
 
489
493
 
@@ -513,13 +517,13 @@ def _codex_hook_definition(
513
517
  "UserPromptSubmit": [{"hooks": [user_prompt]}],
514
518
  "PreToolUse": [
515
519
  {
516
- "matcher": r"^mcp__memleaf__(search|read)$",
520
+ "matcher": r"^mcp__memleaf__(search|list_todos|read)$",
517
521
  "hooks": [pre_tool],
518
522
  }
519
523
  ],
520
524
  "PostToolUse": [
521
525
  {
522
- "matcher": r"^mcp__memleaf__search$",
526
+ "matcher": r"^mcp__memleaf__(search|list_todos)$",
523
527
  "hooks": [post_tool],
524
528
  }
525
529
  ],
@@ -307,9 +307,13 @@ def _dump_scalar(value: Any) -> str:
307
307
  if isinstance(value, (int, float)) and not isinstance(value, bool):
308
308
  return str(value)
309
309
  if isinstance(value, str):
310
- if "\n" in value or "\r" in value:
311
- raise FrontmatterError("multiline strings are not supported")
312
- return json.dumps(value, ensure_ascii=False)
310
+ # JSON escapes retain line breaks in a single YAML quoted scalar.
311
+ # Escape the remaining Unicode line separators too: splitlines() is
312
+ # used by the restricted parser and must not split inside a scalar.
313
+ return (json.dumps(value, ensure_ascii=False)
314
+ .replace("\u0085", "\\u0085")
315
+ .replace("\u2028", "\\u2028")
316
+ .replace("\u2029", "\\u2029"))
313
317
  raise FrontmatterError("unsupported value type")
314
318
 
315
319
 
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 1.0.1
2
+ version: 1.0.2
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch
@@ -403,7 +403,11 @@ def _codex_todo_result(value: Any) -> tuple[str, bool, str | None]:
403
403
  return "error", False, None
404
404
  if not all(
405
405
  isinstance(item, Mapping)
406
- and set(item) == {"memory_id", "title", "due_date"}
406
+ # Directory rows also carry status, ownership, scope and history
407
+ # metadata. Only the identity/date fields are required to observe
408
+ # retrieval; rejecting those additive fields would mark real lists
409
+ # as errors and block the following bounded read.
410
+ and {"memory_id", "title", "due_date"}.issubset(item)
407
411
  and isinstance(item.get("memory_id"), str) and bool(item.get("memory_id"))
408
412
  and isinstance(item.get("title"), str) and bool(item.get("title"))
409
413
  and (item.get("due_date") is None or isinstance(item.get("due_date"), str))
@@ -58,6 +58,18 @@ class HostMemory:
58
58
  raise ValueError()
59
59
  if any(not isinstance(payload[k], dict) for k in ("works", "sources", "requests", "cursors", "plans")):
60
60
  raise ValueError()
61
+ # Reject impossible saved receipts left by older interrupted
62
+ # freezing, including cached replay of a false completion.
63
+ for work in payload["works"].values():
64
+ for entry in work["items"].values():
65
+ receipt = entry["receipt"]
66
+ if receipt["status"] == "saved":
67
+ ids = receipt["memory_ids"]
68
+ committed = receipt["committed_revisions"]
69
+ if (not ids or len(ids) != len(set(ids))
70
+ or [row["memory_id"] for row in committed] != ids
71
+ or receipt["applied"] is not True or receipt["settled"] is not True):
72
+ raise ValueError()
61
73
  return payload
62
74
  except (OSError, ValueError, KeyError, TypeError) as exc:
63
75
  raise V2Error("STATE_CORRUPT", "Host work ledger cannot be verified") from exc
@@ -561,16 +573,20 @@ class HostMemory:
561
573
  self.auth.check(context, "memory.maintain", work=work)
562
574
  if item.get("update_kind") == "retract_memory" or any(p.get("op") == "retract" for p in item.get("body_patch", [])):
563
575
  self.auth.check(context, "memory.retract", work=work)
564
- self._freeze(entry, plan, snapshot)
576
+ frozen_entry = deepcopy(entry)
577
+ self._freeze(frozen_entry, plan, snapshot)
565
578
  if item["action"] == "COMPACT":
566
579
  approval_plan = plan.get("approval_plan")
567
580
  if approval_plan is None:
568
581
  approval_plan = {"target": item["target"], "revision": snapshot["targets"][item["target"]]["revision"],
569
582
  "new_body": item["new_body"], "mapping": item["mapping"]}
570
583
  self.auth.consume_approval(context, item["approval_ref"], "body_compaction", "sha256:" + digest(approval_plan), work_id=work["work_id"], locked=True)
584
+ entry.update(frozen_entry)
571
585
  entry["removed_ranges"] = plan.get("removed_ranges", {})
572
586
  proposals.append(entry)
573
- except V2Error as exc:
587
+ except ValueError as exc:
588
+ if not isinstance(exc, V2Error):
589
+ exc = V2Error("INVALID_SCHEMA", "Memory cannot be serialized into a valid frozen group")
574
590
  exc.budget_charged = True
575
591
  receipt["error"] = exc.public()
576
592
  # Reject implicit dependencies across independently submitted Items.
@@ -589,12 +605,15 @@ class HostMemory:
589
605
  if receipt["action"] == "DEFERRED":
590
606
  receipt["status"] = "deferred"
591
607
  return
592
- receipt.update(operation_id=_id("op_"), group_id=_id("group_") if plan["changes"] else None, status="prepared")
608
+ # Publish prepared only after every group member can be serialized and
609
+ # validated. A later member failure must leave no executable prefix.
610
+ operation_id = _id("op_")
611
+ operations = []
593
612
  now = utc_now()
594
613
  for change in plan["changes"]:
595
614
  before = Memory.from_mapping(change["before"]) if change.get("before") else None
596
615
  after = Memory.from_mapping(change["after"])
597
- after.extra["incremental_operation_id"] = receipt["operation_id"]
616
+ after.extra["incremental_operation_id"] = operation_id
598
617
  after.updated = now
599
618
  if before:
600
619
  after.created = before.created
@@ -604,15 +623,71 @@ class HostMemory:
604
623
  "message_revision": source["revision"], "trust": source["trust"], "role": source["role"]}
605
624
  if evidence not in after.sources:
606
625
  after.sources.append(evidence)
607
- entry["operations"].append({"action": "UPDATE" if before else "CREATE", "memory_id": after.memory_id,
608
- "operation_id": receipt["operation_id"], "prepared_at": now,
626
+ operations.append({"action": "UPDATE" if before else "CREATE", "memory_id": after.memory_id,
627
+ "operation_id": operation_id, "prepared_at": now,
609
628
  "before": before.to_markdown() if before else None, "after": after.to_markdown(),
610
629
  "expected_revision": revision(before).removeprefix("sha256:") if before else None,
611
630
  "replacement_revision": revision(after).removeprefix("sha256:"), "applied": False})
612
- receipt["memory_ids"] = [c["memory_id"] for c in entry["operations"]]
631
+ frozen_receipt = {**receipt, "operation_id": operation_id,
632
+ "group_id": _id("group_") if operations else None,
633
+ "status": "prepared", "memory_ids": [c["memory_id"] for c in operations]}
613
634
  if not plan["changes"]:
614
- receipt["memory_ids"] = [snapshot["targets"][r]["memory"]["memory_id"] for r in plan.get("target_refs", [])]
615
- receipt.update(status="not_recorded" if receipt["action"] == "NO_MEMORY" else "unchanged", settled=True)
635
+ if receipt["action"] not in {"NO_CHANGE", "NO_MEMORY"}:
636
+ raise V2Error("STATE_CORRUPT", "A write requires a complete frozen operation group")
637
+ frozen_receipt["memory_ids"] = [snapshot["targets"][r]["memory"]["memory_id"] for r in plan.get("target_refs", [])]
638
+ frozen_receipt.update(status="not_recorded" if receipt["action"] == "NO_MEMORY" else "unchanged", settled=True)
639
+ else:
640
+ self._validate_frozen({**entry, "receipt": frozen_receipt, "operations": operations}, snapshot)
641
+ entry.update(operations=operations, frozen_digest=self._frozen_digest(operations))
642
+ receipt.update(frozen_receipt)
643
+
644
+ @staticmethod
645
+ def _frozen_digest(operations: list[dict[str, Any]]) -> str:
646
+ # Application bookkeeping changes during recovery; frozen payloads do not.
647
+ fields = ("action", "memory_id", "operation_id", "prepared_at", "before", "after",
648
+ "expected_revision", "replacement_revision")
649
+ return digest([{key: operation[key] for key in fields} for operation in operations])
650
+
651
+ def _validate_frozen(self, entry: dict[str, Any], snapshot: dict[str, Any]) -> None:
652
+ """Prove complete membership and payloads before any write or settlement.
653
+
654
+ Old complete groups have no digest but retain their full receipt IDs and
655
+ proposal participants. Old incomplete groups fail closed without repair
656
+ from an inferred/recompiled plan.
657
+ """
658
+ try:
659
+ receipt, operations = entry["receipt"], entry["operations"]
660
+ ids = [op["memory_id"] for op in operations]
661
+ if not ids or len(ids) != len(set(ids)) or ids != receipt["memory_ids"]:
662
+ raise ValueError()
663
+ proposal = entry["proposal"]
664
+ action = receipt["action"]
665
+ if proposal["action"] != action or action not in {"CREATE", "UPDATE", "MERGE", "COMPACT"}:
666
+ raise ValueError()
667
+ if action == "CREATE":
668
+ if len(operations) != 1 or operations[0]["action"] != "CREATE":
669
+ raise ValueError()
670
+ else:
671
+ refs = [proposal["target"], *proposal.get("duplicates", [])]
672
+ expected = [snapshot["targets"][ref]["memory"]["memory_id"] for ref in refs]
673
+ if ids != expected or any(op["action"] != "UPDATE" for op in operations):
674
+ raise ValueError()
675
+ if "frozen_digest" in entry and entry["frozen_digest"] != self._frozen_digest(operations):
676
+ raise ValueError()
677
+ for op in operations:
678
+ after = Memory.from_markdown(op["after"])
679
+ before = Memory.from_markdown(op["before"]) if op["before"] is not None else None
680
+ if (op["operation_id"] != receipt["operation_id"]
681
+ or after.memory_id != op["memory_id"]
682
+ or after.extra.get("incremental_operation_id") != op["operation_id"]
683
+ or revision(after) != "sha256:" + op["replacement_revision"]
684
+ or (op["action"] == "CREATE" and (before is not None or op["expected_revision"] is not None))
685
+ or (op["action"] == "UPDATE" and (before is None or before.memory_id != after.memory_id
686
+ or revision(before) != "sha256:" + op["expected_revision"]))):
687
+ raise ValueError()
688
+ except (KeyError, TypeError, ValueError) as exc:
689
+ raise V2Error("RECOVERY_REQUIRED", "Frozen operation group is incomplete or invalid",
690
+ next_action="contact_owner") from exc
616
691
 
617
692
  def _verify_source(self, state: dict[str, Any], work: dict[str, Any]) -> None:
618
693
  for source in work["snapshot"]["sources"].values():
@@ -661,19 +736,26 @@ class HostMemory:
661
736
  try:
662
737
  if entry.get("deleted_dependency_ids"):
663
738
  raise V2Error("RECOVERY_REQUIRED", "A deleted dependency requires an exact new owner disposition", next_action="contact_owner")
739
+ self._validate_frozen(entry, work["snapshot"])
664
740
  self.auth.check(context, "memory.write", work=work)
665
741
  # Guard every member before beginning a multi-file dependency group.
666
742
  for op in entry["operations"]:
667
743
  actual = self._applied(op)
668
744
  if actual is None:
669
745
  raise V2Error("REVISION_CONFLICT", retryable=True, next_action="contact_owner")
746
+ if actual is False and op.get("applied") is True:
747
+ raise V2Error("RECOVERY_REQUIRED", "Previously applied content is no longer present", next_action="contact_owner")
670
748
  self.auth.check(context, "memory.write", scopes=Memory.from_markdown(op["after"]).scopes, work=work)
671
749
  for op in entry["operations"]:
672
750
  self.auth.check(context, "memory.write", work=work)
673
751
  if self._applied(op) is not True:
674
752
  writer.write_frozen_unlocked(op)
753
+ if self._applied(op) is not True:
754
+ raise V2Error("IO_INTERRUPTED", "Frozen write cannot be confirmed on disk", retryable=True, next_action="resume")
675
755
  op["applied"] = True
676
756
  self._save(state)
757
+ if not all(self._applied(op) is True for op in entry["operations"]):
758
+ raise V2Error("RECOVERY_REQUIRED", "Complete group persistence cannot be confirmed", next_action="contact_owner")
677
759
  r.update(status="saved", applied=True, settled=True, error=None,
678
760
  committed_revisions=[{"memory_id": o["memory_id"], "revision": "sha256:" + o["replacement_revision"]} for o in entry["operations"]])
679
761
  for key, ranges in entry.get("removed_ranges", {}).items():
@@ -56,12 +56,19 @@ def account_work(host: Any, work_id: str) -> dict[str, Any]:
56
56
  continue
57
57
  operations = entry.get("operations", [])
58
58
  actual = [host._applied(operation) for operation in operations]
59
+ complete = False
60
+ if operations and not entry.get("deleted_dependency_ids"):
61
+ try:
62
+ host._validate_frozen(entry, work["snapshot"])
63
+ complete = True
64
+ except V2Error:
65
+ pass
59
66
  for operation, applied in zip(operations, actual):
60
67
  # Keep a remembered application true even if a later external
61
68
  # deletion makes the current CREATE head absent. Do not revive.
62
69
  if applied is not False or not operation.get("applied"):
63
70
  operation["applied"] = applied
64
- if operations and all(applied is True for applied in actual):
71
+ if complete and all(applied is True for applied in actual):
65
72
  receipt.update(status="saved", applied=True, settled=True, error=None,
66
73
  committed_revisions=[{"memory_id": operation["memory_id"],
67
74
  "revision": "sha256:" + operation["replacement_revision"]}
@@ -170,6 +177,8 @@ def _check_recovery_resources(host: Any, state: dict[str, Any], context: dict[st
170
177
  for entry in work["items"].values():
171
178
  if entry["receipt"]["status"] in _FINAL:
172
179
  continue
180
+ if entry.get("operations"):
181
+ host._validate_frozen(entry, work["snapshot"])
173
182
  if entry["receipt"]["action"] in {"MERGE", "COMPACT"}:
174
183
  host.auth.check(context, "memory.maintain")
175
184
  proposal = entry.get("proposal", {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -24,7 +24,7 @@ Dynamic: license-file
24
24
 
25
25
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
26
26
 
27
- > **稳定版:1.0.1。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
27
+ > **稳定版:1.0.2。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
28
28
  > **Codex 可通过 `--profile host` 使用当前宿主模型完成记忆闭环,无需另配 Memleaf 模型 API Key。** 本地 Owner 仍需配置客户端身份、读写权限及来源信任;客户端主动调用工具,不代表每轮自动记忆。
29
29
  > host profile 支持独立于内容类型的 `actionable` 和结构化 `waiting_on`,每份 work 默认最多三次被接受的 submit;Memleaf 负责确定性校验、落盘与恢复。
30
30
  > 默认 `model` profile 和 Hermes 原生接入继续使用独立 Model Route,提炼与语义复核共用每份工作的最多五次模型请求。旧接口、现有记忆和默认入口保留,升级无需正式 Vault 迁移。Antigravity(反重力)当前不支持。
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