memleaf 0.2.32__tar.gz → 0.2.33__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 (182) hide show
  1. {memleaf-0.2.32 → memleaf-0.2.33}/CHANGELOG.md +9 -0
  2. {memleaf-0.2.32/src/memleaf.egg-info → memleaf-0.2.33}/PKG-INFO +14 -8
  3. {memleaf-0.2.32 → memleaf-0.2.33}/README.en.md +8 -6
  4. {memleaf-0.2.32 → memleaf-0.2.33}/README.md +13 -7
  5. {memleaf-0.2.32 → memleaf-0.2.33}/docs/config-migrations.md +1 -1
  6. {memleaf-0.2.32 → memleaf-0.2.33}/docs/evidence-retention.md +11 -6
  7. {memleaf-0.2.32 → memleaf-0.2.33}/docs/gate-evidence-boundary.md +49 -0
  8. {memleaf-0.2.32 → memleaf-0.2.33}/docs/general-processing.md +59 -3
  9. memleaf-0.2.33/examples/README.md +35 -0
  10. memleaf-0.2.33/examples/live_core_lifecycle_acceptance.py +156 -0
  11. {memleaf-0.2.32 → memleaf-0.2.33}/pyproject.toml +1 -1
  12. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/__init__.py +1 -1
  13. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/admission.py +501 -23
  14. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/cli.py +26 -0
  15. memleaf-0.2.33/src/memleaf/create_coordinator.py +380 -0
  16. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/evidence_policy.py +32 -3
  17. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/hermes_provider/README.md +13 -0
  18. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/hermes_provider/__init__.py +232 -16
  19. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  20. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/host_runtime.py +6 -2
  21. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/installer.py +3 -0
  22. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/mcp_server.py +7 -2
  23. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/memory_planner.py +817 -116
  24. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/memory_writer.py +3 -2
  25. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/model_execution.py +55 -7
  26. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/planning_context.py +72 -16
  27. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/process_common.py +235 -12
  28. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/process_journal.py +205 -14
  29. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/processing.py +7 -2
  30. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/prompts.py +203 -19
  31. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/provenance.py +1 -1
  32. memleaf-0.2.33/src/memleaf/target_reconciliation.py +420 -0
  33. memleaf-0.2.33/src/memleaf/update_coordinator.py +496 -0
  34. memleaf-0.2.33/src/memleaf/update_review.py +297 -0
  35. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/validation.py +37 -16
  36. {memleaf-0.2.32 → memleaf-0.2.33/src/memleaf.egg-info}/PKG-INFO +14 -8
  37. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf.egg-info/SOURCES.txt +20 -1
  38. {memleaf-0.2.32 → memleaf-0.2.33}/tests/semantic_fixtures.py +19 -0
  39. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_admission_noise.py +29 -0
  40. memleaf-0.2.33/tests/test_automatic_duplicate_noop_collision.py +121 -0
  41. memleaf-0.2.33/tests/test_due_date_grounding.py +122 -0
  42. memleaf-0.2.33/tests/test_due_date_grounding_retry.py +135 -0
  43. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_email_actionable_coverage.py +4 -0
  44. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_evidence_budget.py +15 -13
  45. memleaf-0.2.33/tests/test_evidence_retention_policy.py +465 -0
  46. memleaf-0.2.33/tests/test_external_source_dates.py +258 -0
  47. memleaf-0.2.33/tests/test_gate_capacity.py +636 -0
  48. memleaf-0.2.33/tests/test_gate_schema_repair.py +200 -0
  49. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_general_evidence_admission.py +214 -3
  50. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_hermes_provider.py +38 -0
  51. memleaf-0.2.33/tests/test_hermes_transport_evidence.py +94 -0
  52. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_install.py +1 -1
  53. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_maintenance_v2.py +264 -1
  54. memleaf-0.2.33/tests/test_new_scope_source_grounding.py +218 -0
  55. memleaf-0.2.33/tests/test_partial_retry_idempotency.py +336 -0
  56. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_phase2_model_decisions.py +4 -0
  57. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_pypi_install.py +6 -0
  58. memleaf-0.2.33/tests/test_read_only_deferred_isolation.py +190 -0
  59. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b1.py +43 -5
  60. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b2a.py +4 -0
  61. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b2b.py +11 -5
  62. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3b_scope.py +8 -8
  63. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3d_scope_maintenance.py +5 -1
  64. memleaf-0.2.33/tests/test_summary_date_grounding_integration.py +39 -0
  65. memleaf-0.2.33/tests/test_target_reconciliation.py +345 -0
  66. memleaf-0.2.33/tests/test_target_reconciliation_integration.py +50 -0
  67. memleaf-0.2.33/tests/test_update_review.py +435 -0
  68. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_update_target_recovery.py +54 -0
  69. memleaf-0.2.33/tests/test_whole_unit_bindings.py +368 -0
  70. memleaf-0.2.32/examples/README.md +0 -19
  71. memleaf-0.2.32/src/memleaf/update_coordinator.py +0 -200
  72. memleaf-0.2.32/tests/test_evidence_retention_policy.py +0 -201
  73. {memleaf-0.2.32 → memleaf-0.2.33}/IMPLEMENTATION_PLAN.md +0 -0
  74. {memleaf-0.2.32 → memleaf-0.2.33}/LICENSE +0 -0
  75. {memleaf-0.2.32 → memleaf-0.2.33}/MANIFEST.in +0 -0
  76. {memleaf-0.2.32 → memleaf-0.2.33}/RELEASE_CHECKLIST.md +0 -0
  77. {memleaf-0.2.32 → memleaf-0.2.33}/docs/capture-budget-design.md +0 -0
  78. {memleaf-0.2.32 → memleaf-0.2.33}/docs/core-refactor.md +0 -0
  79. {memleaf-0.2.32 → memleaf-0.2.33}/docs/hermes-mcp-runtime.md +0 -0
  80. {memleaf-0.2.32 → memleaf-0.2.33}/docs/performance.md +0 -0
  81. {memleaf-0.2.32 → memleaf-0.2.33}/docs/v0.2.26-processing-status.md +0 -0
  82. {memleaf-0.2.32 → memleaf-0.2.33}/examples/basic_usage.py +0 -0
  83. {memleaf-0.2.32 → memleaf-0.2.33}/examples/live_processing_acceptance.py +0 -0
  84. {memleaf-0.2.32 → memleaf-0.2.33}/examples/mcp_stdio.ndjson +0 -0
  85. {memleaf-0.2.32 → memleaf-0.2.33}/install.ps1 +0 -0
  86. {memleaf-0.2.32 → memleaf-0.2.33}/install.sh +0 -0
  87. {memleaf-0.2.32 → memleaf-0.2.33}/setup.cfg +0 -0
  88. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/__main__.py +0 -0
  89. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/adapters/__init__.py +0 -0
  90. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/adapters/antigravity.py +0 -0
  91. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/adapters/base.py +0 -0
  92. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/adapters/codex.py +0 -0
  93. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/adapters/hermes.py +0 -0
  94. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/budget.py +0 -0
  95. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/capture.py +0 -0
  96. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/compaction.py +0 -0
  97. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/config.py +0 -0
  98. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/credentials.py +0 -0
  99. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/evidence_budget.py +0 -0
  100. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/frontmatter.py +0 -0
  101. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  102. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/hermes_runtime.py +0 -0
  103. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/host_events.py +0 -0
  104. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/inbox.py +0 -0
  105. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/index.py +0 -0
  106. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/inspection.py +0 -0
  107. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/llm/__init__.py +0 -0
  108. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/llm/base.py +0 -0
  109. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/llm/claude_compatible.py +0 -0
  110. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/llm/gemini.py +0 -0
  111. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/llm/openai_compatible.py +0 -0
  112. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/llm/router.py +0 -0
  113. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/locking.py +0 -0
  114. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/memory_commit.py +0 -0
  115. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/model_discovery.py +0 -0
  116. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/models.py +0 -0
  117. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/native_index.py +0 -0
  118. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/native_registration.py +0 -0
  119. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/process_owner.py +0 -0
  120. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/recording_policy.py +0 -0
  121. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/redaction.py +0 -0
  122. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/retention.py +0 -0
  123. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/retrieval.py +0 -0
  124. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/retrieval_gate.py +0 -0
  125. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/scope_maintenance.py +0 -0
  126. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/scope_state.py +0 -0
  127. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/service.py +0 -0
  128. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/source_policy.py +0 -0
  129. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/state_layout.py +0 -0
  130. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/turn_audit.py +0 -0
  131. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/turn_plan.py +0 -0
  132. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf/vault.py +0 -0
  133. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf.egg-info/dependency_links.txt +0 -0
  134. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf.egg-info/entry_points.txt +0 -0
  135. {memleaf-0.2.32 → memleaf-0.2.33}/src/memleaf.egg-info/top_level.txt +0 -0
  136. {memleaf-0.2.32 → memleaf-0.2.33}/tests/__init__.py +0 -0
  137. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_codex_install.py +0 -0
  138. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_codex_native_cli.py +0 -0
  139. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_config_migrations_v028.py +0 -0
  140. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_context_budget.py +0 -0
  141. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_credential_safety.py +0 -0
  142. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_cross_host_acceptance.py +0 -0
  143. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_cross_turn_dedupe_regressions.py +0 -0
  144. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_extraction_quality_regressions.py +0 -0
  145. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_general_tool_provenance.py +0 -0
  146. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_global_todo_acceptance.py +0 -0
  147. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_global_todo_query_no_write.py +0 -0
  148. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_global_todo_retrieval.py +0 -0
  149. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_hermes_native_registration.py +0 -0
  150. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_hermes_runtime_install.py +0 -0
  151. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_hermes_stdio_transport.py +0 -0
  152. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_host_events.py +0 -0
  153. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_host_runtime_contract.py +0 -0
  154. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_inspection_state_v028.py +0 -0
  155. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_long_run_hygiene.py +0 -0
  156. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_model_discovery.py +0 -0
  157. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_model_owned_fields.py +0 -0
  158. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_process_owner_locking.py +0 -0
  159. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_processing_contract_v026.py +0 -0
  160. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_retrieval_gate.py +0 -0
  161. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_retrieval_v2.py +0 -0
  162. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_session_lineage.py +0 -0
  163. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_shared_memory_refactor.py +0 -0
  164. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_source_neutral_todos_v028.py +0 -0
  165. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_a.py +0 -0
  166. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3a_commit.py +0 -0
  167. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3a_contract.py +0 -0
  168. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3b_native_context.py +0 -0
  169. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3b_native_index.py +0 -0
  170. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_b3c_retrieval.py +0 -0
  171. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_c1_mcp.py +0 -0
  172. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_c2_init.py +0 -0
  173. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_stage_c3_packaging.py +0 -0
  174. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_state_layout_v028.py +0 -0
  175. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_todo_state_recovery.py +0 -0
  176. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_upgrade_preserves_vault.py +0 -0
  177. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_v023_scope_correction.py +0 -0
  178. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_v2_gate_limits.py +0 -0
  179. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_v2_host_flow.py +0 -0
  180. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_v2_mcp_flow.py +0 -0
  181. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_v2_nomatch_semantics.py +0 -0
  182. {memleaf-0.2.32 → memleaf-0.2.33}/tests/test_v2_search_gate_acceptance.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to memleaf are documented here.
