memleaf 0.2.30__tar.gz → 0.2.32__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 (160) hide show
  1. {memleaf-0.2.30 → memleaf-0.2.32}/CHANGELOG.md +13 -0
  2. {memleaf-0.2.30/src/memleaf.egg-info → memleaf-0.2.32}/PKG-INFO +4 -4
  3. {memleaf-0.2.30 → memleaf-0.2.32}/README.en.md +3 -3
  4. {memleaf-0.2.30 → memleaf-0.2.32}/README.md +3 -3
  5. memleaf-0.2.32/docs/capture-budget-design.md +74 -0
  6. {memleaf-0.2.30 → memleaf-0.2.32}/docs/evidence-retention.md +11 -3
  7. {memleaf-0.2.30 → memleaf-0.2.32}/docs/gate-evidence-boundary.md +18 -0
  8. {memleaf-0.2.30 → memleaf-0.2.32}/docs/general-processing.md +10 -7
  9. {memleaf-0.2.30 → memleaf-0.2.32}/pyproject.toml +1 -1
  10. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/__init__.py +1 -1
  11. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/admission.py +29 -13
  12. memleaf-0.2.32/src/memleaf/evidence_budget.py +294 -0
  13. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/hermes_provider/__init__.py +28 -19
  14. memleaf-0.2.32/src/memleaf/hermes_provider/evidence_budget.py +294 -0
  15. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  16. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/host_runtime.py +18 -3
  17. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/installer.py +1 -1
  18. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/mcp_server.py +1 -1
  19. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/model_execution.py +37 -2
  20. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/process_common.py +7 -1
  21. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/process_journal.py +2 -1
  22. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/prompts.py +1 -1
  23. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/provenance.py +61 -53
  24. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/validation.py +122 -0
  25. {memleaf-0.2.30 → memleaf-0.2.32/src/memleaf.egg-info}/PKG-INFO +4 -4
  26. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf.egg-info/SOURCES.txt +4 -0
  27. memleaf-0.2.32/tests/test_evidence_budget.py +381 -0
  28. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_evidence_retention_policy.py +2 -2
  29. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_general_evidence_admission.py +128 -1
  30. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_general_tool_provenance.py +2 -2
  31. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_processing_contract_v026.py +6 -6
  32. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_pypi_install.py +1 -0
  33. {memleaf-0.2.30 → memleaf-0.2.32}/IMPLEMENTATION_PLAN.md +0 -0
  34. {memleaf-0.2.30 → memleaf-0.2.32}/LICENSE +0 -0
  35. {memleaf-0.2.30 → memleaf-0.2.32}/MANIFEST.in +0 -0
  36. {memleaf-0.2.30 → memleaf-0.2.32}/RELEASE_CHECKLIST.md +0 -0
  37. {memleaf-0.2.30 → memleaf-0.2.32}/docs/config-migrations.md +0 -0
  38. {memleaf-0.2.30 → memleaf-0.2.32}/docs/core-refactor.md +0 -0
  39. {memleaf-0.2.30 → memleaf-0.2.32}/docs/hermes-mcp-runtime.md +0 -0
  40. {memleaf-0.2.30 → memleaf-0.2.32}/docs/performance.md +0 -0
  41. {memleaf-0.2.30 → memleaf-0.2.32}/docs/v0.2.26-processing-status.md +0 -0
  42. {memleaf-0.2.30 → memleaf-0.2.32}/examples/README.md +0 -0
  43. {memleaf-0.2.30 → memleaf-0.2.32}/examples/basic_usage.py +0 -0
  44. {memleaf-0.2.30 → memleaf-0.2.32}/examples/live_processing_acceptance.py +0 -0
  45. {memleaf-0.2.30 → memleaf-0.2.32}/examples/mcp_stdio.ndjson +0 -0
  46. {memleaf-0.2.30 → memleaf-0.2.32}/install.ps1 +0 -0
  47. {memleaf-0.2.30 → memleaf-0.2.32}/install.sh +0 -0
  48. {memleaf-0.2.30 → memleaf-0.2.32}/setup.cfg +0 -0
  49. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/__main__.py +0 -0
  50. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/adapters/__init__.py +0 -0
  51. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/adapters/antigravity.py +0 -0
  52. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/adapters/base.py +0 -0
  53. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/adapters/codex.py +0 -0
  54. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/adapters/hermes.py +0 -0
  55. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/budget.py +0 -0
  56. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/capture.py +0 -0
  57. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/cli.py +0 -0
  58. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/compaction.py +0 -0
  59. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/config.py +0 -0
  60. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/credentials.py +0 -0
  61. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/evidence_policy.py +0 -0
  62. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/frontmatter.py +0 -0
  63. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/hermes_provider/README.md +0 -0
  64. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/hermes_runtime.py +0 -0
  65. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/host_events.py +0 -0
  66. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/inbox.py +0 -0
  67. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/index.py +0 -0
  68. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/inspection.py +0 -0
  69. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/llm/__init__.py +0 -0
  70. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/llm/base.py +0 -0
  71. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/llm/claude_compatible.py +0 -0
  72. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/llm/gemini.py +0 -0
  73. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/llm/openai_compatible.py +0 -0
  74. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/llm/router.py +0 -0
  75. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/locking.py +0 -0
  76. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/memory_commit.py +0 -0
  77. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/memory_planner.py +0 -0
  78. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/memory_writer.py +0 -0
  79. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/model_discovery.py +0 -0
  80. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/models.py +0 -0
  81. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/native_index.py +0 -0
  82. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/native_registration.py +0 -0
  83. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/planning_context.py +0 -0
  84. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/process_owner.py +0 -0
  85. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/processing.py +0 -0
  86. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/recording_policy.py +0 -0
  87. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/redaction.py +0 -0
  88. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/retention.py +0 -0
  89. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/retrieval.py +0 -0
  90. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/retrieval_gate.py +0 -0
  91. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/scope_maintenance.py +0 -0
  92. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/scope_state.py +0 -0
  93. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/service.py +0 -0
  94. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/source_policy.py +0 -0
  95. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/state_layout.py +0 -0
  96. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/turn_audit.py +0 -0
  97. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/turn_plan.py +0 -0
  98. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/update_coordinator.py +0 -0
  99. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf/vault.py +0 -0
  100. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf.egg-info/dependency_links.txt +0 -0
  101. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf.egg-info/entry_points.txt +0 -0
  102. {memleaf-0.2.30 → memleaf-0.2.32}/src/memleaf.egg-info/top_level.txt +0 -0
  103. {memleaf-0.2.30 → memleaf-0.2.32}/tests/__init__.py +0 -0
  104. {memleaf-0.2.30 → memleaf-0.2.32}/tests/semantic_fixtures.py +0 -0
  105. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_admission_noise.py +0 -0
  106. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_codex_install.py +0 -0
  107. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_codex_native_cli.py +0 -0
  108. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_config_migrations_v028.py +0 -0
  109. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_context_budget.py +0 -0
  110. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_credential_safety.py +0 -0
  111. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_cross_host_acceptance.py +0 -0
  112. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_cross_turn_dedupe_regressions.py +0 -0
  113. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_email_actionable_coverage.py +0 -0
  114. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_extraction_quality_regressions.py +0 -0
  115. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_global_todo_acceptance.py +0 -0
  116. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_global_todo_query_no_write.py +0 -0
  117. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_global_todo_retrieval.py +0 -0
  118. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_hermes_native_registration.py +0 -0
  119. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_hermes_provider.py +0 -0
  120. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_hermes_runtime_install.py +0 -0
  121. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_hermes_stdio_transport.py +0 -0
  122. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_host_events.py +0 -0
  123. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_host_runtime_contract.py +0 -0
  124. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_inspection_state_v028.py +0 -0
  125. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_install.py +0 -0
  126. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_long_run_hygiene.py +0 -0
  127. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_maintenance_v2.py +0 -0
  128. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_model_discovery.py +0 -0
  129. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_model_owned_fields.py +0 -0
  130. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_phase2_model_decisions.py +0 -0
  131. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_process_owner_locking.py +0 -0
  132. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_retrieval_gate.py +0 -0
  133. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_retrieval_v2.py +0 -0
  134. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_session_lineage.py +0 -0
  135. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_shared_memory_refactor.py +0 -0
  136. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_source_neutral_todos_v028.py +0 -0
  137. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_a.py +0 -0
  138. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b1.py +0 -0
  139. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b2a.py +0 -0
  140. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b2b.py +0 -0
  141. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3a_commit.py +0 -0
  142. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3a_contract.py +0 -0
  143. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3b_native_context.py +0 -0
  144. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3b_native_index.py +0 -0
  145. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3b_scope.py +0 -0
  146. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3c_retrieval.py +0 -0
  147. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_b3d_scope_maintenance.py +0 -0
  148. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_c1_mcp.py +0 -0
  149. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_c2_init.py +0 -0
  150. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_stage_c3_packaging.py +0 -0
  151. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_state_layout_v028.py +0 -0
  152. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_todo_state_recovery.py +0 -0
  153. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_update_target_recovery.py +0 -0
  154. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_upgrade_preserves_vault.py +0 -0
  155. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_v023_scope_correction.py +0 -0
  156. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_v2_gate_limits.py +0 -0
  157. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_v2_host_flow.py +0 -0
  158. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_v2_mcp_flow.py +0 -0
  159. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_v2_nomatch_semantics.py +0 -0
  160. {memleaf-0.2.30 → memleaf-0.2.32}/tests/test_v2_search_gate_acceptance.py +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to memleaf are documented here.
