memleaf 0.2.88__tar.gz → 0.2.90__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.
- {memleaf-0.2.88 → memleaf-0.2.90}/CHANGELOG.md +46 -0
- {memleaf-0.2.88/src/memleaf.egg-info → memleaf-0.2.90}/PKG-INFO +4 -4
- {memleaf-0.2.88 → memleaf-0.2.90}/README.en.md +3 -3
- {memleaf-0.2.88 → memleaf-0.2.90}/README.md +3 -3
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/hermes-source-comparison-context.md +7 -3
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/memory-state-contract.md +20 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/pyproject.toml +1 -1
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/README.md +2 -2
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/_provider.py +12 -11
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_commit.py +1 -1
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_preview.py +13 -12
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_prompts.py +4 -3
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_protocol.py +2 -2
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_semantics.py +27 -6
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/mcp_server.py +66 -14
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/retrieval_gate.py +52 -2
- memleaf-0.2.90/src/memleaf/retrieval_lifecycle.py +60 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/service.py +13 -4
- {memleaf-0.2.88 → memleaf-0.2.90/src/memleaf.egg-info}/PKG-INFO +4 -4
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf.egg-info/SOURCES.txt +1 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/LICENSE +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/MANIFEST.in +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/acceptance-evidence-guide.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/automatic-processing-route.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/body-compaction.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/candidate-reconciliation.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/capture-budget-design.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/config-migrations.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/core-refactor.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/development-closeout.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/explicit-memory-update.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/extraction-latency.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/failed-run-recovery.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/general-processing.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/hermes-provider-compatibility.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/host-acceptance-runbook.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/immutable-release-assets.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-commit-contract.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-execution.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-native-comparison.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-partial-recovery.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-planner-preview.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-runtime-contract.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-scope-registration.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/incremental-selected-retention.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/installed-artifact-verification.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/memory-lifecycle-tools.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/migration-preflight.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/public-query-integrity.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/query-performance.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/remember-incremental-route.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/runtime-state-retention.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/semantic-extraction-protocol.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/source-work-contract.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/examples/README.md +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/examples/basic_usage.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/examples/incremental_acceptance.json +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/examples/query_benchmark.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/install.ps1 +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/install.sh +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/setup.cfg +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/acceptance.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/adapters/base.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/batch_review.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/capture.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/cli.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/compaction.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/config.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/explicit_text_source.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/_shared.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/host_retention.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/host_turn_identity.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_dates.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_execution.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_explicit.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_failed_recovery.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_journal.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_merge.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_native.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_partial.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_recovery.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_run_state.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_runtime.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_scopes.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/incremental_selection.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/index.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/inspection.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/installer.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/legacy_update_journal.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/memory_commit.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/memory_planner.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/memory_retraction.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/memory_update.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/migration.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/model_execution.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/models.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/parallel_model.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/planning_candidates.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/planning_context.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/process_common.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/process_jobs.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/process_journal.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/processing.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/processing_route.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/prompts.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/provider_compatibility.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/query_clock.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/query_progress.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/query_scan.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/receipt_codec.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/remember_route.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/retention.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/runtime_retention.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/semantic_maintenance.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/semantic_protocol.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/single_pass_memory_planner.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/single_pass_plan.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/source_policy.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/state_layout.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/summary_batch.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/update_review.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/validation.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf/vault.py +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.88 → memleaf-0.2.90}/src/memleaf.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,52 @@ All notable changes to memleaf are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.2.90 — 2026-10-05
|
|
8
|
+
|
|
9
|
+
### Expose withdrawal state in explicit historical retrieval
|
|
10
|
+
|
|
11
|
+
- Include matching retracted current heads when historical retrieval is
|
|
12
|
+
explicitly requested. Ordinary retrieval continues to exclude them.
|
|
13
|
+
- Project bounded lifecycle facts through search and paged MCP reads: the
|
|
14
|
+
historical snapshot flag, exact current identity link, invalidation reason,
|
|
15
|
+
and current validity from the same validated query scan. Keep missing,
|
|
16
|
+
unreadable, conflicting or ambiguous links unknown; support legacy links
|
|
17
|
+
without guessing from titles or content.
|
|
18
|
+
- Preserve archived validity and distinguish past withdrawal from later
|
|
19
|
+
restoration. Associate successful lifecycle-only reads with the captured
|
|
20
|
+
turn's comparison context without fabricating body characters.
|
|
21
|
+
- Reuse query scans and existing pagination, permissions and version checks.
|
|
22
|
+
No model stage, model call, tool or persistence format is added.
|
|
23
|
+
- Validation: 97 affected checks passed, plus an isolated real-conversation
|
|
24
|
+
replay through a source MCP process. One legacy test still expects the
|
|
25
|
+
previously removed waiting_on field and fails identically on the prior
|
|
26
|
+
release. Replay used zero model calls and left the formal Vault unchanged.
|
|
27
|
+
|
|
28
|
+
## 0.2.89 — 2026-10-04
|
|
29
|
+
|
|
30
|
+
### Complete comparison context and bound tool continuation
|
|
31
|
+
|
|
32
|
+
- Remove the 20-memory comparison ceiling and capture truncation. Keep all
|
|
33
|
+
required read, receipt and revision targets, limiting only optional recall
|
|
34
|
+
and the existing total request size. Preserve strict source, scope and
|
|
35
|
+
missing-target validation; incomplete historical capsules remain protected.
|
|
36
|
+
- Use one discoverable Hermes MCP `save_turn` route for source-bound retention
|
|
37
|
+
and lifecycle requests. Stop exposing a competing native save schema while
|
|
38
|
+
preserving its legacy handler and internal completion interface. Queueing
|
|
39
|
+
remains idempotent and does not claim that changes have already been saved.
|
|
40
|
+
- Bind long-body continuation to server-owned memory IDs, versions, offsets
|
|
41
|
+
and history flags. Clients follow `body_continuation` without copying record
|
|
42
|
+
tuples; version changes require an explicit restart of the same target.
|
|
43
|
+
Preserve direct versioned reads and strict current-turn checks.
|
|
44
|
+
- Keep delivery dates and progress out of task titles, retaining years that
|
|
45
|
+
identify the subject. Verify body clauses against actual user-confirmed
|
|
46
|
+
facts and repair unsupported prose in the existing review pass without
|
|
47
|
+
changing evidence, ownership or deadlines. No model stage is added.
|
|
48
|
+
- Validation: 411 affected local checks, an isolated replay of the real
|
|
49
|
+
21-action conversation, and three real DeepSeek extraction/review samples
|
|
50
|
+
passed. Formal Vault data and historical jobs were not changed; updated
|
|
51
|
+
Hermes desktop behavior still requires installation and a host restart.
|
|
52
|
+
|
|
7
53
|
## 0.2.88 — 2026-10-04
|
|
8
54
|
|
|
9
55
|
### Reliable queued retention and bounded action queries
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.90
|
|
4
4
|
Summary: A local-first Markdown memory core for AI agents
|
|
5
5
|
Author: memleaf contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ Dynamic: license-file
|
|
|
23
23
|
|
|
24
24
|
[English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
|
|
25
25
|
|
|
26
|
-
> **版本:0.2.
|
|
26
|
+
> **版本:0.2.90。**
|
|
27
27
|
> 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
|
|
28
28
|
> 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
|
|
29
29
|
> 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
|
|
@@ -166,13 +166,13 @@ python -m pip install -U memleaf && python -m memleaf install
|
|
|
166
166
|
3. 发现并保存可用的聊天模型路由;Hermes CLI 返回的脱敏凭证不会被当成真实 API key,而会继续尝试环境变量和 Hermes `.env`;已有有效 memleaf 路由会保留;
|
|
167
167
|
4. 激活 `memory.provider=memleaf`;
|
|
168
168
|
5. 通过 Hermes 官方 CLI 配置 memleaf MCP;
|
|
169
|
-
6. 配置 MCP lazy/idle 生命周期,以及绑定真实用户消息的 Hermes
|
|
169
|
+
6. 配置 MCP lazy/idle 生命周期,以及绑定真实用户消息的 Hermes MCP 保存入口;
|
|
170
170
|
7. 验证 MCP Server 能发现当前版本声明的全部工具;
|
|
171
171
|
8. 写入本地 Agent 状态索引。
|
|
172
172
|
|
|
173
173
|
完成后重启 Hermes。
|
|
174
174
|
|
|
175
|
-
Hermes
|
|
175
|
+
Hermes 显式保存使用 MCP `save_turn`:先绑定本轮并排队,轮次完成后再以真实用户消息提炼,排队不表示已保存。它与检索工具共用 Hermes 的延迟工具入口,Provider 不再另外暴露原生保存工具。安装器会在 Hermes 中屏蔽独立 MCP `remember`,保留其他工具选择;Core/Python API 和其他客户端的 `remember` 继续可用。仅升级 pip 包不足以更新 Provider 和这项工具配置。
|
|
176
176
|
|
|
177
177
|
如果没有检测到 Hermes、无法取得完整模型路由、Provider 激活失败或 MCP 工具发现验证失败,安装会明确返回失败,不会把未完成的接入报告为成功。
|
|
178
178
|
|
|
@@ -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.
|
|
7
|
+
> **Version: 0.2.90.**
|
|
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.
|
|
@@ -147,13 +147,13 @@ Both installation paths automatically:
|
|
|
147
147
|
3. Discover and save a callable chat-model route. Redacted credentials returned by the Hermes CLI are never treated as real API keys; discovery falls through to environment variables and Hermes `.env`, while preserving an existing valid memleaf route.
|
|
148
148
|
4. Activate `memory.provider=memleaf`.
|
|
149
149
|
5. Configure the memleaf MCP entry through Hermes' official CLI.
|
|
150
|
-
6. Configure MCP lazy/idle lifecycle settings and the source-bound
|
|
150
|
+
6. Configure MCP lazy/idle lifecycle settings and the source-bound Hermes MCP save route.
|
|
151
151
|
7. Verify that the MCP server exposes all tools declared by the installed version.
|
|
152
152
|
8. Record the local Agent integration status.
|
|
153
153
|
|
|
154
154
|
Restart Hermes after installation.
|
|
155
155
|
|
|
156
|
-
Hermes uses
|
|
156
|
+
Hermes uses MCP `save_turn` to queue a request bound to the current turn, then retain the actual user messages after the turn completes. Queued does not mean saved. It shares the deferred MCP tool space with retrieval; the Provider no longer exposes a separate native save schema. The installer excludes standalone MCP `remember` in Hermes while preserving other tool choices; the Core/Python API and other clients keep `remember`. Upgrading only the pip package does not refresh the Provider or this tool configuration.
|
|
157
157
|
|
|
158
158
|
If Hermes cannot be detected, no complete model route can be configured, Provider activation fails, or MCP tool-discovery verification fails, the installer returns an explicit failure rather than reporting an incomplete integration as successful.
|
|
159
159
|
|
|
@@ -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.
|
|
7
|
+
> **版本:0.2.90。**
|
|
8
8
|
> 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
|
|
9
9
|
> 提炼先比较旧事项中的编号、目标、进展、责任和期限;补全未知信息也应维护原事项,另建独立待办不能替代项目事实更新。确认重复的记忆在现有处理请求中合并,保留历史与来源;项目和独立任务保持各自生命周期,Core 不按标题强行合并。
|
|
10
10
|
> 新请求兼容等价的字段表示;未知执行人保持 `null`,新增或变更负责人和期限需要真实来源;等待条件在正文维护,不再提炼 `waiting_on`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
|
|
@@ -147,13 +147,13 @@ python -m pip install -U memleaf && python -m memleaf install
|
|
|
147
147
|
3. 发现并保存可用的聊天模型路由;Hermes CLI 返回的脱敏凭证不会被当成真实 API key,而会继续尝试环境变量和 Hermes `.env`;已有有效 memleaf 路由会保留;
|
|
148
148
|
4. 激活 `memory.provider=memleaf`;
|
|
149
149
|
5. 通过 Hermes 官方 CLI 配置 memleaf MCP;
|
|
150
|
-
6. 配置 MCP lazy/idle 生命周期,以及绑定真实用户消息的 Hermes
|
|
150
|
+
6. 配置 MCP lazy/idle 生命周期,以及绑定真实用户消息的 Hermes MCP 保存入口;
|
|
151
151
|
7. 验证 MCP Server 能发现当前版本声明的全部工具;
|
|
152
152
|
8. 写入本地 Agent 状态索引。
|
|
153
153
|
|
|
154
154
|
完成后重启 Hermes。
|
|
155
155
|
|
|
156
|
-
Hermes
|
|
156
|
+
Hermes 显式保存使用 MCP `save_turn`:先绑定本轮并排队,轮次完成后再以真实用户消息提炼,排队不表示已保存。它与检索工具共用 Hermes 的延迟工具入口,Provider 不再另外暴露原生保存工具。安装器会在 Hermes 中屏蔽独立 MCP `remember`,保留其他工具选择;Core/Python API 和其他客户端的 `remember` 继续可用。仅升级 pip 包不足以更新 Provider 和这项工具配置。
|
|
157
157
|
|
|
158
158
|
如果没有检测到 Hermes、无法取得完整模型路由、Provider 激活失败或 MCP 工具发现验证失败,安装会明确返回失败,不会把未完成的接入报告为成功。
|
|
159
159
|
|
|
@@ -40,9 +40,13 @@ assistant event, including the immutable capture receipt and payload digest.
|
|
|
40
40
|
It accepts no caller-supplied list of IDs through this interface. The capsule is
|
|
41
41
|
comparison metadata, not factual evidence, and its token is not projected into
|
|
42
42
|
the model request. Automatic preparation gives these required targets priority
|
|
43
|
-
alongside explicit-write receipts
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
alongside explicit-write receipts. There is no 20-ID truncation or target-count
|
|
44
|
+
ceiling: required targets expand the preferred candidate count; optional recall
|
|
45
|
+
only fills remaining slots. A missing target or an oversized request still blocks
|
|
46
|
+
preparation instead of silently dropping required context. The existing request
|
|
47
|
+
byte budget remains; no model call is added to capture. Legacy capsules already
|
|
48
|
+
marked overflow remain incomplete and cannot be silently reconstructed from a
|
|
49
|
+
later live gate.
|
|
46
50
|
|
|
47
51
|
A delayed callback can use its own unexpired gate even after a later turn starts.
|
|
48
52
|
Once the capsule is captured, gate expiry cannot erase it or break a duplicate
|
|
@@ -83,6 +83,26 @@ Ordinary `read`, `read_page`, `search`, `context`, and `list_todos` exclude retr
|
|
|
83
83
|
|
|
84
84
|
The derived active index excludes retracted heads. History indexing remains available only through APIs that explicitly include history. Closed todos remain in `knowledge/`; status filtering, rather than age-based deletion, controls their normal presentation.
|
|
85
85
|
|
|
86
|
+
For explicit historical questions, `search_candidates(include_history=True)` and
|
|
87
|
+
MCP `search` also expose matching retracted current heads. Candidate entries and
|
|
88
|
+
`read_page`/MCP `read` with `include_history=True` carry a bounded `lifecycle`:
|
|
89
|
+
|
|
90
|
+
- `historical` distinguishes an archived snapshot from a current head.
|
|
91
|
+
- `active_memory_id`, when present, is the stored exact current-identity link.
|
|
92
|
+
- `invalidated_reason`, when present, describes why that snapshot was archived.
|
|
93
|
+
- `current_validity` comes from the validated current head in the same query
|
|
94
|
+
observation. It is `null` when the linked head is missing, unreadable or
|
|
95
|
+
ambiguous; conflicting or missing legacy links are not inferred from titles.
|
|
96
|
+
|
|
97
|
+
An archived snapshot's own `validity=valid` describes its past assertion. A past
|
|
98
|
+
`invalidated_reason=retracted` does not establish current withdrawal after a
|
|
99
|
+
later restoration. Use `lifecycle.current_validity` for that distinction. The
|
|
100
|
+
projection contains no current body and grants no edit authority. Ordinary
|
|
101
|
+
retrieval remains unchanged and excludes retracted assertions. Historical body
|
|
102
|
+
pages retain their existing explicit history permission and version checks.
|
|
103
|
+
Successful lifecycle-only reads also associate the exact ID with the captured
|
|
104
|
+
turn's comparison context; an empty assertion does not fabricate body characters.
|
|
105
|
+
|
|
86
106
|
## Compatibility boundary
|
|
87
107
|
|
|
88
108
|
`closed_todo_retention_days` remains accepted so existing config files continue to load, but it no longer retires the stable current identity. Optional structured fields stored in frontmatter participate in the protected revision. The incremental model protocol supports UPDATE.patch.validity and an explicit restore with current content. The legacy planner still excludes retracted heads; its older protocol is not silently extended. Deterministic Python retraction and explicit update use the same current identity and protected revision semantics.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.90"
|
|
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
|
|
@@ -10,14 +10,14 @@ Automatic capture and processing use only the visible user and assistant text.
|
|
|
10
10
|
Tool calls and results, email or attachment bodies, and other hidden payloads are
|
|
11
11
|
not automatic memory input.
|
|
12
12
|
Hermes uses MCP `search` and `read` with the current `retrieval_id` for recall.
|
|
13
|
-
The
|
|
13
|
+
The deferred MCP `save_turn` tool queues explicit retention bound to the current
|
|
14
14
|
turn; the completed capture supplies the original user messages and source times.
|
|
15
15
|
A queued response is not a saved memory. The installer excludes standalone MCP
|
|
16
16
|
`remember` in Hermes while preserving other configured tool choices. MCP still
|
|
17
17
|
supports `remember` for other clients, plus `forget` and maintenance operations.
|
|
18
18
|
This is a Soft Gate, not a guarantee that every answer has performed retrieval.
|
|
19
19
|
|
|
20
|
-
The bound
|
|
20
|
+
The bound MCP route also handles conversational corrections, completion,
|
|
21
21
|
cancellation, reopening and withdrawal. A successful full-turn retention receipt
|
|
22
22
|
settles automatic extraction without another model request. Partial selections
|
|
23
23
|
remain separate. MCP lifecycle updates do not accept model-supplied source times;
|
|
@@ -1371,7 +1371,8 @@ class MemleafMemoryProvider(MemoryProvider):
|
|
|
1371
1371
|
return (
|
|
1372
1372
|
"# Memleaf Memory\n"
|
|
1373
1373
|
"For a request to save, correct, complete, cancel, reopen, or withdraw memory, "
|
|
1374
|
-
"call
|
|
1374
|
+
"discover and call the deferred MCP tool mcp__memleaf__save_turn once with this turn's retrieval_id, "
|
|
1375
|
+
"using the exact name returned by tool_search through tool_call. It queues the actual "
|
|
1375
1376
|
"whole turn; no rewritten content, target revision, or message time is needed. "
|
|
1376
1377
|
"Use the returned user_message as the save acknowledgement. A queued request "
|
|
1377
1378
|
"starts processing only after your final reply, so its changes are pending.\n"
|
|
@@ -1390,8 +1391,8 @@ class MemleafMemoryProvider(MemoryProvider):
|
|
|
1390
1391
|
"todos, all unfinished work, urgent work, or work due in a time range, call memleaf MCP "
|
|
1391
1392
|
"list_todos(view=full) instead of relevance search; omit scope for a global query. "
|
|
1392
1393
|
"Use the returned continuation arguments until has_more=false, without copying a cursor. "
|
|
1393
|
-
"Each row includes a body;
|
|
1394
|
-
"
|
|
1394
|
+
"Each row includes a body; after list pagination, follow the returned body_continuation "
|
|
1395
|
+
"with MCP read until it is absent. The server owns the memory ID, version and offset. "
|
|
1395
1396
|
"Never exclude a todo because another Hermes session or another Agent created it. Hermes has a soft "
|
|
1396
1397
|
"observer only: do not claim a search happened unless the visible tool "
|
|
1397
1398
|
"messages show it. Visible Hermes "
|
|
@@ -2067,8 +2068,10 @@ class MemleafMemoryProvider(MemoryProvider):
|
|
|
2067
2068
|
if not tool_name.startswith(prefix):
|
|
2068
2069
|
return None
|
|
2069
2070
|
name = tool_name[len(prefix):]
|
|
2070
|
-
if name not in {"search", "read", "list_todos", "scope_catalog", "remember", "update_memory"}:
|
|
2071
|
+
if name not in {"search", "read", "list_todos", "scope_catalog", "remember", "update_memory", "save_turn"}:
|
|
2071
2072
|
return None
|
|
2073
|
+
if name == "save_turn" and (not self._write_enabled or not self._gate_enabled):
|
|
2074
|
+
raise ValueError("memory_host_retention_disabled")
|
|
2072
2075
|
turn_id = _host_turn_id(turn_context)
|
|
2073
2076
|
session = turn_context.get("session_id") if isinstance(turn_context, Mapping) else None
|
|
2074
2077
|
ordinal = turn_context.get("turn_number") if isinstance(turn_context, Mapping) else None
|
|
@@ -2105,13 +2108,11 @@ class MemleafMemoryProvider(MemoryProvider):
|
|
|
2105
2108
|
return bound
|
|
2106
2109
|
|
|
2107
2110
|
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
|
2108
|
-
#
|
|
2109
|
-
#
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
return [
|
|
2113
|
-
"parameters": {"type": "object", "properties": {"retrieval_id": {"type": "string"}},
|
|
2114
|
-
"required": ["retrieval_id"], "additionalProperties": False}}]
|
|
2111
|
+
# One discoverable MCP save_turn route avoids the host's split between
|
|
2112
|
+
# native provider tools and deferred tool_call dispatch. Keep the old
|
|
2113
|
+
# handler below for already registered native clients, without exposing
|
|
2114
|
+
# two competing ways to save the same turn.
|
|
2115
|
+
return []
|
|
2115
2116
|
|
|
2116
2117
|
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs: Any) -> str:
|
|
2117
2118
|
if (tool_name != "memleaf_remember" or not self._write_enabled or not self._gate_enabled
|
|
@@ -56,7 +56,7 @@ def _arguments(source, session_id, turn_id, scope, priority_memory_ids, candidat
|
|
|
56
56
|
source, session_id = safe_component(source, "source"), safe_component(session_id, "session id")
|
|
57
57
|
if not isinstance(turn_id, str) or not turn_id or len(turn_id) > 800:
|
|
58
58
|
raise ValueError("invalid_turn_id")
|
|
59
|
-
if type(candidate_limit) is not int or
|
|
59
|
+
if type(candidate_limit) is not int or candidate_limit < 1:
|
|
60
60
|
raise ValueError("invalid_candidate_limit")
|
|
61
61
|
if type(allow_new_scopes) is not bool:
|
|
62
62
|
raise ValueError("invalid_context_flags")
|
|
@@ -30,13 +30,14 @@ def _prepare_incremental_unlocked(service: Any, *, source: str, session_id: str,
|
|
|
30
30
|
|
|
31
31
|
At this stage evidence units are complete visible messages, not per-sentence
|
|
32
32
|
heuristic fragments. Caller-provided target IDs are required context and are
|
|
33
|
-
never dropped to fit
|
|
33
|
+
never dropped to fit a count limit. candidate_limit bounds optional recall;
|
|
34
|
+
required comparison targets expand that count within the request byte budget.
|
|
34
35
|
"""
|
|
35
36
|
source = safe_component(source, "source")
|
|
36
37
|
session_id = safe_component(session_id, "session id")
|
|
37
38
|
if not isinstance(turn_id, str) or not turn_id:
|
|
38
39
|
raise ValueError("invalid_turn_id")
|
|
39
|
-
if type(candidate_limit) is not int or
|
|
40
|
+
if type(candidate_limit) is not int or candidate_limit < 1:
|
|
40
41
|
raise ValueError("invalid_candidate_limit")
|
|
41
42
|
from .incremental_scopes import registry_view, resolve_scope
|
|
42
43
|
registry, scope_guard, scope_aliases = registry_view(service.vault.config())
|
|
@@ -47,8 +48,6 @@ def _prepare_incremental_unlocked(service: Any, *, source: str, session_id: str,
|
|
|
47
48
|
if isinstance(priority_memory_ids, (str, bytes)):
|
|
48
49
|
raise ValueError("invalid_priority_ids")
|
|
49
50
|
priority = list(dict.fromkeys(priority_memory_ids))
|
|
50
|
-
if len(priority) > candidate_limit:
|
|
51
|
-
raise ValueError("blocked_context")
|
|
52
51
|
for identity in priority:
|
|
53
52
|
safe_component(identity, "memory id")
|
|
54
53
|
# Do not use the mutation boundary: it would resume compaction writes.
|
|
@@ -149,15 +148,13 @@ def _prepare_incremental_unlocked(service: Any, *, source: str, session_id: str,
|
|
|
149
148
|
if context.get("overflow") is not False:
|
|
150
149
|
raise ValueError("blocked_context")
|
|
151
150
|
identities = context.get("memory_ids")
|
|
152
|
-
if not isinstance(identities, list)
|
|
151
|
+
if not isinstance(identities, list):
|
|
153
152
|
raise ValueError("invalid_comparison_context")
|
|
154
153
|
for identity in identities:
|
|
155
154
|
safe_component(identity, "memory id")
|
|
156
155
|
read_ids.extend(identities)
|
|
157
156
|
priority = list(dict.fromkeys(explicit_ids + read_ids + priority))
|
|
158
157
|
priority += sorted(x for x in prior_ids if x.casefold() in records and x not in priority)
|
|
159
|
-
if len(priority) > candidate_limit:
|
|
160
|
-
raise ValueError("blocked_context")
|
|
161
158
|
chosen = []
|
|
162
159
|
for identity in priority:
|
|
163
160
|
if identity.casefold() not in records:
|
|
@@ -165,15 +162,19 @@ def _prepare_incremental_unlocked(service: Any, *, source: str, session_id: str,
|
|
|
165
162
|
chosen.append(records[identity.casefold()])
|
|
166
163
|
from .planning_candidates import ComparisonRanker
|
|
167
164
|
ranker = ComparisonRanker(records)
|
|
165
|
+
# Required reads/receipts are supplied in full. Recall adds only enough
|
|
166
|
+
# optional targets to reach the caller's preferred count, without ranking
|
|
167
|
+
# unrelated memories when required context already exceeds it.
|
|
168
|
+
optional_limit = min(max(0, candidate_limit - len(chosen)), len(records))
|
|
169
|
+
seen = {memory.memory_id.casefold() for memory in chosen}
|
|
168
170
|
query_rows = []
|
|
169
|
-
for event in evidence:
|
|
171
|
+
for event in evidence if optional_limit else ():
|
|
170
172
|
for is_native in (False, True):
|
|
171
|
-
keys = [key for key in records if (key in native_keys) == is_native]
|
|
172
|
-
query_rows.append([records[key] for key in ranker.rank(event["text"], keys,
|
|
173
|
+
keys = [key for key in records if key not in seen and (key in native_keys) == is_native]
|
|
174
|
+
query_rows.append([records[key] for key in ranker.rank(event["text"], keys, optional_limit)])
|
|
173
175
|
# Reuse the pure retrieval functions, not service._search_unlocked(),
|
|
174
176
|
# whose index accessor may rebuild files or recover compaction.
|
|
175
|
-
|
|
176
|
-
for rank in range(candidate_limit):
|
|
177
|
+
for rank in range(optional_limit):
|
|
177
178
|
for candidates in query_rows:
|
|
178
179
|
if len(chosen) >= candidate_limit:
|
|
179
180
|
break
|
|
@@ -6,9 +6,10 @@ INCREMENTAL_SYSTEM = """从完整可见对话提炼长期记忆,返回严格 J
|
|
|
6
6
|
- 用户明确撤回旧事实或偏好:UPDATE 原 target,patch.validity=retracted;撤回说明转入历史,不作为仍有效的偏好保留。用户明确给出替代事实/新偏好:UPDATE 原 target 为新的有效内容。撤回后“需要时另说”不代表已建立替代偏好。
|
|
7
7
|
- user 的已确认事实、决定、持续目标、责任、期限、进展、偏好可保留。临时操作请求及普通应答不自动产生长期待办。
|
|
8
8
|
- assistant 仅能解释已确认事实、报告有依据的已执行结果。assistant 的建议、计划、推测、额外安排不产生用户/第三人义务、承诺、偏好或期限;用户确认一件事不等于确认回复的其他建议。不要把“建议以后做”改写成“需要做”。
|
|
9
|
+
- 逐分句核验正文:assistant 增加的原因、影响、条件或后续步骤,user 未确认则省略;其余已确认事实保留。
|
|
9
10
|
- use=new 是本轮变化依据;context、旧目标、explicit_writes 仅供比较,不能独立产生新事实。逐项核对证据是否支持拟写事实、归属、执行人和日期。
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
比较 new 与目标 observed 的先后;已被较新 context/目标覆盖的变化不重放,无变化 NO_CHANGE,不借 context 清空 deadline。按身份/编号、主体、目标和业务上下文比较全部候选,包含已完成项和跨会话来源。补编号、纠正状态、补有依据的结构字段均 UPDATE 原目标;完整覆盖才 NO_CHANGE。独立新事项 CREATE;同一变化同步维护全部相关候选,旧职责不可遗漏。项目与独立任务各保留生命周期;确为同一事项的重复记录才 MERGE,不凭标题相似合并。保留其余有效旧事实,正文表达当前状态,不追加日志。
|
|
12
13
|
|
|
13
14
|
动作:
|
|
14
15
|
CREATE:action、非空 evidence、memory;memory 必填 type/scope/title/body。
|
|
@@ -18,13 +19,13 @@ NO_CHANGE:action、非空 evidence、target;正文及结构化字段均已
|
|
|
18
19
|
DEFERRED:action、非空 evidence、reason、need;reason=missing_identity|missing_context|conflict,need 说明无法确定的内容。
|
|
19
20
|
NO_MEMORY:仅 action;自动模式整轮无值得维护/新增的内容时独占 items。
|
|
20
21
|
每项 evidence 是引用字符串数组,例如 ["e1","e2"],不能复制整个证据对象。至少一个 use=new 引用,必须实际支持本项,不要求覆盖每条消息。
|
|
21
|
-
|
|
22
|
+
结构示例:
|
|
22
23
|
{"action":"CREATE","evidence":["e1"],"memory":{"type":"fact","scope":"unscoped","title":"…","body":"…"}}
|
|
23
24
|
{"action":"UPDATE","evidence":["e1"],"target":"m1","patch":{"body":"…"}}
|
|
24
|
-
{"action":"NO_CHANGE","evidence":["e1"],"target":"m1"}
|
|
25
25
|
{"items":[{"action":"NO_MEMORY"}]}
|
|
26
26
|
|
|
27
27
|
字段:
|
|
28
|
+
title 标识事项,不放交期/发生日/进度,改存正文与结构字段;旧标题交期一并清理。名称固有年度等身份日期保留。
|
|
28
29
|
type=fact|todo|preference|project|event|identity|other。有独立完成条件的持续行动设 actionable:true(todo 隐含);事实类别与行动属性可分开表达。
|
|
29
30
|
status=active|completed|cancelled;完成/取消/交接同时纠正冲突的 title/body;独立未完行动不能被兄弟项完成吞掉。重新开启 UPDATE 使用 reopen:true 和 patch.status=active,需要较新明确依据。
|
|
30
31
|
assignee 是实际执行人,只有明确由用户本人执行才 user,其他执行人用明确名称,未知为 null。用户记录/转发/协调不证明本人执行。等待依赖、卡点仅在正文维护,按最新事实替换过时内容,不生成等待字段。
|
|
@@ -24,7 +24,7 @@ from .turn_plan import revision_digest
|
|
|
24
24
|
from .validation import ModelOutputError, parse_strict_json
|
|
25
25
|
|
|
26
26
|
PROTOCOL_VERSION = "incremental-items-v1"
|
|
27
|
-
SEMANTIC_PROTOCOL = "incremental-turn-
|
|
27
|
+
SEMANTIC_PROTOCOL = "incremental-turn-v17"
|
|
28
28
|
EXTRACTION_CONTRACT = "field-reviewed-v1"
|
|
29
29
|
MAX_BYTES = 128 * 1024
|
|
30
30
|
MAX_ITEMS = 64
|
|
@@ -341,7 +341,7 @@ class PlanningSnapshot:
|
|
|
341
341
|
for status in basis_statuses.values())):
|
|
342
342
|
raise ValueError("invalid_basis_status")
|
|
343
343
|
targets = targets or {}
|
|
344
|
-
if
|
|
344
|
+
if writable and set(writable) - set(targets):
|
|
345
345
|
raise ValueError("invalid_targets")
|
|
346
346
|
for ref, memory in targets.items():
|
|
347
347
|
if not re.fullmatch(r"m[1-9][0-9]*", ref) or not isinstance(memory, Memory):
|
|
@@ -15,10 +15,11 @@ REVIEW_SYSTEM = """核验拟写入的持续行动。original_input 是唯一事
|
|
|
15
15
|
每个 draft.items 都必须有且只有一个 decision,item 是从 0 开始的位置。keep 判断该事项本身是否由原文确立且持续值得保留。临时操作及助手额外建议不能产生用户长期待办,原话只证明另一个操作时 keep:false。独立事实不因其他候选有误而拒绝。
|
|
16
16
|
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
17
|
不能仅检查引文存在:必须判断原话是否确立该具体行动、责任、状态和期限。assistant 的建议/计划不能赋予 user 新义务。转发、协调、记录不等于本人执行;支持旧任务不等于接受新安排。
|
|
18
|
+
body 必须逐个事实分句核验;assistant 自行增加的原因、影响判断、条件或后续步骤不受 user 原话支持,不能因整段主题相符就判 true。剔除无依据分句,保留其余已确认事实,可用 replace 修正 CREATE 的 title/body;没有修正时 body:false。title 只标识事项,交期/发生日/进度放正文与结构字段;名称固有的年度等身份信息保留。UPDATE/NO_CHANGE 的旧标题仍含交期时,在现有 replace/updates 中一起维护。
|
|
18
19
|
尤其检查拟改 deadline:依赖条件、交接执行人、进度、尚未完成都不证明原期限已取消;只有明确取消/替换这个期限才支持变更。没有明确期限变化时 deadline:false,原期限沿用。卡点和等待条件仅在正文中维护,并清除已经解决的旧卡点。
|
|
19
20
|
新引用只能来自 new;context 和旧目标只供比较,不能重新推动旧变化。候选若仅重新陈述旧目标已覆盖的内容,不应作为新 UPDATE。同一事项补充/纠正维护原目标,独立事项允许 CREATE,不能凭标题相似合并。
|
|
20
21
|
逐字段判断后输出 decisions;对共同事实变更,检查所有候选记忆中是否仍有旧职责或旧状态,不能认可只更新一条而遗留冲突。确认 MERGE 各方确为同一事项且完整保留有效内容;独立生命周期的项目/任务不可合并。
|
|
21
|
-
修正遗漏时,decision 可增加 replace(同 target 的完整 UPDATE
|
|
22
|
+
修正遗漏时,decision 可增加 replace(同 target 的完整 UPDATE,或原 CREATE 仅改 title/body 的完整副本),field_support 判断替换后的字段;CREATE 修正不得改变其 evidence、scope、type、责任、期限或引用。不得替换 DEFERRED,也不得新增义务或猜测未知。还可输出 updates:[{"row":UPDATE,"field_support":{...}}] 更新原 draft 未涉及的可写候选目标,证据仍只取原输入 new。没有修正就省略 replace/updates。每条修正必须保留仍有效的旧事实与原期限;撤回用 validity=retracted。不要输出解释。
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
25
|
_METADATA = {"responsibility_basis", "effective", "at", "reopen"}
|
|
@@ -165,11 +166,30 @@ def apply_review(request, response):
|
|
|
165
166
|
raise ValueError
|
|
166
167
|
row = rows[i]
|
|
167
168
|
if "replace" in decision:
|
|
168
|
-
if
|
|
169
|
-
or row.get("action") not in {"UPDATE", "NO_CHANGE"}):
|
|
169
|
+
if i in data.get("invalid_representation_items", []) or not decision["keep"]:
|
|
170
170
|
raise ValueError
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
if row.get("action") == "CREATE":
|
|
172
|
+
replacement = deepcopy(decision["replace"])
|
|
173
|
+
if (not isinstance(replacement, dict) or replacement.get("action") != "CREATE"
|
|
174
|
+
or not isinstance(replacement.get("memory"), dict)
|
|
175
|
+
or not isinstance(row.get("memory"), dict)
|
|
176
|
+
or set(support) != fields(replacement)
|
|
177
|
+
or any(v is not True for v in support.values())):
|
|
178
|
+
raise ValueError
|
|
179
|
+
# Review can repair prose, not rewrite the draft's source,
|
|
180
|
+
# authority, identity, lifecycle or field annotations.
|
|
181
|
+
def without_prose(value):
|
|
182
|
+
value = deepcopy(value)
|
|
183
|
+
for key in ("title", "body"):
|
|
184
|
+
value["memory"].pop(key, None)
|
|
185
|
+
return value
|
|
186
|
+
if without_prose(row) != without_prose(replacement):
|
|
187
|
+
raise ValueError
|
|
188
|
+
elif row.get("action") in {"UPDATE", "NO_CHANGE"}:
|
|
189
|
+
replacement = _maintenance_update(decision["replace"], support, data["original_input"])
|
|
190
|
+
if replacement["target"] != row["target"]:
|
|
191
|
+
raise ValueError
|
|
192
|
+
else:
|
|
173
193
|
raise ValueError
|
|
174
194
|
row = rows[i] = replacement
|
|
175
195
|
data.get("unverified_fields", [[] for _ in rows])[i] = []
|
|
@@ -224,7 +244,8 @@ def apply_review(request, response):
|
|
|
224
244
|
rows[i] = None
|
|
225
245
|
rows = [r for r in rows if r is not None]
|
|
226
246
|
additions = review.get("updates", [])
|
|
227
|
-
|
|
247
|
+
from .incremental_protocol import MAX_ITEMS
|
|
248
|
+
if not isinstance(additions, list) or len(rows) + len(additions) > MAX_ITEMS:
|
|
228
249
|
raise ValueError
|
|
229
250
|
used = {row.get("target") for row in rows if isinstance(row, dict)}
|
|
230
251
|
for entry in additions:
|