4
4
 
5
+ ## 0.2.33 — 2026-09-08
6
+
7
+ - Extend the Gate with bounded physical-evidence batches, exact unit/quote bindings, isolated cross-batch candidate IDs, same-target update coordination, and bounded model reconciliation for compatible CREATE proposals. Failed batches remain retryable and fail closed without advancing the watermark or cleanup.
8
+ - Align Core, HostRuntime, and the copied Hermes provider on document/attachment classification and effective capture-policy reporting: ordinary structural files follow the selected retention mode, while explicitly identified attachments still require attachment opt-in and bounded retention.
9
+ - Keep automatic UPDATEs as `NO_CHANGE` when current evidence only restates the target or adds provenance/source metadata; preserve the selected target for real semantic state changes. Add synthetic-document real-model lifecycle acceptance and regressions for coverage, deadlines, todo completion, capture policy, and lifecycle behavior.
10
+ - Harden automatic processing around source/date grounding, target reconciliation, update review, duplicate/no-op collision handling, and partial-retry idempotency so unresolved ownership, target, evidence, or timing stays deferred without fabricated writes.
11
+ - Isolate read-only/general queries from stale deferred automatic turns while preserving retries for assertions, explicit scopes, and external observations; extend Core/Hermes transport and evidence regressions for these boundaries.
12
+ - Validation for this release: the full suite ran 911 tests successfully with 2 skips. A four-phase synthetic-input real-model acceptance also passed; this does not claim real-mail or customer-business acceptance.
13
+
5
14
  ## 0.2.32 — 2026-09-07
6
15
 