4
4
 
5
+ ## 0.2.32 — 2026-09-07
6
+
7
+ - 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.
8
+ - Give bounded correction attempts the failed constraint, field path and immutable legal-ID inventory used by validation, so coverage and evidence-binding references are repaired by regeneration rather than host-side ID substitution or relaxed evidence authority.
9
+ - Keep `invalid_evidence` compatibility, source-neutral physical evidence boundaries, and watermark/cleanup safety unchanged: a persistently failed Gate does not advance the watermark or trigger cleanup.
10
+
11
+ ## 0.2.31 — 2026-09-07
12
+
13
+ - Unify Core and the copied Hermes provider on one idempotent UTF-8 evidence budget: at most 64 source records, 32 KiB per body, and 128 KiB of aggregate body text. Complete matched sources above the former 2,000-character/eight-record limits remain available within those bounds.
14
+ - Keep loss diagnostics bounded to 64 marker identities plus one aggregate marker. Per-record, aggregate, and marker overflow remains explicit incomplete evidence and cannot authorize a write or successful cleanup.
15
+ - Preserve metadata/off, attachment, redaction, and call-ID behavior, and consume metadata-mode pending evidence after a successful capture using the same effective policy on pending and inbox sides.
16
+ - Add provider-copy, capture-to-process, loss-defer, marker-capacity and lifecycle regressions. Deterministic tests do not claim real-model semantic quality, real-session replay, or customer acceptance.
17
+
5
18
  ## 0.2.30 — 2026-09-07
