memleaf 0.2.95__tar.gz → 1.0.0__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 (189) hide show
  1. {memleaf-0.2.95 → memleaf-1.0.0}/CHANGELOG.md +47 -0
  2. {memleaf-0.2.95 → memleaf-1.0.0}/MANIFEST.in +2 -3
  3. {memleaf-0.2.95/src/memleaf.egg-info → memleaf-1.0.0}/PKG-INFO +3 -2
  4. {memleaf-0.2.95 → memleaf-1.0.0}/README.en.md +1 -1
  5. {memleaf-0.2.95 → memleaf-1.0.0}/README.md +1 -1
  6. {memleaf-0.2.95 → memleaf-1.0.0}/docs/acceptance-evidence-guide.md +12 -18
  7. {memleaf-0.2.95 → memleaf-1.0.0}/docs/host-acceptance-runbook.md +4 -7
  8. {memleaf-0.2.95 → memleaf-1.0.0}/docs/installed-artifact-verification.md +5 -2
  9. memleaf-1.0.0/docs/processing-quality-acceptance.md +33 -0
  10. {memleaf-0.2.95 → memleaf-1.0.0}/docs/query-performance.md +7 -8
  11. memleaf-1.0.0/docs/stability.md +56 -0
  12. {memleaf-0.2.95 → memleaf-1.0.0}/pyproject.toml +2 -1
  13. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/__init__.py +1 -1
  14. memleaf-1.0.0/src/memleaf/body_preservation.py +112 -0
  15. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  16. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_execution.py +5 -2
  17. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_semantics.py +44 -9
  18. {memleaf-0.2.95 → memleaf-1.0.0/src/memleaf.egg-info}/PKG-INFO +3 -2
  19. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf.egg-info/SOURCES.txt +2 -3
  20. memleaf-0.2.95/docs/processing-quality-acceptance.md +0 -189
  21. memleaf-0.2.95/examples/incremental_acceptance.json +0 -747
  22. memleaf-0.2.95/examples/query_benchmark.py +0 -133
  23. memleaf-0.2.95/src/memleaf/acceptance.py +0 -477
  24. {memleaf-0.2.95 → memleaf-1.0.0}/LICENSE +0 -0
  25. {memleaf-0.2.95 → memleaf-1.0.0}/docs/automatic-processing-route.md +0 -0
  26. {memleaf-0.2.95 → memleaf-1.0.0}/docs/body-compaction.md +0 -0
  27. {memleaf-0.2.95 → memleaf-1.0.0}/docs/candidate-reconciliation.md +0 -0
  28. {memleaf-0.2.95 → memleaf-1.0.0}/docs/capture-budget-design.md +0 -0
  29. {memleaf-0.2.95 → memleaf-1.0.0}/docs/config-migrations.md +0 -0
  30. {memleaf-0.2.95 → memleaf-1.0.0}/docs/core-refactor.md +0 -0
  31. {memleaf-0.2.95 → memleaf-1.0.0}/docs/development-closeout.md +0 -0
  32. {memleaf-0.2.95 → memleaf-1.0.0}/docs/evidence-retention.md +0 -0
  33. {memleaf-0.2.95 → memleaf-1.0.0}/docs/explicit-memory-update.md +0 -0
  34. {memleaf-0.2.95 → memleaf-1.0.0}/docs/extraction-latency.md +0 -0
  35. {memleaf-0.2.95 → memleaf-1.0.0}/docs/failed-run-recovery.md +0 -0
  36. {memleaf-0.2.95 → memleaf-1.0.0}/docs/gate-evidence-boundary.md +0 -0
  37. {memleaf-0.2.95 → memleaf-1.0.0}/docs/general-processing.md +0 -0
  38. {memleaf-0.2.95 → memleaf-1.0.0}/docs/hermes-mcp-runtime.md +0 -0
  39. {memleaf-0.2.95 → memleaf-1.0.0}/docs/hermes-provider-compatibility.md +0 -0
  40. {memleaf-0.2.95 → memleaf-1.0.0}/docs/hermes-source-comparison-context.md +0 -0
  41. {memleaf-0.2.95 → memleaf-1.0.0}/docs/immutable-release-assets.md +0 -0
  42. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-commit-contract.md +0 -0
  43. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-execution.md +0 -0
  44. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-native-comparison.md +0 -0
  45. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-partial-recovery.md +0 -0
  46. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-planner-preview.md +0 -0
  47. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-runtime-contract.md +0 -0
  48. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-scope-registration.md +0 -0
  49. {memleaf-0.2.95 → memleaf-1.0.0}/docs/incremental-selected-retention.md +0 -0
  50. {memleaf-0.2.95 → memleaf-1.0.0}/docs/memory-lifecycle-tools.md +0 -0
  51. {memleaf-0.2.95 → memleaf-1.0.0}/docs/memory-state-contract.md +0 -0
  52. {memleaf-0.2.95 → memleaf-1.0.0}/docs/migration-preflight.md +0 -0
  53. {memleaf-0.2.95 → memleaf-1.0.0}/docs/public-query-integrity.md +0 -0
  54. {memleaf-0.2.95 → memleaf-1.0.0}/docs/remember-incremental-route.md +0 -0
  55. {memleaf-0.2.95 → memleaf-1.0.0}/docs/runtime-state-retention.md +0 -0
  56. {memleaf-0.2.95 → memleaf-1.0.0}/docs/semantic-extraction-protocol.md +0 -0
  57. {memleaf-0.2.95 → memleaf-1.0.0}/docs/source-work-contract.md +0 -0
  58. {memleaf-0.2.95 → memleaf-1.0.0}/docs/v0.2.26-processing-status.md +0 -0
  59. {memleaf-0.2.95 → memleaf-1.0.0}/examples/README.md +0 -0
  60. {memleaf-0.2.95 → memleaf-1.0.0}/examples/basic_usage.py +0 -0
  61. {memleaf-0.2.95 → memleaf-1.0.0}/examples/mcp_stdio.ndjson +0 -0
  62. {memleaf-0.2.95 → memleaf-1.0.0}/install.ps1 +0 -0
  63. {memleaf-0.2.95 → memleaf-1.0.0}/install.sh +0 -0
  64. {memleaf-0.2.95 → memleaf-1.0.0}/setup.cfg +0 -0
  65. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/__main__.py +0 -0
  66. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/adapters/__init__.py +0 -0
  67. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/adapters/antigravity.py +0 -0
  68. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/adapters/base.py +0 -0
  69. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/adapters/codex.py +0 -0
  70. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/adapters/hermes.py +0 -0
  71. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/admission.py +0 -0
  72. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/batch_review.py +0 -0
  73. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/budget.py +0 -0
  74. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/capture.py +0 -0
  75. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/cli.py +0 -0
  76. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/compaction.py +0 -0
  77. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/config.py +0 -0
  78. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/create_coordinator.py +0 -0
  79. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/credentials.py +0 -0
  80. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/evidence_budget.py +0 -0
  81. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/evidence_policy.py +0 -0
  82. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/evidence_structure.py +0 -0
  83. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/evidence_syntax.py +0 -0
  84. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/explicit_text_source.py +0 -0
  85. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/extraction_budget.py +0 -0
  86. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/extraction_capability.py +0 -0
  87. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/extraction_work_state.py +0 -0
  88. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/frontmatter.py +0 -0
  89. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/README.md +0 -0
  90. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/__init__.py +0 -0
  91. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
  92. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/_provider.py +0 -0
  93. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/_shared.py +0 -0
  94. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
  95. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
  96. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/hermes_runtime.py +0 -0
  97. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/historical_context.py +0 -0
  98. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/host_events.py +0 -0
  99. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/host_retention.py +0 -0
  100. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/host_runtime.py +0 -0
  101. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/host_turn_identity.py +0 -0
  102. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/inbox.py +0 -0
  103. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_commit.py +0 -0
  104. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_dates.py +0 -0
  105. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_explicit.py +0 -0
  106. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_failed_recovery.py +0 -0
  107. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_journal.py +0 -0
  108. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_merge.py +0 -0
  109. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_native.py +0 -0
  110. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_partial.py +0 -0
  111. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_preview.py +0 -0
  112. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_prompts.py +0 -0
  113. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_protocol.py +0 -0
  114. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_recovery.py +0 -0
  115. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_run_state.py +0 -0
  116. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_runtime.py +0 -0
  117. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_scopes.py +0 -0
  118. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/incremental_selection.py +0 -0
  119. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/index.py +0 -0
  120. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/inspection.py +0 -0
  121. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/installer.py +0 -0
  122. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/legacy_update_journal.py +0 -0
  123. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/lifecycle_maintenance.py +0 -0
  124. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/__init__.py +0 -0
  125. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/base.py +0 -0
  126. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/claude_compatible.py +0 -0
  127. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/gemini.py +0 -0
  128. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/openai_compatible.py +0 -0
  129. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/router.py +0 -0
  130. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/llm/thinking.py +0 -0
  131. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/locking.py +0 -0
  132. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/mcp_server.py +0 -0
  133. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/memory_commit.py +0 -0
  134. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/memory_planner.py +0 -0
  135. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/memory_retraction.py +0 -0
  136. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/memory_update.py +0 -0
  137. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/memory_writer.py +0 -0
  138. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/migration.py +0 -0
  139. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/model_capabilities.py +0 -0
  140. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/model_discovery.py +0 -0
  141. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/model_execution.py +0 -0
  142. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/models.py +0 -0
  143. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/native_index.py +0 -0
  144. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/native_registration.py +0 -0
  145. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/parallel_model.py +0 -0
  146. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/planning_candidates.py +0 -0
  147. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/planning_context.py +0 -0
  148. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/process_common.py +0 -0
  149. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/process_jobs.py +0 -0
  150. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/process_journal.py +0 -0
  151. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/process_owner.py +0 -0
  152. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/processing.py +0 -0
  153. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/processing_route.py +0 -0
  154. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/prompts.py +0 -0
  155. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/provenance.py +0 -0
  156. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/provider_compatibility.py +0 -0
  157. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/query_clock.py +0 -0
  158. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/query_progress.py +0 -0
  159. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/query_scan.py +0 -0
  160. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/receipt_codec.py +0 -0
  161. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/recording_policy.py +0 -0
  162. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/redaction.py +0 -0
  163. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/remember_route.py +0 -0
  164. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/retention.py +0 -0
  165. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/retrieval.py +0 -0
  166. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/retrieval_gate.py +0 -0
  167. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/retrieval_lifecycle.py +0 -0
  168. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/runtime_retention.py +0 -0
  169. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/scope_maintenance.py +0 -0
  170. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/scope_state.py +0 -0
  171. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/semantic_maintenance.py +0 -0
  172. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/semantic_protocol.py +0 -0
  173. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/service.py +0 -0
  174. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/single_pass_memory_planner.py +0 -0
  175. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/single_pass_plan.py +0 -0
  176. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/source_policy.py +0 -0
  177. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/state_layout.py +0 -0
  178. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/subprocess_flags.py +0 -0
  179. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/summary_batch.py +0 -0
  180. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/target_reconciliation.py +0 -0
  181. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/turn_audit.py +0 -0
  182. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/turn_plan.py +0 -0
  183. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/update_coordinator.py +0 -0
  184. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/update_review.py +0 -0
  185. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/validation.py +0 -0
  186. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf/vault.py +0 -0
  187. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf.egg-info/dependency_links.txt +0 -0
  188. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf.egg-info/entry_points.txt +0 -0
  189. {memleaf-0.2.95 → memleaf-1.0.0}/src/memleaf.egg-info/top_level.txt +0 -0