7
16
  - Add bounded `unknown_unit` Gate diagnostics that identify the exact response field and retain only allowlisted type/length/digest and expected-set summaries; raw invalid values, legal ID lists and model output remain excluded from normal logs and failed state.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.32
3
+ Version: 0.2.33
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -23,8 +23,8 @@ 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.32。**
27
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版在既有 UTF-8 证据留存预算和 metadata pending 消费策略上,为 Gate 的 `unknown_unit` 提供字段路径、类型/长度/摘要和期望集合摘要等受限诊断,并在同一合法 ID 清单约束下最多三次尝试、最多两次纠正;不记录原始值、不做 ID 猜测,持续失败不推进水位且不触发清理。保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
26
+ > **版本:0.2.33。**
27
+ > 本版扩展有界物理证据 Gate:支持精确 unit/quote 绑定、分批覆盖、跨批候选隔离与受限协调,并在失败时保留可重试的原始轮次;统一 Core、HostRuntime 与 Hermes Provider 的文件/附件留存分类,普通结构化文件遵循所选模式,显式附件仍需单独放行;自动 UPDATE 在仅措辞、复述或来源元数据变化时保持 `NO_CHANGE`,真实状态变化才更新原目标。新增合成文档真实模型生命周期验收示例与对应回归测试。Markdown 仍是唯一事实源,运行时不引入 SQLite。验收仅覆盖合成输入和真实模型路由,不代表真实邮件或客户业务验收。
28
28
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
29
29
 
30
30
  ## 项目定位
@@ -443,7 +443,7 @@ history:
443
443
 
444
444
  ## 隐私与安全边界
445
445
 
446
- - 对话捕获只接收 user/assistant 可见文本,不捕获 system/developer 指令或隐藏推理。匹配到当前轮工具调用的证据另由 `capture.tool_evidence_mode` 控制;新建 Vault 默认 `bounded`(脱敏、有界正文),文件/附件正文默认不留存。旧配置显式关闭工具输出时不自动升级为保留正文;
446
+ - 对话捕获只接收 user/assistant 可见文本,不捕获 system/developer 指令或隐藏推理。匹配到当前轮工具调用的证据另由 `capture.tool_evidence_mode` 控制;新建 Vault 默认 `bounded`(脱敏、有界正文),显式标记为附件的正文默认不留存,普通结构化文件正文按该模式保留。旧配置显式关闭工具输出时不自动升级为保留正文;
447
447
  - 捕获落盘前尽力脱敏常见 API key、Bearer token、Cookie、JWT 和私钥,但脱敏不是加密,也不能保证识别所有敏感信息;
448
448
  - 路径校验、符号链接检查、Vault 锁、同目录临时文件、fsync 和原子替换用于保护本地写入;
449
449
  - memleaf 不主动上传整个 Vault,也没有托管后台、遥测或账号系统;
@@ -490,7 +490,7 @@ MIT,见 [LICENSE](LICENSE)。
490
490
  *Your memories, in files you own.*
491
491
 
492
492
 
493
- ## 通用处理与只读验收(0.2.32)
493
+ ## 通用处理与只读验收(0.2.33)
494
494
 
495
495
  邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
496
496
  自动摘要只能使用获准引用的原文;助手复述和旧记忆回读不能单独授权新增写入。
@@ -507,6 +507,10 @@ memleaf process --vault /path/to/existing/vault --source hermes --session-id SES
507
507
 
508
508
  工具执行状态与证据完整性分别记录;超限、丢失或不完整内容不会被模型的 NO_CHANGE 升级为完整。
509
509
  执行成功但尚有未解决项时,结果显示 `coverage_status=partial`,并保留来源以供有界重试或补充证据。
510
+ `external_evidence_status` 另外说明本批工具正文是否实际可供提炼:`metadata_only` 是仅留元数据,
511
+ `disabled` 是关闭采集,`unavailable` 是没有可用完整正文,`partial` 是只有部分可用,
512
+ `available` 是有可用正文,`not_provided` 是本批没有外部记录。可用正文不等于业务事项已提炼完整,
513
+ 也不证明工具读取了原始邮件或文档的全文。
510
514
  行为、限制、测试协议变更见 [通用处理说明](docs/general-processing.md)。
511
515
 
512
516
  ### 工具证据留存配置
@@ -524,9 +528,11 @@ capture:
524
528
 
525
529
  现有配置未提供新字段时,旧 `include_tool_output: false` 或未设置该开关均按
526
530
  `metadata` 处理,旧 `true` 按 `bounded` 处理。显式新字段优先;新建 Vault 只写
527
- 新字段,不再同时写含义冲突的旧开关。`include_attachments: true` 仍受上述总模式限制。
528
- 宿主通过结构化文件路径、文件/附件标识识别文档;不承诺识别任意 Shell 命令或不透明工具
529
- 隐藏读取的文件。用户粘贴的可见文档和显式 `remember` 内容不属于自动附件抓取。
531
+ 新字段,不再同时写含义冲突的旧开关。`include_attachments: true` 只放行显式标记的
532
+ attachment,且仍受上述总模式限制;普通结构化文件结果不需要该开关。宿主把路径、file、
533
+ file_id 或 file URI 归为 document,只有明确的 `attachment_id` 才归为 attachment;无法仅凭
534
+ 路径判断某个文件是否为附件,也不承诺识别任意 Shell 命令或不透明工具隐藏读取的文件。用户
535
+ 粘贴的可见文档和显式 `remember` 内容不属于自动附件抓取。
530
536
 
531
537
  策略在待捕获缓存、inbox 写入和新模型提炼输入处共同执行。配置收紧不会自动删除已提交
532
538
  记忆或改写已捕获 inbox;失败前已经冻结的提交计划也不作为新的模型调用重新提炼。
@@ -4,8 +4,8 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.2.32.**
8
- > The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. Building on the existing UTF-8 evidence budget and metadata-mode pending-evidence policy, this release adds bounded `unknown_unit` Gate diagnostics for field path, type/length/digest and expected-set summaries, with at most three attempts and two corrections constrained by the same legal-ID inventory; it never logs the raw value or guesses an ID, and a persistently failed Gate does not advance the watermark or trigger cleanup. This preserves source-neutral semantics, Markdown as the sole source of truth, and zero SQLite runtime dependencies. Real-model semantics still require local acceptance with the selected model and representative inputs.
7
+ > **Version: 0.2.33.**
8
+ > This release extends bounded physical-evidence Gate processing with exact unit/quote bindings, batch coverage, isolated cross-batch candidates and bounded model reconciliation, while failed batches remain retryable and fail closed. Core, HostRuntime and the Hermes provider now share document/attachment classification: ordinary structural files follow the selected retention mode, while explicitly marked attachments still require separate opt-in. Automatic UPDATEs return `NO_CHANGE` for wording, restatement or provenance-only changes and retain the selected target only for real semantic state changes. It adds a synthetic-document real-model lifecycle acceptance example and regression coverage. Markdown remains the sole source of truth with no SQLite runtime dependency. Acceptance covers synthetic inputs and the configured real-model route; it does not claim real-mail or customer-business acceptance.
9
9
  > **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