6
19
 
7
20
  - Add a physical evidence projection for the Gate: the complete local inventory remains available for provenance, replay and audit, while only user-origin units and complete external observations become bindable model evidence. Assistant synthesis, retrieved memory, incomplete observations and metadata-only records remain context or unresolved ledger state.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.30
3
+ Version: 0.2.32
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.30。**
27
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版将完整证据清单与 Gate 可绑定的 physical evidence projection 分开,统一 `candidates`、`coverage`、`evidence_bindings` 协议,保留 legacy candidate-only exact/bound compatibility,对未决证据安全收口并提供 allowlisted `evidence_check` 诊断,同时保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
26
+ > **版本:0.2.32。**
27
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版在既有 UTF-8 证据留存预算和 metadata pending 消费策略上,为 Gate `unknown_unit` 提供字段路径、类型/长度/摘要和期望集合摘要等受限诊断,并在同一合法 ID 清单约束下最多三次尝试、最多两次纠正;不记录原始值、不做 ID 猜测,持续失败不推进水位且不触发清理。保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
28
28
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
29
29
 
30
30
  ## 项目定位
@@ -490,7 +490,7 @@ MIT,见 [LICENSE](LICENSE)。
490
490
  *Your memories, in files you own.*
491
491
 
492
492
 
493
- ## 通用处理与只读验收(0.2.30
493
+ ## 通用处理与只读验收(0.2.32
494
494
 
495
495
  邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
496
496
  自动摘要只能使用获准引用的原文;助手复述和旧记忆回读不能单独授权新增写入。
@@ -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.30.**
8
- > The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. This release separates the complete evidence inventory from the Gate's bindable physical evidence projection, unifies the `candidates`, `coverage`, and `evidence_bindings` contract, preserves legacy candidate-only exact/bound compatibility, keeps unresolved evidence safe, and adds allowlisted `evidence_check` diagnostics while preserving 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.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.
9
9
  > **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