@@ -5,6 +5,53 @@ All notable changes to memleaf are documented here.
5
5
  ## Unreleased
6
6
 
7
7
 
8
+ ## 1.0.0 — 2026-10-06
9
+
10
+ ### Stable public interfaces and production-only distributions
11
+
12
+ - Define the 1.x compatibility scope for documented Python exports, CLI entry
13
+ points, MCP tools, configuration and persisted memory formats. Preserve the
14
+ 0.2.96 production runtime, prompts, existing storage/recovery contracts and
15
+ five-request budget; no model request or Vault migration is added.
16
+ - Move the isolated acceptance harness, synthetic acceptance suite and query
17
+ benchmark into the ignored local development directory. Remove the former
18
+ `python -m memleaf.acceptance` developer entry point from public artifacts;
19
+ normal Python/CLI/MCP interfaces and usage/discovery examples remain.
20
+ - Remove obsolete packaged-tool instructions and prevent developer/private files
21
+ from re-entering wheel or source distributions through the release CI. Keep
22
+ the existing five-cell native installation matrix and publish verified bytes.
23
+ - The 0.2.96 installed Hermes acceptance remains evidence for the unchanged
24
+ production runtime: four successful turns, eight Memleaf requests and 42,518
25
+ observed tokens. The 1.0.0 release verifies interface compatibility, isolated
26
+ installed regressions and clean artifact payloads separately; a stable version
27
+ is not a guarantee of perfect future model responses.
28
+
29
+
30
+ ## 0.2.96 — 2026-10-06
31
+
32
+ ### Preserve existing information when replacing memory bodies
33
+
34
+ - Expose omitted old prose to semantic review for UPDATE and every MERGE
35
+ member. Require dispositions bound to the final body or new user evidence;
36
+ boolean body approval alone cannot erase existing information. Unconfirmed
37
+ replacements remain DEFERRED with the previous memory preserved.
38
+ - Allow the existing review to repair the full body while retaining candidate
39
+ identity, evidence and other fields. Check the final replacement, accept valid
40
+ redundant coverage, and isolate invalid optional maintenance corrections from
41
+ independently valid decisions. Protect manual partial repairs as well.
42
+ - Reuse the existing review request for ongoing actions; add a second review
43
+ only where a previously unreviewed fact update could omit existing prose.
44
+ Ordinary fact additions and repeated no-change turns remain single-request.
45
+ No third review, automatic retry or request-budget increase is added.
46
+ - Validation: 122 affected checks and isolated frozen-candidate replays passed.
47
+ The final three live Hermes turns preserved unchanged requirements, avoided
48
+ duplicate writes and applied only an explicitly withdrawn requirement:
49
+ five Memleaf requests, 30,821 observed tokens, no issues or unresolved items.
50
+ Intermediate development failures were preserved separately; historical raw
51
+ review responses were not reconstructed. Equivalence still relies on semantic
52
+ review, while Core verifies complete coverage and source/quote bindings.
53
+
54
+
8
55
  ## 0.2.95 — 2026-10-05