10
10
 
11
11
  ## Project scope
@@ -426,7 +426,7 @@ Directories are normally created with mode `0700`, and files are stored as plain
426
426
 
427
427
  ## Privacy and security boundaries
428
428
 
429
- - Conversation capture accepts visible user/assistant text, never system/developer instructions or hidden reasoning. Matched current-turn tool evidence is controlled separately by `capture.tool_evidence_mode`: new Vaults use bounded/redacted observations; document/attachment bodies are excluded by default. Legacy configurations disabling tool output are not silently opted into body retention.
429
+ - Conversation capture accepts visible user/assistant text, never system/developer instructions or hidden reasoning. Matched current-turn tool evidence is controlled separately by `capture.tool_evidence_mode`: new Vaults use bounded/redacted observations; explicitly marked attachment bodies are excluded by default, while ordinary structural file/document bodies follow the selected mode. Legacy configurations disabling tool output are not silently opted into body retention.
430
430
  - Common API keys, Bearer tokens, cookies, JWTs, and private keys are redacted on a best-effort basis before capture is written. Redaction is not encryption and cannot detect every secret.
431
431
  - Path validation, symlink checks, Vault locks, same-directory temporary files, fsync, and atomic replacement protect local writes.
432
432
  - memleaf does not upload the entire Vault and has no hosted backend, telemetry, or account system.
@@ -474,7 +474,7 @@ MIT; see [LICENSE](LICENSE).
474
474
  *Your memories, in files you own.*
475
475
 
476
476
 
477
- ## General processing and read-only inspection (0.2.32)
477
+ ## General processing and read-only inspection (0.2.33)
478
478
 
479
479
  Dialogue, calendars, tickets, files, web results and other tools share the evidence, coverage and write path.
480
480
  Models interpret semantics; Core validates physical provenance and exact original quotations.
@@ -508,8 +508,10 @@ assistant synthesis or retrieved old memory independent evidence of new facts.
508
508
  For an existing file without the new mode, legacy `include_tool_output: false` or an
509
509
  absent boolean means `metadata`; true means `bounded`. An explicit new mode takes
510
510
  precedence. New Vaults write only the new mode. Attachment opt-in remains subject to the
511
- mode. Adapters classify structural file paths, file IDs and attachment handles, not
512
- arbitrary opaque shell commands. Pasted visible documents and explicit remember text
511
+ mode and applies only to explicitly marked attachments. Adapters classify structural file
512
+ paths, file IDs and file URIs as documents, and an explicit `attachment_id` as an attachment;
513
+ a path alone cannot identify every file that happens to be an attachment. They do not
514
+ classify arbitrary opaque shell commands. Pasted visible documents and explicit remember text
513
515
  are not automatic attachment capture.
514
516
 
515
517
  The policy applies to pending cache, inbox writes, and new model-planning inputs.
@@ -4,8 +4,8 @@
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.32。**
8
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版在既有 UTF-8 证据留存预算和 metadata pending 消费策略上,为 Gate 的 `unknown_unit` 提供字段路径、类型/长度/摘要和期望集合摘要等受限诊断,并在同一合法 ID 清单约束下最多三次尝试、最多两次纠正;不记录原始值、不做 ID 猜测,持续失败不推进水位且不触发清理。保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
7
+ > **版本:0.2.33。**
8
+ > 本版扩展有界物理证据 Gate:支持精确 unit/quote 绑定、分批覆盖、跨批候选隔离与受限协调,并在失败时保留可重试的原始轮次;统一 Core、HostRuntime 与 Hermes Provider 的文件/附件留存分类,普通结构化文件遵循所选模式,显式附件仍需单独放行;自动 UPDATE 在仅措辞、复述或来源元数据变化时保持 `NO_CHANGE`,真实状态变化才更新原目标。新增合成文档真实模型生命周期验收示例与对应回归测试。Markdown 仍是唯一事实源,运行时不引入 SQLite。验收仅覆盖合成输入和真实模型路由,不代表真实邮件或客户业务验收。
9
9
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
10
10
 
11
11
  ## 项目定位
@@ -424,7 +424,7 @@ history:
424
424
 
425
425
  ## 隐私与安全边界
426
426
 
427
- - 对话捕获只接收 user/assistant 可见文本,不捕获 system/developer 指令或隐藏推理。匹配到当前轮工具调用的证据另由 `capture.tool_evidence_mode` 控制;新建 Vault 默认 `bounded`(脱敏、有界正文),文件/附件正文默认不留存。旧配置显式关闭工具输出时不自动升级为保留正文;
427
+ - 对话捕获只接收 user/assistant 可见文本,不捕获 system/developer 指令或隐藏推理。匹配到当前轮工具调用的证据另由 `capture.tool_evidence_mode` 控制;新建 Vault 默认 `bounded`(脱敏、有界正文),显式标记为附件的正文默认不留存,普通结构化文件正文按该模式保留。旧配置显式关闭工具输出时不自动升级为保留正文;
428
428
  - 捕获落盘前尽力脱敏常见 API key、Bearer token、Cookie、JWT 和私钥,但脱敏不是加密,也不能保证识别所有敏感信息;
429
429
  - 路径校验、符号链接检查、Vault 锁、同目录临时文件、fsync 和原子替换用于保护本地写入;
430
430
  - memleaf 不主动上传整个 Vault,也没有托管后台、遥测或账号系统;
@@ -471,7 +471,7 @@ MIT,见 [LICENSE](LICENSE)。
471
471
  *Your memories, in files you own.*
472
472
 
473
473
 
474
- ## 通用处理与只读验收(0.2.32)
474
+ ## 通用处理与只读验收(0.2.33)
475
475
 
476
476
  邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
477
477
  自动摘要只能使用获准引用的原文;助手复述和旧记忆回读不能单独授权新增写入。
@@ -488,6 +488,10 @@ memleaf process --vault /path/to/existing/vault --source hermes --session-id SES
488
488
 