10
10
 
11
11
  ## Project scope
@@ -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.30)
477
+ ## General processing and read-only inspection (0.2.32)
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.
@@ -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.30。**
8
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版将完整证据清单与 Gate 可绑定的 physical evidence projection 分开,统一 `candidates`、`coverage`、`evidence_bindings` 协议,保留 legacy candidate-only exact/bound compatibility,对未决证据安全收口并提供 allowlisted `evidence_check` 诊断,同时保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
7
+ > **版本:0.2.32。**
8
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版在既有 UTF-8 证据留存预算和 metadata pending 消费策略上,为 Gate `unknown_unit` 提供字段路径、类型/长度/摘要和期望集合摘要等受限诊断,并在同一合法 ID 清单约束下最多三次尝试、最多两次纠正;不记录原始值、不做 ID 猜测,持续失败不推进水位且不触发清理。保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
9
9
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
10
10
 
11
11
  ## 项目定位
@@ -471,7 +471,7 @@ MIT,见 [LICENSE](LICENSE)。
471
471
  *Your memories, in files you own.*
472
472
 
473
473
 
474
- ## 通用处理与只读验收(0.2.30
474
+ ## 通用处理与只读验收(0.2.32
475
475
 
476
476
  邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
477
477
  自动摘要只能使用获准引用的原文;助手复述和旧记忆回读不能单独授权新增写入。
@@ -0,0 +1,74 @@
1
+ # Tool evidence capture budget
2
+
3
+ ## Problem and ownership
4
+
5
+ Capture permissions and capture capacity are separate contracts. `metadata`
6
+ deliberately removes source bodies; `off` removes observations. Neither setting
7
+ can produce new external facts for extraction. Enabling `bounded` authorizes
8
+ bounded body retention, but does not reconstruct previously discarded data.
9
+
10
+ The previous implementation also applied independent eight-record and
11
+ 2,000-character limits in the Hermes adapter, provenance normalization and
12
+ pending-state processing. Early discovery and skill results could consume the
13
+ entire record allowance before later source results arrived. Larger plain-text
14
+ results were truncated before Core could account for their complete content.
15
+ This is an ingestion-capacity problem; changing Gate semantics cannot recover
16
+ those bytes.
17
+
18
+ ## Required implementation contract
19
+
20
+ - Keep one shared, deterministic budget implementation for Core and the copied
21
+ Hermes provider. The provider must continue to work without importing Core
22
+ from the host's Python environment.
23
+ - Bound record count, each body and aggregate body size. Publish the limits as
24
+ engineering capacity, not a guarantee of complete capture for arbitrary turns.
25
+ The implementation target is 64 source records, 32 KiB of UTF-8 text per body
26
+ and 128 KiB of total body text. Omission markers have a separate allowance of
27
+ 64 call identities and one aggregate marker. They do not consume source-record
28
+ capacity or count themselves as newly lost observations on a later normalization
29
+ pass. Marker bodies are fixed diagnostics, never retained source excerpts.
30
+ - Preserve complete, matched source results within those limits. Do not rank
31
+ business topics or tool names, infer facts from assistant text, split arbitrary
32
+ stdout into invented document records, or promote truncated prefixes to
33
+ complete observations.
34
+ - Normalize retained evidence idempotently. Cache, capture, inbox read and
35
+ planning must not each discard another part of an already bounded inventory.
36
+ - Keep explicit omission/incompleteness accounting when a limit is exceeded.
37
+ Policy-authorized but incomplete observations remain unresolved and cannot
38
+ authorize a write or successful source cleanup.
39
+ - Apply `metadata`, `off`, attachment exclusions and redaction before persistent
40
+ writes. A capacity increase must not silently change capture permission.
41
+ - Keep the existing Gate/Summarize semantic responsibility and Markdown storage
42
+ model. Larger source capacity is not a claim of successful model extraction.
43
+
44
+ ## Acceptance
45
+
46
+ Use synthetic host messages shaped like an ordinary discovery/read turn: early
47
+ tool discovery, skills and memory search followed by nine later results, with
48
+ individual bodies ranging from hundreds to tens of thousands of characters.
49
+ The old path loses all nine later results; the revised path must retain complete
50
+ results when the full input is within its declared capacity.
51
+
52
+ Verify the same evidence across adapter, pending cache, capture and inbox read;
53
+ then verify a supported later-source candidate can pass the actual processing
54
+ path in an isolated Vault. Check record/body/aggregate overflow separately,
55
+ including repeated reads, accurate loss accounting, no write from incomplete
56
+ evidence, and no cleanup of unresolved turns. Retain permission, redaction,
57
+ cross-turn isolation and standalone installation coverage.
58
+
59
+ Tests must use synthetic data and a deterministic backend. Live model semantics,
60
+ changes to a user's capture permission, installation and release are separate
61
+ operations with separately reported results.
62
+
63
+ ## Model capacity remains a separate limit
64
+
65
+ The capture limits bound retained source data, not the final prompt or model
66
+ token count. Evidence annotations, conversation context, retrieved context and
67
+ the output allowance also consume model capacity. The current `llm.context_window`
68
+ setting is not a pre-call tokenizer check. A source inventory within the capture
69
+ budget can still exceed a selected model's usable context.
70
+
71
+ Do not silently drop evidence to make that call succeed. Existing model-failure
72
+ handling must preserve the failed turn and prevent cleanup. Automatic batching
73
+ or model-specific token accounting needs its own design and is outside this
74
+ capture-budget change.
@@ -17,12 +17,20 @@ legacy boolean (false/absent -> metadata, true -> bounded). An explicit new mode
17
17
  wins over the legacy boolean. Invalid strings and non-boolean flags fail closed.