9
56
 
10
57
  ### Preserve turn identity around internal Hermes continuations
@@ -5,9 +5,8 @@ include install.ps1
5
5
  include README.md
6
6
  include README.en.md
7
7
  recursive-include examples *.md *.ndjson *.py
8
- recursive-exclude examples *_acceptance.py
8
+ recursive-exclude examples *_acceptance.py query_benchmark.py incremental_acceptance.json
9
9
  prune tests
10
+ prune tests_public
10
11
 
11
12
  include docs/*.md
12
-
13
- include examples/incremental_acceptance.json
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.95
3
+ Version: 1.0.0
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
7
7
  Keywords: ai-agents,local-first,markdown,memory
8
+ Classifier: Development Status :: 5 - Production/Stable
8
9
  Classifier: Intended Audience :: Developers
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -23,7 +24,7 @@ Dynamic: license-file
23
24
 
24
25
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
25
26
 
26
- > **版本:0.2.95。**
27
+ > **稳定版:1.0.0。** 公开接口进入稳定维护,版本与兼容范围见[稳定性约定](docs/stability.md)。
27
28
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
28
29
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
29
30
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
@@ -4,7 +4,7 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.2.95.**
7
+ > **Stable release: 1.0.0.** Public interfaces enter stable maintenance; see the [stability policy](docs/stability.md) for versioning and compatibility scope.
8
8
  > Automatic extraction and review share one future-value standard: the model weighs likely reuse, information gain, direct usability when read again, and the cost of forgetting. It keeps only the smallest core that can materially help future understanding, decisions, or actions; information without clear value is not extracted, and no business-specific exclusion rule is hard-coded.
9
9
  > Extraction compares existing identifiers, objectives, progress, responsibility and deadlines first. Filling an unknown also maintains the original item; a separate task does not replace updating shared project facts. Confirmed duplicates can be merged in the existing processing requests with their sources and history preserved. Independent project and task lifecycles remain separate; Core does not merge matching titles.
10
10
  > New requests accept equivalent field representations. Unknown executors remain `null`; new or changed responsibility and deadlines require actual source evidence. Dependencies belong in the body; `waiting_on` is no longer extracted. Proposed actions and related fields undergo bounded semantic verification within a shared five-request limit per work item, stopping on success; failed verification blocks writes. Source time resolves relative dates without inventing factual dates.
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **版本:0.2.95。**
7
+ > **稳定版:1.0.0。** 公开接口进入稳定维护,版本与兼容范围见[稳定性约定](docs/stability.md)。
8
8
  > 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
9
9
  > 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
10
10
  > 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
@@ -24,7 +24,7 @@ the suite count or from module-name references.
24
24
 
25
25
  ## Current route semantics
26
26
 
27
- The v0.2.67 candidate has one processing engine: incremental. New Vaults default
27
+ The supported runtime has one processing engine: incremental. New Vaults default
28
28
  both `process.automatic_pipeline` and `process.remember_pipeline` to
29
29
  `incremental`. A retained `legacy` value from an older Vault is not executable;
30
30
  migration preflight reports it as `legacy_pipeline_configuration`, and runtime
@@ -38,23 +38,17 @@ preflight result.
38
38
 
39
39
  ## Preparation with no paid calls
40
40
 
41
- Use the existing entry point on a known candidate installation:
42
-
43
- ```sh
44
- python -m memleaf.acceptance --suite examples/incremental_acceptance.json --repeat 5
45
- ```
46
-
47
- This is plan-only: it validates the suite and reports hashes and request bounds;
48
- it does not load a backend configuration or create a Vault. `process --dry-run`
49
- is different and may call the configured model. Do not use it as a free preview.
50
-
51
- The shipped suite is public synthetic regression material. Take case count, turn
52
- count, repetitions, normalized suite hash, prompt hash and request bounds from
53
- this command on the selected candidate; do not inherit numbers from an older
54
- report. Also record the original suite file SHA-256: it is not the normalized
55
- semantic suite hash. The reported bound is not a spending authorization. A private
56
- held-out suite requires its own provenance, permission and count. Merely adding
57
- a label `holdout` does not prove the examples were unseen in tuning.
41
+ Acceptance runners and synthetic fixtures are local development tools, not
42
+ entry points shipped with an installed package. Use a controlled development
43
+ checkout to plan the approved suite without constructing a model, loading
44
+ credentials or opening a production Vault. Record the suite file and normalized
45
+ hashes, prompt hash, repetitions and request bound from that exact checkout.
46
+
47
+ `process --dry-run` is a product processing preview and may call the configured
48
+ model. It is not a free substitute for local acceptance planning. A request bound
49
+ is not a spending authorization; a private held-out suite also needs provenance
50
+ and explicit data/route permission. A `holdout` label alone proves nothing about
51
+ whether those examples were used in development.
58
52
 
59
53
  Do not execute until the actual route/model, local credential reference, suite
60
54
  hash, repeats and total allowance are approved. Keep credentials local. Do not
@@ -46,14 +46,11 @@ Do not return API keys, tokens, authentication headers, credential-bearing URLs,
46
46
  full configuration, chat bodies, native-memory text or backups. Keep raw paths
47
47
  and necessary private fingerprints in local evidence rather than public CI.
48
48
 
49
- Prepare the existing public suite with the exact candidate Python:
49
+ Acceptance planning uses the exact candidate Python, locally retained tooling and approved
50
+ fixtures. Neither the runner nor its suite is included in an installed package;
51
+ do not construct a production runtime to replace a missing development tool.
50
52
 
51
- ```sh
52
- python -m memleaf.acceptance --suite examples/incremental_acceptance.json --repeat 5
53
- ```
54
-
55
- The suite path must point to the selected checkout; an installed wheel alone need
56
- not place `examples/` in the current directory. Planning accepts no backend,
53
+ The suite path must point to the controlled local development checkout. Planning accepts no backend,
57
54
  output or execution flags. Save stdout by the caller into the evidence directory,
58
55
  not with the tool's `--output` option, which belongs to execution. Record both
59
56
  file SHA-256 and the normalized suite digest in the result. This planning command
@@ -11,12 +11,15 @@ The build job:
11
11
 
12
12
  1. compiles `src/` with Python's standard-library compiler;
13
13
  2. builds one wheel and one source distribution;
14
- 3. uploads only those two distributions as the workflow artifact.
14
+ 3. rejects development harnesses, tests, private runtime directories and local
15
+ environment files in either artifact;
16
+ 4. uploads only those two distributions as the workflow artifact.
15
17
 
16
18
  The `verify-artifacts` job downloads those exact bytes and installs each artifact
17
19
  in its own fresh virtual environment. It imports `memleaf` and checks the
18
20
  reported package version. It does not read a source-tree checkout, run a model,
19
- access a production Vault or create a release.
21
+ access a production Vault or create a release. The former developer-only
22
+ `memleaf.acceptance` module must not be importable from either installation.
20
23
 
21
24
  The matrix is:
22
25
 
@@ -0,0 +1,33 @@
1
+ # Processing quality and release checks
2
+
3
+ Memleaf uses the incremental capture, planner, compiler, review and commit path.
4
+ Extraction, review and eligible maintenance share each work item's durable
5
+ five-request budget. A successful result stops further extraction requests;
6
+ missing support remains unknown or unresolved rather than being invented.
7
+
8
+ ## Separate kinds of evidence
9
+
10
+ - Deterministic checks verify source binding, identity, permissions, state,
11
+ recovery and request accounting without external model calls.
12
+ - Source-based review of actual retained memories verifies whether useful facts
13
+ were kept, unchanged requirements survived updates and independent matters
14
+ stayed independent. A JSON or schema pass alone is insufficient.
15
+ - Real-host acceptance verifies the loaded Core/Provider, actual session model,
16
+ capture and background processing through the installed host.
17
+ - Artifact CI verifies source syntax, clean distribution payloads and native
18
+ wheel/sdist installation. It does not establish model quality.
19
+
20
+ Development acceptance runners, synthetic fixtures, benchmarks and private
21
+ reports are retained locally and are not shipped as package modules, examples
22
+ or user-facing diagnostic tools. The normal runtime APIs and configuration are
23
+ used for supported product operations.
24
+
25
+ Real model acceptance records the actual route, all attempted runs, request
26
+ counts and obtainable usage metrics. Scripted replay is reported separately.
27
+ Unknown usage remains unknown; failed attempts are not relabelled as passing
28
+ checks. Normal `process --dry-run` may call the configured model and must not be
29
+ used as a free development preview.
30
+
31
+ See the [evidence guide](acceptance-evidence-guide.md),
32
+ [installed artifact checks](installed-artifact-verification.md) and
33
+ [stability policy](stability.md).
@@ -39,12 +39,11 @@ snapshot nor claimed to be eliminated.
39
39
 
40
40
  ## Reproduce an isolated local measurement
41
41
 
42
- ```sh
43
- # Source checkout; for an installed wheel omit PYTHONPATH.
44
- PYTHONPATH=src python examples/query_benchmark.py --sizes 100 1000 10000 --repeat 5
45
- ```
42
+ The isolated benchmark is retained only in the local development directory.
43
+ It is not distributed as an example or installed package tool. A measurement
44
+ must record the exact source or installed artifact being imported.
46
45
 
47
- The example accepts sizes/repetitions only, not a production Vault, model config
46
+ The local runner accepts sizes/repetitions only, not a production Vault, model config
48
47
  or credentials. Each size uses a fresh TemporaryDirectory, 1..10,000 synthetic
49
48
  independent Todo heads, and a fixed source clock. Fixture creation is outside the
50
49
  timed section and does not rebuild an index for each inserted file. One warmup is
@@ -86,9 +85,9 @@ unchanged recheck parsing fell from N to zero. No business records were omitted
86
85
  to improve latency. At 10,000 heads the fixture contains 7,550,000 bytes; it is not
87
86
  a sample of private user conversations or an actual production Vault.
88
87
 
89
- The harness is included in sdist through the existing examples manifest. Running
90
- it against an installed wheel must use that wheel's imports, not an editable
91
- checkout. The measurements do not cover provider latency, tokens, recall/semantic
88
+ The harness is excluded from both wheel and sdist. Local runs against an
89
+ installed wheel must use that wheel's imports, not an editable checkout.
90
+ The measurements do not cover provider latency, tokens, recall/semantic
92
91
  quality, native Hermes installation or Windows/macOS performance. Those remain
93
92
  separate G5 acceptance gates; successful timings never authorize switching.
94
93
 
@@ -0,0 +1,56 @@
1
+ # Memleaf 1.x stability policy
2
+
3
+ Memleaf 1.0.0 begins stable maintenance of its documented public interfaces.
4
+ Version numbers follow [Semantic Versioning](https://semver.org/): compatible
5
+ fixes use a patch release, compatible additions use a minor release, and
6
+ incompatible public API changes require a new major version. Stable maintenance
7
+ does not imply that every future conversation or model response will be correct.
8
+
9
+ ## Public interfaces
10
+
11
+ The compatibility scope consists of:
12
+
13
+ - The names exported by `memleaf.__all__`, their documented constructors and
14
+ functions, and the documented public methods of `Memleaf`, `Core`,
15
+ `MemoryService`, `Memory` and `Vault`.
16
+ - The `memleaf`, `memleaf-mcp` and `memleaf-mcpw` entry points, documented CLI
17
+ commands and options, and their documented JSON fields and error codes.
18
+ - The published MCP tool names, accepted parameters and documented result
19
+ fields. Clients should tolerate additional optional fields.
20
+ - Documented configuration keys and the Markdown/frontmatter memory format.
21
+ Existing committed memories and persistent recovery state must retain their
22
+ identity, provenance and interpretation across compatible upgrades. Any
23
+ required migration must be documented before it is applied.
24
+
25
+ Module internals, names beginning with `_`, internal planner/reviewer protocols,
26
+ development harnesses and exact natural-language model wording are outside this
27
+ scope. An internal protocol change may fence pending model responses and require
28
+ an explicit recovery action; it must not silently reinterpret saved work, erase
29
+ unresolved outcomes or reset consumed request budgets.
30
+
31
+ ## Upgrade to 1.0.0
32
+
33
+ The 1.0.0 runtime preserves the 0.2.96 production interfaces, configuration,
34
+ storage formats, prompts and five-request limit. It requires no new model calls
35
+ or Vault migration from 0.2.96. Earlier unsupported pipeline settings and pending
36
+ work retain their existing migration/recovery requirements; the version number
37
+ does not mark them complete.
38
+
39
+ Update Core and the copied Hermes Provider together through `memleaf install`,
40
+ then restart Hermes to load the installed files. The existing build/protocol
41
+ compatibility checks still apply. Codex connections must reconnect or restart
42
+ their MCP process after an upgrade.
43
+
44
+ The former `memleaf.acceptance` module, synthetic acceptance suite and query
45
+ benchmark are development tools. They are removed from the public package and
46
+ repository in 1.0.0 and retained only in the local development directory. Normal
47
+ usage examples, MCP discovery examples and artifact verification in CI remain.
48
+
49
+ ## Release evidence
50
+
51
+ Each release builds one wheel and source distribution, verifies their payloads
52
+ and installs those same artifacts on the existing native CI matrix before
53
+ publishing. Local regressions and real-host/model acceptance are separate gates:
54
+ green installation checks do not prove model semantics, and a successful host
55
+ conversation does not replace package verification. Development tests and private
56
+ acceptance reports are kept outside the public source and distributions.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.95"
7
+ version = "1.0.0"
8
8
  description = "A local-first Markdown memory core for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -13,6 +13,7 @@ license-files = ["LICENSE"]
13
13
  authors = [{name = "memleaf contributors"}]
14
14
  keywords = ["ai-agents", "local-first", "markdown", "memory"]
15
15
  classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
16
17
  "Intended Audience :: Developers",
17
18
  "Programming Language :: Python :: 3",
18
19
  "Programming Language :: Python :: 3 :: Only",
@@ -1,6 +1,6 @@
1
1
  """Local-first Markdown memory core for AI agents."""
2
2
 
3
- __version__ = "0.2.95"
3
+ __version__ = "1.0.0"
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
@@ -0,0 +1,112 @@
1
+ """Account for omitted old prose without guessing which facts remain valid.
2
+
3
+ Text fragments are comparison aids, not a parser for facts. The existing
4
+ semantic review judges equivalence or an authorized change; Core checks that
5
+ every omission has a real, source-bound disposition for the final candidate.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import re
10
+
11
+
12
+ def _compact(text):
13
+ return re.sub(r"\s+", "", text)
14
+
15
+
16
+ def omissions(row, original):
17
+ if not isinstance(row, dict):
18
+ return []
19
+ action = row.get("action")
20
+ action = action.strip().upper() if isinstance(action, str) else ""
21
+ if action not in {"UPDATE", "MERGE"}:
22
+ return []
23
+ patch = row.get("patch")
24
+ if not isinstance(patch, dict):
25
+ return []
26
+ # Retraction erases the stored body even when the model omits patch.body.
27
+ body = "" if patch.get("validity") == "retracted" else patch.get("body")
28
+ if not isinstance(body, str):
29
+ return []
30
+ refs = [row.get("target")]
31
+ if action == "MERGE" and isinstance(row.get("duplicates"), list):
32
+ refs.extend(row["duplicates"])
33
+ refs = [ref.strip() for ref in refs if isinstance(ref, str)]
34
+ new = _compact(body)
35
+ missing = []
36
+ seen = set()
37
+ for memory in original.get("memories", []):
38
+ if memory.get("ref") not in refs or memory.get("validity", "valid") != "valid":
39
+ continue
40
+ old = memory.get("body", "")
41
+ if not isinstance(old, str):
42
+ continue
43
+ # Language-neutral punctuation/line boundaries. No names, keywords or
44
+ # expected facts influence the comparison. Short fragments count too.
45
+ for text in re.split(r"[。!?!?;;\n,,]+|(?<=\.)\s+", old):
46
+ text = text.strip()
47
+ key = (memory["ref"], text)
48
+ if text and _compact(text) not in new and key not in seen:
49
+ missing.append({"target": key[0], "old": text})
50
+ seen.add(key)
51
+ return missing
52
+
53
+
54
+ def covered(row, original, coverage=None):
55
+ """Missing/malformed proofs defer this proposal, never erase the head."""
56
+ missing = omissions(row, original)
57
+ if not missing:
58
+ return True
59
+ if not isinstance(coverage, list):
60
+ return False
61
+ expected = {(part["target"], part["old"]) for part in missing}
62
+ # A reviewer may restore a fragment and still include its proof. Check
63
+ # against the real old fragments, not an exact output-count convention.
64
+ all_old = omissions({**row, "patch": {"body": ""}}, original)
65
+ allowed = {(part["target"], part["old"]) for part in all_old}
66
+ verified = set()
67
+ body = "" if row["patch"].get("validity") == "retracted" else row["patch"].get("body", "")
68
+ evidence = {e["ref"]: e for e in original.get("evidence", [])}
69
+ for proof in coverage:
70
+ if not isinstance(proof, dict):
71
+ return False
72
+ key = (proof.get("target"), proof.get("old"))
73
+ if not all(isinstance(value, str) for value in key) or key not in allowed or key in verified:
74
+ return False
75
+ if set(proof) == {"target", "old", "body"}:
76
+ quote = proof["body"]
77
+ if not isinstance(quote, str) or not quote.strip() or quote not in body:
78
+ return False
79
+ elif set(proof) == {"target", "old", "source"}:
80
+ selection = proof["source"]
81
+ if not isinstance(selection, dict) or set(selection) != {"ref", "text"}:
82
+ return False
83
+ if not isinstance(selection["ref"], str):
84
+ return False
85
+ event = evidence.get(selection.get("ref"))
86
+ quote = selection.get("text")
87
+ # An assistant suggestion cannot authorize discarding user facts.
88
+ if (event is None or event.get("use") != "new" or event.get("role") != "user"
89
+ or event["ref"] not in row.get("evidence", [])
90
+ or not isinstance(quote, str) or not quote.strip() or quote not in event.get("text", "")):
91
+ return False
92
+ else:
93
+ return False
94
+ verified.add(key)
95
+ return expected <= verified
96
+
97
+
98
+ def defer_unreviewed(response, snapshot):
99
+ """A manual partial repair has no model reviewer; preserve unsafe heads."""
100
+ from .incremental_semantics import envelope
101
+ from .validation import parse_strict_json
102
+ from .incremental_protocol import compile_incremental
103
+ import json
104
+ draft = envelope(parse_strict_json(response))
105
+ original = snapshot.model_input()
106
+ for i, row in enumerate(draft["items"]):
107
+ if (omissions(row, original)
108
+ and not compile_incremental(json.dumps({"items": [row]}), snapshot)["issues"]):
109
+ draft["items"][i] = {"action": "DEFERRED", "evidence": row.get("evidence", []),
110
+ "reason": "missing_context",
111
+ "need": "The body replacement has unverified omissions from the previous memory."}
112
+ return json.dumps(draft, ensure_ascii=False, separators=(",", ":"))
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.2.95
2
+ version: 1.0.0
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch
@@ -232,11 +232,14 @@ def _drive(service, run_id, token, backend, calls, metric_ordinals):
232
232
  return public_result(run, calls=calls[0], metric_ordinals=metric_ordinals)
233
233
  if run["status"] in {"response_ready", "committing"}:
234
234
  try:
235
+ if (run.get("partial_used") and run.get("partial_recovery", {}).get("mode") == "repair"):
236
+ from .body_preservation import defer_unreviewed
237
+ run["response"] = defer_unreviewed(run["response"], snapshot)
235
238
  compiled = compile_incremental(run["response"], snapshot)
236
239
  from .incremental_semantics import needs_review, review_request
237
240
  if (not (run.get("partial_used") and run.get("partial_recovery", {}).get("mode") == "repair")
238
241
  and run.get("semantic_stage") != "reviewed"
239
- and needs_review(compiled, run["response"])):
242
+ and needs_review(compiled, run["response"], snapshot)):
240
243
  if run["reserved_requests"] >= MAX_INCREMENTAL_REQUESTS:
241
244
  _finish(service, processed, run, "blocked", "semantic_review_budget_exhausted")
242
245
  return public_result(run, calls=calls[0], metric_ordinals=metric_ordinals)
@@ -391,7 +394,7 @@ def _drive(service, run_id, token, backend, calls, metric_ordinals):
391
394
  elif not run.get("partial_used"):
392
395
  try:
393
396
  from .incremental_semantics import needs_review
394
- run["semantic_review_required"] = needs_review(compile_incremental(response, snapshot), response)
397
+ run["semantic_review_required"] = needs_review(compile_incremental(response, snapshot), response, snapshot)
395
398
  except (ValueError, TypeError):
396
399
  run["semantic_review_required"] = False
397
400
  save_run(service, processed, run)
@@ -10,8 +10,10 @@ from copy import deepcopy
10
10
  import json
11
11
 
12
12
  from .validation import parse_strict_json, ModelOutputError
13
+ from .body_preservation import omissions, covered
13
14
 
14
- REVIEW_SYSTEM = """核验拟写入的持续行动。original_input 是唯一事实和权限来源;draft 是待审候选,不能当作事实。返回 JSON 对象,必填 decisions,值为数组;每项有 item(整数)、keep(布尔值)、field_support(对象)。按输入 fields_to_review 的实际字段填 field_support,不能复制其他事项的字段名。
15
+ REVIEW_SYSTEM = """核验拟写入的记忆及持续行动。original_input 是唯一事实和权限来源;draft 是待审候选,不能当作事实。返回 JSON 对象,必填 decisions,值为数组;每项有 item(整数)、keep(布尔值)、field_support(对象)。按输入 fields_to_review 的实际字段填 field_support,不能复制其他事项的字段名。
16
+ 存在 body_omissions 时,keep:true 的 UPDATE/MERGE 必须逐项说明省略的去向;仅 body:true 不足以通过。body_omissions 按 item 列出拟议正文中不再原样出现的旧片段(比较辅助,不表示已经失效)。逐项核对完整含义,不能因主题相同、进展重复或用户未再次提及就删除。需要补回或修正文句时,直接在 decision 增加 body(修正后的完整正文字符串),field_support.body=true;不必复制 action、target、evidence,也不要把该 target 放入 updates。Core 沿用该候选的身份、证据和其他字段;已有 replace 方式仍可用,但不能同时使用 body 和 replace。对最终 UPDATE/MERGE(包括 body、replace 和 updates 修正)的剩余省略片段,在对应 decision/updates 项增加 body_coverage 数组:每项 {"target":"旧目标ref","old":"旧片段原文","body":"最终正文中完整承接该片段含义的逐字引文"},或 {"target":"旧目标ref","old":"旧片段原文","source":{"ref":"new用户证据ref","text":"明确纠正/撤回该内容的逐字引文"}}。body 引文必须完整等价且真实存在于最终正文;不能只引用旧目标中的文字却忘了将它写入修正正文。source 不能引用旧上下文或助手建议。修正后原样保留的片段无需列入。MERGE 对所有被合并目标逐项核对,撤回整个目标也需明确用户依据。无法确认则 keep:false;不要猜测或提供主题相符但实际无关的引文。没有剩余省略时省略 body_coverage。
15
17
  每个 draft.items 都必须有且只有一个 decision,item 是从 0 开始的位置。keep 判断该事项本身是否由原文确立且持续值得保留。临时操作及助手额外建议不能产生用户长期待办,原话只证明另一个操作时 keep:false。独立事实不因其他候选有误而拒绝。
16
18
  keep:true 时,field_support 必须逐个列出候选 memory/patch 中的业务字段,每个布尔值判断该字段的拟议值是否受来源支持;不列 responsibility_basis、effective、at、reopen 等引用/控制字段。没有 replace 的 NO_CHANGE/DEFERRED/NO_MEMORY 的 field_support 为 {}。keep:false 时 field_support 为 {}。unverified_fields 是系统发现未能验证引用的可选责任字段,必须判为 false;不能补造引用。这不影响其他有依据的事实或任务。归属/客户尚未确定不自动表示行动被阻塞。
17
19
  不能仅检查引文存在:必须判断原话是否确立该具体行动、责任、状态和期限。assistant 的建议/计划不能赋予 user 新义务。转发、协调、记录不等于本人执行;支持旧任务不等于接受新安排。
@@ -45,15 +47,23 @@ def fields(row):
45
47
  return {key for key in payload if key not in _METADATA} if isinstance(payload, dict) else set()
46
48
 
47
49
 
48
- def needs_review(compiled, response=None):
50
+ def needs_review(compiled, response=None, snapshot=None):
49
51
  if response is not None:
50
52
  rows = envelope(parse_strict_json(response))["items"]
53
+ original = snapshot.model_input() if snapshot is not None else None
54
+ update_ids = {op.get("target") for op in compiled["operations"] if op["action"] == "UPDATE"}
55
+ writable_updates = ({ref for ref, target in snapshot.state()["targets"].items()
56
+ if target["memory"]["memory_id"] in update_ids} if snapshot is not None else set())
51
57
  actions = {str(row.get("action", "")).strip().upper() for row in rows if isinstance(row, dict)}
52
58
  if "MERGE" in actions or {"UPDATE", "NO_CHANGE"} <= actions:
53
59
  return True
54
60
  for row in rows:
55
61
  if not isinstance(row, dict):
56
62
  continue
63
+ target = row.get("target")
64
+ if (original is not None and isinstance(target, str) and target.strip() in writable_updates
65
+ and omissions(row, original)):
66
+ return True
57
67
  action = row.get("action")
58
68
  action = action.strip().upper() if isinstance(action, str) else ""
59
69
  payload = row.get("memory") if action == "CREATE" else row.get("patch")
@@ -118,10 +128,12 @@ def review_request(original, response, snapshot=None):
118
128
  row["action"] = row["action"].strip().upper()
119
129
  rows.append(row)
120
130
  draft["items"] = rows
131
+ missing = [omissions(row, source) for row in rows]
121
132
  return {"system": REVIEW_SYSTEM,
122
133
  "user": json.dumps({"original_input": source, "draft": draft,
123
134
  "fields_to_review": [sorted(fields(row)) for row in draft["items"]],
124
135
  "unverified_fields": [unverified_fields(row, source) for row in draft["items"]],
136
+ **({"body_omissions": missing} if any(missing) else {}),
125
137
  **({"invalid_representation_items": invalid} if invalid else {})},
126
138
  ensure_ascii=False, separators=(",", ":"))}
127
139
 
@@ -164,7 +176,7 @@ def apply_review(request, response):
164
176
  rejected.append(deepcopy(row))
165
177
  for decision in decisions:
166
178
  if (not isinstance(decision, dict) or not {"item", "keep", "field_support"} <= set(decision)
167
- or set(decision) - {"item", "keep", "field_support", "replace"}):
179
+ or set(decision) - {"item", "keep", "field_support", "replace", "body", "body_coverage"}):
168
180
  raise ValueError
169
181
  i = decision["item"]
170
182
  if type(i) is not int or not 0 <= i < len(rows) or i in positions or type(decision["keep"]) is not bool:
@@ -174,6 +186,14 @@ def apply_review(request, response):
174
186
  if not isinstance(support, dict) or any(type(v) is not bool for v in support.values()):
175
187
  raise ValueError
176
188
  row = rows[i]
189
+ if "body" in decision:
190
+ if (not decision["keep"] or "replace" in decision
191
+ or i in data.get("invalid_representation_items", [])
192
+ or row.get("action") not in {"CREATE", "UPDATE", "MERGE"}
193
+ or "body" not in fields(row) or support.get("body") is not True
194
+ or not isinstance(decision["body"], str)):
195
+ raise ValueError
196
+ row["memory" if row["action"] == "CREATE" else "patch"]["body"] = decision["body"]
177
197
  if "replace" in decision:
178
198
  if i in data.get("invalid_representation_items", []) or not decision["keep"]:
179
199
  raise ValueError
@@ -253,6 +273,9 @@ def apply_review(request, response):
253
273
  if row["action"].upper() == "UPDATE" and not payload:
254
274
  discard(row)
255
275
  rows[i] = None
276
+ elif not covered(row, data["original_input"], decision.get("body_coverage")):
277
+ discard(row)
278
+ rows[i] = None
256
279
  rows = [r for r in rows if r is not None]
257
280
  additions = review.get("updates", [])
258
281
  from .incremental_protocol import MAX_ITEMS
@@ -260,12 +283,24 @@ def apply_review(request, response):
260
283
  raise ValueError
261
284
  used = {row.get("target") for row in rows if isinstance(row, dict)}
262
285
  for entry in additions:
263
- if not isinstance(entry, dict) or set(entry) != {"row", "field_support"}:
264
- raise ValueError
265
- row = _maintenance_update(entry["row"], entry["field_support"], data["original_input"])
266
- if row["target"] in used:
267
- raise ValueError
268
- rows.append(row)
286
+ try:
287
+ if (not isinstance(entry, dict) or not {"row", "field_support"} <= set(entry)
288
+ or set(entry) - {"row", "field_support", "body_coverage"}):
289
+ raise ValueError
290
+ row = _maintenance_update(entry["row"], entry["field_support"], data["original_input"])
291
+ if row["target"] in used:
292
+ raise ValueError
293
+ except (KeyError, TypeError, ValueError):
294
+ # Optional correction is not an authority grant. Reject it
295
+ # explicitly without erasing independently valid decisions.
296
+ rows.append({"action": "DEFERRED", "evidence": [e["ref"] for e in data["original_input"]["evidence"]
297
+ if e["use"] == "new"], "reason": "missing_context",
298
+ "need": "The additional maintenance correction is not source-verified."})
299
+ continue
300
+ if covered(row, data["original_input"], entry.get("body_coverage")):
301
+ rows.append(row)
302
+ else:
303
+ discard(row)
269
304
  used.add(row["target"])
270
305
  for rejected_row in rejected:
271
306
  # The same review can supply a complete, independently validated