489
489
  工具执行状态与证据完整性分别记录;超限、丢失或不完整内容不会被模型的 NO_CHANGE 升级为完整。
490
490
  执行成功但尚有未解决项时,结果显示 `coverage_status=partial`,并保留来源以供有界重试或补充证据。
491
+ `external_evidence_status` 另外说明本批工具正文是否实际可供提炼:`metadata_only` 是仅留元数据,
492
+ `disabled` 是关闭采集,`unavailable` 是没有可用完整正文,`partial` 是只有部分可用,
493
+ `available` 是有可用正文,`not_provided` 是本批没有外部记录。可用正文不等于业务事项已提炼完整,
494
+ 也不证明工具读取了原始邮件或文档的全文。
491
495
  行为、限制、测试协议变更见 [通用处理说明](docs/general-processing.md)。
492
496
 
493
497
  ### 工具证据留存配置
@@ -505,9 +509,11 @@ capture:
505
509
 
506
510
  现有配置未提供新字段时,旧 `include_tool_output: false` 或未设置该开关均按
507
511
  `metadata` 处理,旧 `true` 按 `bounded` 处理。显式新字段优先;新建 Vault 只写
508
- 新字段,不再同时写含义冲突的旧开关。`include_attachments: true` 仍受上述总模式限制。
509
- 宿主通过结构化文件路径、文件/附件标识识别文档;不承诺识别任意 Shell 命令或不透明工具
510
- 隐藏读取的文件。用户粘贴的可见文档和显式 `remember` 内容不属于自动附件抓取。
512
+ 新字段,不再同时写含义冲突的旧开关。`include_attachments: true` 只放行显式标记的
513
+ attachment,且仍受上述总模式限制;普通结构化文件结果不需要该开关。宿主把路径、file、
514
+ file_id 或 file URI 归为 document,只有明确的 `attachment_id` 才归为 attachment;无法仅凭
515
+ 路径判断某个文件是否为附件,也不承诺识别任意 Shell 命令或不透明工具隐藏读取的文件。用户
516
+ 粘贴的可见文档和显式 `remember` 内容不属于自动附件抓取。
511
517
 
512
518
  策略在待捕获缓存、inbox 写入和新模型提炼输入处共同执行。配置收紧不会自动删除已提交
513
519
  记忆或改写已捕获 inbox;失败前已经冻结的提交计划也不作为新的模型调用重新提炼。
@@ -6,7 +6,7 @@ This document describes the configuration and Vault-layout compatibility boundar
6
6
 
7
7
  The current persisted top-level sections are `vault`, `agents`, `scopes`, `native_sources`, `process`, `history`, `capture`, and `llm`. Retrieval remains Scope Map -> search -> read; there is no configurable legacy injection mode.
8
8
 
9
- `capture.tool_evidence_mode` is the current tool-evidence retention setting and accepts `bounded`, `metadata`, or `off`. `capture.include_attachments` is independent and defaults to `false`.
9
+ `capture.tool_evidence_mode` is the current tool-evidence retention setting and accepts `bounded`, `metadata`, or `off`. `capture.include_attachments` is independent, defaults to `false`, and gates only evidence explicitly identified as an attachment. Ordinary structural file/document results follow the selected mode.
10
10
 
11
11
  ## Deprecated fields
12
12
 
@@ -31,12 +31,17 @@ These are source-retention limits, not a guarantee that every configured model
31
31
  can process the resulting prompt. See [capture budget design](capture-budget-design.md)
32
32
  for the ingestion boundary, model-capacity limitation and acceptance contract.
33
33
 
34
- Document/attachment bodies require include_attachments=true and bounded mode.
35
- HostRuntime and the standalone Hermes adapter classify structural file arguments
36
- using the same tested contract (path/file_path/file_id/attachment_id/file URI,
37
- including bounded nesting). This is not a claim to identify every file hidden
38
- behind arbitrary terminal commands or undocumented remote tools. Direct callers
39
- must truthfully identify document evidence with source_type=document.
34
+ Document bodies follow the selected `tool_evidence_mode`; ordinary structural
35
+ file arguments do not require `include_attachments=true`. Bodies explicitly
36
+ identified as attachments (`source_type=attachment`, or an `attachment_id`
37
+ argument in the host adapters) require `include_attachments=true` and bounded
38
+ mode. A path that happens to point at an attachment cannot be classified as an
39
+ attachment from the path alone. HostRuntime and the standalone Hermes adapter
40
+ use the same tested contract: path/file/file_id/file URI means `document`, while
41
+ an explicit `attachment_id` means `attachment`, including bounded nesting. This
42
+ is not a claim to identify every file hidden behind arbitrary terminal commands
43
+ or undocumented remote tools. Direct callers must truthfully identify document
44
+ or attachment evidence with the matching `source_type`.
40
45
 
41
46
  ## Lifecycle
42
47
 
@@ -44,6 +44,12 @@ The documented response contains `candidates`, `coverage` and
44
44
  including a decision that no candidate is warranted. An empty candidate list is
45
45
  not, by itself, proof of complete coverage.
46
46
 
47
+ Candidates with exact `evidence_bindings` may omit `evidence_event_ids`. Core
48
+ validates the bound unit, quote and role before deriving the source event IDs
49
+ from those units. Explicit event IDs remain strict constraints and are never
50
+ silently replaced. Likewise, unique exact quotes can omit numeric offsets;
51
+ Core computes their source positions without asking the model to count them.
52
+
47
53
  The three empty lists describe a complete response only when there are no
48
54
  model-visible evidence units. With units present, no-admission still needs
49
55
  explicit `NO_CHANGE` or `DEFERRED` accounting. Examples must obey the same rules
@@ -55,6 +61,49 @@ bounded coverage-repair path. Missing accounting must not
55
61
  silently become permission to clean the source turn. Unknown unit IDs,
56
62
  contradictory coverage, invalid spans and unauthorized sources remain errors.
57
63
 