18
18
  Loading config does not rewrite it. Normal saves make the effective mode explicit.
19
19
 
20
- The inherited limits remain eight records, 2,000 characters per body, and 320 per
21
- metadata field. Oversized eligible evidence remains incomplete; its prefix is
22
- never promoted into a complete fact. Policy-excluded observations are marked
20
+ The shared capture budget allows 64 source records, 32 KiB of UTF-8 text per body,
21
+ 128 KiB of total body text, and 320 characters per metadata field. Loss markers
22
+ have a separate allowance of 64 call identities plus one aggregate marker, with
23
+ fixed diagnostic bodies. Reapplying normalization does not reduce an
24
+ already bounded inventory or count existing omissions again. Oversized eligible
25
+ evidence remains incomplete; its prefix is never promoted into a complete fact.
26
+ Policy-excluded observations are marked
23
27
  retention=metadata, have no content, and are not retried as missing evidence.
24
28
  No later relaxation recreates discarded original content.
25
29
 
30
+ These are source-retention limits, not a guarantee that every configured model
31
+ can process the resulting prompt. See [capture budget design](capture-budget-design.md)
32
+ for the ingestion boundary, model-capacity limitation and acceptance contract.
33
+
26
34
  Document/attachment bodies require include_attachments=true and bounded mode.
27
35
  HostRuntime and the standalone Hermes adapter classify structural file arguments