64
+ ## Bounded external records and Gate batches
65
+
66
+ Retained JSON and unstructured external observations remain complete source
67
+ records. Plain-text documents with explicit paragraphs, headings or list items
68
+ use those structural boundaries and preserve the parent section path. Commas
69
+ and sentence punctuation do not create independent units. Every unit retains
70
+ its source record identity and exact text. If a legacy
71
+ record exceeds the capture bound, the host may split it into contiguous
72
+ UTF-8-safe blocks. Every block keeps the same tool/call/record identity and
73
+ exact character offsets; the host never inserts a header or copies text from
74
+ another record into a block.
75
+
76
+ Physical units are sent to the Gate in ordered batches of at most eight units
77
+ and 64 KiB of serialized evidence metadata/body. A complete unit is never
78
+ truncated to fit a batch; a single oversized unit remains a singleton and the
79
+ normal model-output limit is still a hard failure boundary. Every batch receives
80
+ the full current-turn conversation and the same bounded related-memory and
81
+ scope context. Coverage is complete per batch. Candidate IDs are namespaced by
82
+ batch before they enter the turn audit, while unit IDs and source spans remain
83
+ global and immutable.
84
+
85
+ The retained physical text also supplies local related-memory search terms, so
86
+ a short conversation accompanying a document can still retrieve the facts
87
+ already stored from that document. Scope filtering and related-context limits
88
+ still apply. Native memory readers receive the original conversation query;
89
+ this local retrieval step does not send document bodies to another reader.
90
+
91
+ The host waits for all Gate batches before running admission or summarization,
92
+ so a hard failure in a later batch cannot commit an earlier batch's proposals.
93
+ Each batch uses its own candidate IDs and does not receive earlier batches'
94
+ proposals. Same-target updates are reconciled by the update coordinator.
95
+ Cross-batch CREATE proposals with the same validated type and scopes go through
96
+ a bounded model reconciliation step. It must account for every proposal and
97
+ retain the contributing evidence; Core does not merge by keyword or text
98
+ similarity. Failed reconciliation produces a deferred outcome.
99
+
100
+ Partial coverage keeps the unresolved physical units deferred and the source
101
+ turn available for retry without a cleanup deadline. A hard Gate/model failure
102
+ keeps the existing failed processing marker and does not advance the turn
103
+ watermark. These are separate outcomes: accepted partial coverage retains the
104
+ existing journal behavior, while a failed batch never reaches the commit
105
+ boundary.
106
+
58
107
  ## Diagnostics and recovery
59
108
 
60
109
  Preserve the existing failure category, retry bound and watermark behavior.
@@ -1,4 +1,4 @@
1
- # General processing reliability contract — 0.2.32
1
+ # General processing reliability contract — 0.2.33
2
2
 
3
3
  This is source-neutral processing, not a mail extractor. Dialogue, documents,
4
4
  calendars, issue trackers and terminal/tool observations use the same admission
@@ -22,8 +22,23 @@ be evidence; examples, suggestions and hypothetical content are not new facts.
22
22
  The Gate still decides future value, entailment, semantic role, ownership and
23
23
  CREATE/UPDATE/NO_CHANGE. Each writable candidate quotes actual evidence units.
24
24
  Core validates the unit/event identity, physical source, exact text and bounds.
25
+ Summary title/body dates are also checked against the admitted source spans.
26
+ Current user timestamps can anchor supported relative dates; external retrieval
27
+ timestamps cannot. An explicit yearless source date can remain yearless, and
28
+ an update may preserve dates already present in its selected target. Neither
29
+ case authorizes inventing a new year or borrowing dates from another memory.
25
30
  Start/end are relative to unit.text; they may both be omitted for a unique exact
26
31
  quotation, in which case Core locates it without model character counting.
32
+ The model may instead explicitly select a complete supplied unit with
33
+ `unit_id`, `whole_unit: true`, and `role`, omitting quote/start/end. Core resolves
34
+ the original text and canonical offsets from that same batch's inventory.
35
+ This avoids copying multiline text back through the model; it does not repair
36
+ an inaccurate quote, grant authority to metadata or assistant text, or prove
37
+ semantic entailment. Both forms use the same downstream source checks.
38
+ An unregistered model-generated project name must also be supported by that
39
+ candidate's own bound source unit. A name introduced only in the model's
40
+ proposal cannot establish a new Scope. Registered names/aliases and explicit
41
+ user/session scope attribution keep their existing rules.
27
42
  Malformed/ambiguous references fail the contract. Matching a quote proves
28
43
  provenance, not semantic truth. This mechanism is not a universal NLP proof.
29
44
 
@@ -80,6 +95,20 @@ labels them NO_CHANGE. Incomplete turns retain their source instead of being
80
95
  cleaned after the usual grace period. Scope-filtered retries may revisit them;
81
96
  no endless automatic model retry or extra external tool call is introduced.
82
97
 
98
+ `external_evidence_status` reports the effective capture-policy and physical
99
+ source boundary separately from model coverage. Its detail object counts raw
100
+ external records, usable retained body records/bytes, and metadata-only,
101
+ incomplete or unusable records. `available` means complete source text is
102
+ available to the planner; it is not a semantic extraction verdict or proof
103
+ that the host read an entire underlying document. Error and partial native
104
+ execution outputs do not gain authority merely because they contain text.
105
+
106
+ Native Hermes terminal/code `output` envelopes are projected as observed text,
107
+ with execution and host truncation kept as metadata. Explicit text record
108
+ dividers retain each record's header and paragraphs in one exact source span;
109
+ arbitrary application JSON remains JSON. Historical document dates must not
110
+ be shifted to the time at which a tool retrieved the document.
111
+
83
112
  Tool evidence uses one shared budget: 64 source records, at most 32 KiB of UTF-8
84
113
  body text per record and 128 KiB of total body text, plus 320-character metadata
85
114
  fields, with redaction at Core capture. Cache and inbox normalization are
@@ -122,6 +151,33 @@ independent titled tasks are not merged solely because their bodies match.
122
151
  Model-assisted same-future-use matching still uses bounded existing candidates;
123
152
  it is not replaced with fuzzy string authorization or embeddings.
124
153
 
154
+ When candidate-specific retrieval discovers an active local memory missing
155
+ from the initial Gate context, a bounded target reconciliation stage compares
156
+ the validated proposal and its evidence against the current records. The model
157
+ chooses CREATE, UPDATE, NO_CHANGE or DEFERRED; an UPDATE must explicitly retain
158
+ the target's type. Insufficient or oversized context defers the proposal.
159
+ This contract is shared by conversation, document and arbitrary tool evidence.
160
+
161
+ Final automatic UPDATE proposals receive a separate semantic review after
162
+ same-target consolidation. The reviewer compares the selected current target,
163
+ admitted source spans and proposed replacement, retaining still-valid old
164
+ information unless current evidence supersedes it. It can accept, revise,
165
+ return NO_CHANGE or defer. Revisions must pass the same source, date, type,
166
+ Scope and target checks; review failure preserves the original memory. This
167
+ adds a bounded model stage, not a local text-concatenation or keyword rule.
168
+ Automatic duplicate observations remain NO_CHANGE ledger entries and do not
169
+ enter the mutation batch as empty metadata operations.
170
+
171
+ Partial semantic retries submit only unresolved evidence units. Settled outcomes
172
+ are retained in the ledger; an identical external observation in a later turn
173
+ is recognized by its source identity and exact content, without interpreting
174
+ business keywords. New conversation assertions keep their distinct turn identity.
175
+ When all newly pending turns in a session are read-only, automatic processing
176
+ does not bundle retries of older deferred turns into that query. Those deferred
177
+ records and their retry allowance remain available; an explicit scope retry
178
+ keeps its existing behavior. Classification uses the current capture policy,
179
+ including when an older inbox record still contains an excluded body.
180
+
125
181
  A retry resumes a matching persisted plan without asking the model for a new
126
182
  summary. CREATE/UPDATE outcomes survive interrupted final-ledger writes.
127
183
  Explicit cross-project correction and retirement preserve their original
@@ -189,8 +245,8 @@ not a passing semantic test. Never publish based only on deterministic mocks.
189
245
 
190
246
  ## Capture policy (shared-core refactor)
191
247
 
192
- Tool evidence is controlled by `capture.tool_evidence_mode` and document opt-in,
193
- not the presence of business words. The same policy runs before cache/inbox writes
248
+ Tool evidence is controlled by `capture.tool_evidence_mode`; only explicitly
249
+ identified attachment evidence also requires the attachment opt-in. The same policy runs before cache/inbox writes
194
250
  and new model-planning calls. Intentional exclusion is not missing evidence.
195
251
  See [retention contract](evidence-retention.md) for legacy settings, plaintext
196
252
  metadata, opaque-resource limitations and the distinction from explicit forget.