28
36
  using the same tested contract (path/file_path/file_id/attachment_id/file URI,
@@ -62,6 +62,18 @@ Record a separate, allowlisted evidence-check identifier for the failing
62
62
  constraint. Diagnostics must not contain raw model responses, message text,
63
63
  credentials or arbitrary exception strings.
64
64
 
65
+ An unknown unit reference must identify the exact response field, such as
66
+ `coverage[2].unit_id` or `evidence_bindings[0].claims[1].unit_id`. Persist only
67
+ the field path, value type/length/digest and expected-set count/digest. The
68
+ invalid value and legal ID list do not belong in normal logs or failed state.
69
+
70
+ A bounded retry receives the failed constraint, exact field path and the legal
71
+ IDs from the same immutable model-visible inventory used by the validator.
72
+ These IDs are a reference constraint, not a suggested semantic decision. The
73
+ model must regenerate a consistent response; the host must not substitute a
74
+ nearby ID, guess a source, or relax evidence authority. Prompt examples must not
75
+ provide a literal placeholder that looks like a usable evidence reference.
76
+
65
77
  A failed Gate cannot advance the turn watermark or create a cleanup deadline.
66
78
  A successful no-change decision can advance the watermark and start the normal
67
79
  retention period. Incomplete physical observations remain deferred even if other
@@ -97,3 +109,9 @@ That adapter/capture limitation must be reported independently of Gate success.
97
109
  - Deterministic tests establish the protocol. Live synthetic tests establish
98
110
  behavior only for those samples. A synthetic success does not identify the
99
111
  cause of an earlier real-model failure; do not claim otherwise.
112
+
113
+ A successful replay is evidence about that new run. If an earlier raw response
114
+ was not retained, its precise invalid value cannot be recovered from an
115
+ `unknown_unit` category alone. Verify recovery with controlled invalid coverage
116
+ and binding references as well as ordinary live samples, and distinguish those
117
+ results from reproduction of the original incident.
@@ -1,4 +1,4 @@
1
- # General processing reliability contract — 0.2.30
1
+ # General processing reliability contract — 0.2.32
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
@@ -80,12 +80,15 @@ labels them NO_CHANGE. Incomplete turns retain their source instead of being
80
80
  cleaned after the usual grace period. Scope-filtered retries may revisit them;
81
81
  no endless automatic model retry or extra external tool call is introduced.
82
82
 
83
- Tool evidence is bounded to eight records with at most 2,000 content characters
84
- per captured result record and 320-character metadata fields, with redaction at
85
- Core capture. Large unambiguous top-level record collections retain complete
86
- records and enclosing context within that budget. Per-record provenance takes
87
- precedence over common source metadata. An overflow slot reports omitted
88
- records. Arbitrary large prose is not split into falsely complete facts;
83
+ Tool evidence uses one shared budget: 64 source records, at most 32 KiB of UTF-8
84
+ body text per record and 128 KiB of total body text, plus 320-character metadata
85
+ fields, with redaction at Core capture. Cache and inbox normalization are
86
+ idempotent under this budget. Large unambiguous top-level record collections
87
+ retain complete records and enclosing context within that budget. Per-record provenance takes
88
+ precedence over common source metadata. Separate loss markers report omitted
89
+ records, with at most 64 call identities plus one aggregate marker. These markers
90
+ have fixed diagnostic bodies and cannot supply external facts.
91
+ Arbitrary large prose is not split into falsely complete facts;
89
92
  unsupported/incomplete content needs a supported complete source excerpt or a
90
93
  later source input. Switching from `metadata` to `bounded` does not reconstruct
91
94
  body content that was never captured; bounded retention can still produce an
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.30"
7
+ version = "0.2.32"
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.30"
3
+ __version__ = "0.2.32"
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
@@ -242,7 +242,7 @@ def validate_bindings(value: Any, units: Iterable[EvidenceUnit],
242
242
  evidence_check="binding_shape")
243
243
  result: dict[str, list[dict[str, Any]]] = {}
244
244
  allowed_roles = {"assertion", "source_excerpt", "user_confirmation"}
245
- for row in value:
245
+ for binding_index, row in enumerate(value):
246
246
  if not isinstance(row, dict) or set(row) != {"candidate_id", "claims"}:
247
247
  raise ModelOutputError("invalid evidence binding", validation_detail="invalid_evidence",
248
248
  evidence_check="binding_shape")
@@ -255,7 +255,7 @@ def validate_bindings(value: Any, units: Iterable[EvidenceUnit],
255
255
  raise ModelOutputError("empty evidence claims", validation_detail="invalid_evidence",
256
256
  evidence_check="binding_shape")
257
257
  checked = []
258
- for claim in claims:
258
+ for claim_index, claim in enumerate(claims):
259
259
  if not isinstance(claim, dict) or set(claim) not in (
260
260
  {"unit_id", "start", "end", "quote", "role"}, {"unit_id", "quote", "role"}):
261
261
  raise ModelOutputError("invalid evidence claim", validation_detail="invalid_evidence",
@@ -263,8 +263,13 @@ def validate_bindings(value: Any, units: Iterable[EvidenceUnit],
263
263
  claim = dict(claim)
264
264
  uid = claim["unit_id"]
265
265
  if not isinstance(uid, str) or uid not in by_unit:
266
- raise ModelOutputError("unknown evidence unit", validation_detail="invalid_evidence",
267
- evidence_check="unknown_unit")
266
+ error = ModelOutputError("unknown evidence unit", validation_detail="invalid_evidence",
267
+ evidence_check="unknown_unit")
268
+ raise error.with_evidence_context(
269
+ path=f"evidence_bindings[{binding_index}].claims[{claim_index}].unit_id",
270
+ actual=uid,
271
+ expected_ids=tuple(by_unit),
272
+ )
268
273
  unit = by_unit[uid]
269
274
  quote = claim["quote"]
270
275
  if "start" not in claim:
@@ -356,20 +361,27 @@ _DEFERRED_COVERAGE_REASONS = frozenset({
356
361
 
357
362
  def parse_coverage(value: Any, units: Iterable[EvidenceUnit], candidates: Iterable[Mapping[str, Any]], *, require_complete: bool = True) -> dict[str, dict[str, Any]]:
358
363
  """Validate accounting without trusting the model's evidence identities."""
364
+ units = tuple(units)
365
+ expected_units = tuple(dict.fromkeys(u.unit_id for u in units))
359
366
  units = {u.unit_id: u for u in units}
360
367
  candidates = {c["candidate_id"]: c for c in candidates}
361
368
  if not isinstance(value, list):
362
369
  raise ModelOutputError("coverage must be a list", validation_detail="invalid_evidence",
363
370
  evidence_check="coverage_shape")
364
371
  result = {}
365
- for row in value:
372
+ for row_index, row in enumerate(value):
366
373
  if not isinstance(row, dict) or set(row) - {"unit_id", "decision", "candidate_ids", "reason"}:
367
374
  raise ModelOutputError("invalid coverage row", validation_detail="invalid_evidence",
368
375
  evidence_check="coverage_shape")
369
376
  uid = row.get("unit_id")
370
377
  if not isinstance(uid, str) or uid not in units:
371
- raise ModelOutputError("invalid coverage unit", validation_detail="invalid_evidence",
372
- evidence_check="unknown_unit")
378
+ error = ModelOutputError("invalid coverage unit", validation_detail="invalid_evidence",
379
+ evidence_check="unknown_unit")
380
+ raise error.with_evidence_context(
381
+ path=f"coverage[{row_index}].unit_id",
382
+ actual=uid,
383
+ expected_ids=expected_units,
384
+ )
373
385
  if uid in result:
374
386
  raise ModelOutputError("duplicate coverage unit", validation_detail="invalid_evidence",
375
387
  evidence_check="duplicate_coverage")
@@ -466,9 +478,10 @@ def evidence_prompt(units: Iterable[EvidenceUnit]) -> str:
466
478
  "candidates, coverage, and evidence_bindings. "
467
479
  "Coverage must contain exactly one row for EVERY supplied evidence unit. "
468
480
  "A response with coverage omitted or with coverage=[] is complete only when no units are supplied. "
469
- "Each row is "
470
- '{"unit_id":"supplied id","decision":"CANDIDATE","candidate_ids":["id"]} or '
471
- '{"unit_id":"supplied id","decision":"NO_CHANGE or DEFERRED","reason":"reason"}. '
481
+ "For each row, copy unit_id character-for-character from the supplied evidence list. "
482
+ "Use decision=CANDIDATE with candidate_ids, or decision=NO_CHANGE/DEFERRED with reason. "
483
+ "The words in this schema description are labels only; never return a placeholder, event key, "
484
+ "call ID, or digest as unit_id. "
472
485
  'Allowed reasons: ' + ', '.join(sorted(COVERAGE_REASONS)) + '. '
473
486
  'Use NO_CHANGE only with reasons: ' + ', '.join(sorted(_NO_CHANGE_COVERAGE_REASONS)) + '. '
474
487
  'Use DEFERRED only with reasons: ' + ', '.join(sorted(_DEFERRED_COVERAGE_REASONS)) + '. '
@@ -489,9 +502,12 @@ def evidence_prompt(units: Iterable[EvidenceUnit]) -> str:
489
502
 
490
503
 
491
504
  SEMANTIC_BINDING_INSTRUCTIONS = """
492
- For every worth=true candidate, also return top-level evidence_bindings:
493
- [{"candidate_id":"existing candidate id","claims":[{"unit_id":"supplied id",
494
- "start":0,"end":10,"quote":"exact substring","role":"assertion"}]}].
505
+ For every worth=true candidate, also return top-level evidence_bindings. Each
506
+ binding must name a candidate_id copied from the candidates list and claims
507
+ whose unit_id is copied character-for-character from the supplied evidence
508
+ list. Do not return schema labels, placeholders, event keys, call IDs or
509
+ digests as unit_id values. Each claim contains an exact quote and role, and
510
+ may include start/end offsets.
495
511
  Offsets are relative to the supplied unit text. Roles: assertion (a current
496
512
  statement of fact or change), source_excerpt (actual quoted material, not a
497
513
  demonstration), user_confirmation (explicit adoption of a uniquely identified