@@ -0,0 +1,35 @@
1
+ # Examples
2
+
3
+ `basic_usage.py` runs entirely offline. Without `--vault` it creates a
4
+ temporary vault; pass a directory explicitly when you want to inspect the
5
+ generated Markdown after the process exits.
6
+
7
+ The example shows a lightweight `context()` directory followed by an explicit
8
+ `read_page()` call for the selected entry. Directory results contain no body;
9
+ long bodies can be read in subsequent pages using `next_offset` and `version`.
10
+
11
+ The `mcp_stdio.ndjson` file contains one legacy initialization request and one
12
+ modern discovery request. Pipe it to the stdio adapter with an explicit vault:
13
+
14
+ ```sh
15
+ python -m memleaf.mcp_server --vault <your-vault> < examples/mcp_stdio.ndjson
16
+ ```
17
+
18
+ The NDJSON file is request-only and intentionally contains no tool call,
19
+ network endpoint, local path, or secret.
20
+
21
+ `live_core_lifecycle_acceptance.py` is an opt-in real-model check. It sends only
22
+ generated Cedar/Birch documents to the configured model and writes to a fresh
23
+ temporary Vault. Only the supplied config's `llm` route is read; credentials
24
+ are kept in memory, and existing inboxes, memories, and attachments are never read.
25
+
26
+ ```sh
27
+ PYTHONPATH=src python examples/live_core_lifecycle_acceptance.py --model-config ~/.memleaf/config.yaml
28
+ ```
29
+
30
+ It checks large tool inputs, cross-batch duplicate CREATEs, completion of an
31
+ existing todo, structured deadlines, public todo readback, repeat processing,
32
+ and read-only queries. It fails on incorrect business results even if processing
33
+ reports success. Up to 60 real model calls are permitted; this script is not
34
+ part of the offline test suite. The printed temporary directory contains only
35
+ the synthetic Vault, model prompts/replies, and acceptance results.
@@ -0,0 +1,156 @@
1
+ """Opt-in real-model acceptance with synthetic documents and a fresh Vault.
2
+
3
+ Only the supplied config's llm section is read, in memory. No real inbox,
4
+ knowledge, native source, or attachment is read. Credentials are never copied
5
+ to the diagnostic Vault. This is excluded from automatic unittest discovery.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ from datetime import datetime, timedelta, timezone
11
+ import json
12
+ import os
13
+ from pathlib import Path
14
+ import tempfile
15
+ from typing import Any
16
+
17
+ from memleaf import Memleaf
18
+ from memleaf.config import save_config
19
+ from memleaf.frontmatter import load_yaml
20
+ from memleaf.llm import ModelRouter
21
+
22
+
23
+ class RecordedRoute:
24
+ def __init__(self, route: ModelRouter, output: Path):
25
+ self.route, self.output, self.calls = route, output, 0
26
+
27
+ def complete(self, prompt: str, **kwargs: Any) -> str:
28
+ if self.calls >= 60:
29
+ raise RuntimeError("synthetic acceptance call budget exhausted")
30
+ self.calls += 1
31
+ number = self.calls
32
+ # Inputs and replies contain only the generated synthetic scenario.
33
+ (self.output / f"{number}-request.json").write_text(
34
+ json.dumps({"prompt": prompt, **kwargs}, ensure_ascii=False), encoding="utf-8")
35
+ print(f"call {number}: {kwargs.get('purpose')}", flush=True)
36
+ response = self.route.complete(prompt, **kwargs)
37
+ (self.output / f"{number}-response.json").write_text(response, encoding="utf-8")
38
+ return response
39
+
40
+
41
+ def run(output: Path, backend: RecordedRoute) -> list[dict[str, Any]]:
42
+ core = Memleaf.initialize(output / "vault")
43
+ config = core.vault.config()
44
+ config["native_sources"] = {}
45
+ config["capture"].update(tool_evidence_mode="bounded", include_attachments=False)
46
+ config["scopes"] = {"project:Cedar": {}, "project:Birch": {}}
47
+ save_config(core.vault.config_path, config)
48
+ core.create_memory(memory_id="cedar-checklist", title="Cedar deployment checklist",
49
+ body="The Cedar deployment checklist is awaiting completion.", type="todo",
50
+ scopes=["project:Cedar"], scope_source="user", status="active")
51
+ core.create_memory(memory_id="birch-contact", title="Birch contact",
52
+ body="The Birch contact is Morgan.", type="fact", scopes=["project:Birch"], scope_source="user")
53
+
54
+ today = datetime.now(timezone.utc).date()
55
+ report_due, handover_due = (today + timedelta(days=7)).isoformat(), (today + timedelta(days=8)).isoformat()
56
+ actions = {
57
+ 0: f"Project Cedar. Please prepare the Cedar acceptance report by {report_due}.",
58
+ 1: f"Project Cedar. The Cedar deployment checklist is now complete, confirmed on {today}.",
59
+ 2: "Project Birch. The current Birch contact is Morgan.",
60
+ 13: f"For Project Cedar, you need to finish the acceptance report by {report_due}.",
61
+ 22: f"Project Birch. Please arrange the Birch handover meeting by {handover_due}.",
62
+ }
63
+ records = []
64
+ for number in range(23):
65
+ log = "\n".join(f"run-{number:02d}-{row:03d}, check result=normal, observed value={row:03d};"
66
+ for row in range(56))
67
+ records.append({"tool_name": "read_file", "call_id": f"synthetic-read-{number}",
68
+ "record_id": f"synthetic-record-{number}", "schema_version": "2",
69
+ "kind": "external_observation", "result_status": "success",
70
+ "execution_status": "success", "completeness": "complete", "source_type": "document",
71
+ "content": json.dumps({"record": number, "body": actions.get(number,
72
+ "Routine diagnostic output only; no new project decision, assignment, or state change."),
73
+ "diagnostic_log": log}, ensure_ascii=False)})
74
+
75
+ def capture(number: int, *, query_only: bool = False) -> None:
76
+ user = ("What are my current Project Cedar and Project Birch todos?" if query_only else
77
+ "I manage Project Cedar and Project Birch. Review these source records and retain confirmed "
78
+ "follow-up actions and state changes. Do not retain routine diagnostic logs.")
79
+ assistant = ("Your active follow-ups are the Cedar acceptance report and Birch handover meeting. "
80
+ "The Cedar deployment checklist is completed." if query_only else
81
+ "I reviewed the supplied source records.")
82
+ core.capture("hermes", "synthetic-lifecycle", f"turn-{number}", "user", user,
83
+ event_id=f"synthetic-u-{number}")
84
+ core.capture("hermes", "synthetic-lifecycle", f"turn-{number}", "assistant", assistant,
85
+ event_id=f"synthetic-a-{number}", tool_evidence=None if query_only else records)
86
+
87
+ def snapshot() -> dict[str, bytes]:
88
+ return {str(path.relative_to(core.vault.root)): path.read_bytes()
89
+ for area in ("knowledge", "history") for path in core.vault.list_markdown(area)}
90
+
91
+ rows = []
92
+ capture(1)
93
+ for phase in ("first", "same_turn", "new_turn", "query_only"):
94
+ if phase == "new_turn":
95
+ capture(2)
96
+ elif phase == "query_only":
97
+ capture(3, query_only=True)
98
+ calls_before, before = backend.calls, snapshot()
99
+ result = core.process(source="hermes", session_id="synthetic-lifecycle", model=backend)
100
+ row = {"phase": phase, "calls": backend.calls - calls_before, "result": result}
101
+ rows.append(row)
102
+ (output / "results.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
103
+ memories = [record.memory for record in core._read_memories_unlocked("knowledge")]
104
+ (output / f"{phase}-memories.json").write_text(
105
+ json.dumps([memory.to_dict() for memory in memories], ensure_ascii=False, indent=2), encoding="utf-8")
106
+ todos = core.list_todos(status="active")
107
+ (output / f"{phase}-todos.json").write_text(json.dumps(todos, ensure_ascii=False), encoding="utf-8")
108
+
109
+ assert result.get("coverage_status") == "complete" and result.get("deferred_candidates", 0) == 0, \
110
+ "confirmed synthetic evidence remains unresolved"
111
+ assert len(memories) == 4, "expected two seeded memories and two distinct new todos"
112
+ assert core.read("cedar-checklist").status == "completed", "existing checklist was not completed"
113
+ cedar = [memory for memory in memories if memory.type == "todo"
114
+ and memory.scopes == ["project:Cedar"] and memory.memory_id != "cedar-checklist"]
115
+ birch = [memory for memory in memories if memory.type == "todo" and memory.scopes == ["project:Birch"]]
116
+ assert len(cedar) == 1 and cedar[0].status == "active", "Cedar report was lost, duplicated, or closed"
117
+ assert len(birch) == 1 and birch[0].status == "active", "final-source Birch handover was lost or duplicated"
118
+ assert cedar[0].due_date == report_due and birch[0].due_date == handover_due, "source deadlines were lost"
119
+ assert len(todos["results"]) == 2, "public todo readback did not return both active follow-ups"
120
+ if phase != "first":
121
+ assert result["memories_written"] == 0 and snapshot() == before, "repeated facts or a query changed memory"
122
+ if phase == "same_turn":
123
+ assert backend.calls == calls_before, "already-processed turn called the model again"
124
+ row["status"] = "pass"
125
+ print(json.dumps({"phase": phase, "status": "pass", "calls": row["calls"]}), flush=True)
126
+ return rows
127
+
128
+
129
+ def main() -> int:
130
+ parser = argparse.ArgumentParser(description=__doc__)
131
+ parser.add_argument("--model-config", type=Path, required=True,
132
+ help="read only llm routing from this config; never read its Vault content")
133
+ args = parser.parse_args()
134
+ os.umask(0o077)
135
+ output = Path(tempfile.mkdtemp(prefix="memleaf-live-core-"))
136
+ print(f"Synthetic acceptance output: {output}", flush=True)
137
+ try:
138
+ # Parse this file without validating or probing unrelated native paths.
139
+ route_config = load_yaml(args.model_config.read_text(encoding="utf-8"))["llm"]
140
+ backend = RecordedRoute(ModelRouter.from_config({"llm": route_config}), output)
141
+ rows = run(output, backend)
142
+ result = {"status": "pass", "calls": backend.calls, "phases": rows}
143
+ except Exception as error:
144
+ result = {"status": "fail", "error_type": type(error).__name__,
145
+ "validation_detail": getattr(error, "validation_detail", None),
146
+ "evidence_check": getattr(error, "evidence_check", None)}
147
+ # Assertion messages are authored above; do not expose server exception text.
148
+ if isinstance(error, AssertionError):
149
+ result["assertion"] = str(error)
150
+ (output / "summary.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
151
+ print(json.dumps({key: value for key, value in result.items() if key != "phases"}), flush=True)
152
+ return 0 if result["status"] == "pass" else 1
153
+
154
+
155
+ if __name__ == "__main__":
156
+ raise SystemExit(main())
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.32"
7
+ version = "0.2.33"
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.32"
3
+ __version__ = "0.2.33"
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