memleaf 0.2.89__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.89 → memleaf-0.2.90}/CHANGELOG.md +21 -0
- {memleaf-0.2.89/src/memleaf.egg-info → memleaf-0.2.90}/PKG-INFO +2 -2
- {memleaf-0.2.89 → memleaf-0.2.90}/README.en.md +1 -1
- {memleaf-0.2.89 → memleaf-0.2.90}/README.md +1 -1
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/memory-state-contract.md +20 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/pyproject.toml +1 -1
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/mcp_server.py +13 -3
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/retrieval_gate.py +3 -1
- memleaf-0.2.90/src/memleaf/retrieval_lifecycle.py +60 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/service.py +13 -4
- {memleaf-0.2.89 → memleaf-0.2.90/src/memleaf.egg-info}/PKG-INFO +2 -2
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/SOURCES.txt +1 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/LICENSE +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/MANIFEST.in +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/acceptance-evidence-guide.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/automatic-processing-route.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/body-compaction.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/candidate-reconciliation.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/capture-budget-design.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/config-migrations.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/core-refactor.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/development-closeout.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/explicit-memory-update.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/extraction-latency.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/failed-run-recovery.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/general-processing.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/hermes-provider-compatibility.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/hermes-source-comparison-context.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/host-acceptance-runbook.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/immutable-release-assets.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-commit-contract.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-execution.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-native-comparison.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-partial-recovery.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-planner-preview.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-runtime-contract.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-scope-registration.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/incremental-selected-retention.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/installed-artifact-verification.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/memory-lifecycle-tools.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/migration-preflight.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/public-query-integrity.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/query-performance.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/remember-incremental-route.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/runtime-state-retention.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/semantic-extraction-protocol.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/source-work-contract.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/examples/README.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/examples/basic_usage.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/examples/incremental_acceptance.json +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/examples/query_benchmark.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/install.ps1 +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/install.sh +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/setup.cfg +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/acceptance.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/base.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/batch_review.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/capture.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/cli.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/compaction.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/config.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/explicit_text_source.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/_provider.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/_shared.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_retention.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/host_turn_identity.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_commit.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_dates.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_execution.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_explicit.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_failed_recovery.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_journal.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_merge.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_native.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_partial.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_preview.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_prompts.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_protocol.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_recovery.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_run_state.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_runtime.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_scopes.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_selection.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/incremental_semantics.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/index.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/inspection.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/installer.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/legacy_update_journal.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_commit.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_planner.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_retraction.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_update.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/migration.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/model_execution.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/models.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/parallel_model.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/planning_candidates.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/planning_context.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_common.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_jobs.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_journal.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/processing.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/processing_route.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/prompts.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/provider_compatibility.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/query_clock.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/query_progress.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/query_scan.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/receipt_codec.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/remember_route.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/retention.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/runtime_retention.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/semantic_maintenance.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/semantic_protocol.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/single_pass_memory_planner.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/single_pass_plan.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/source_policy.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/state_layout.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/summary_batch.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/update_review.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/validation.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf/vault.py +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.89 → memleaf-0.2.90}/src/memleaf.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,27 @@ 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
|
+
|
|
7
28
|
## 0.2.89 — 2026-10-04
|
|
8
29
|
|
|
9
30
|
### Complete comparison context and bound tool continuation
|
|
@@ -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`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
|
|
@@ -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.
|
|
@@ -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`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
|
|
@@ -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
|
|
@@ -244,6 +244,8 @@ _TOOLS: tuple[dict[str, Any], ...] = (
|
|
|
244
244
|
"MCP client may omit it only on the first page; memleaf returns a short-lived retrieval_id "
|
|
245
245
|
"for pagination/read. Errors are failures, never no_match. Managed MCP search is "
|
|
246
246
|
"directory-only; view=full is rejected. "
|
|
247
|
+
"For explicit history/withdrawal questions use include_history=true. Results include lifecycle: "
|
|
248
|
+
"historical snapshot, linked current ID/validity and invalidation reason; null current validity is unknown. "
|
|
247
249
|
"Python search(view='full') remains the compatibility interface."
|
|
248
250
|
),
|
|
249
251
|
"inputSchema": _object_schema(
|
|
@@ -303,9 +305,10 @@ _TOOLS: tuple[dict[str, Any], ...] = (
|
|
|
303
305
|
"name": "read",
|
|
304
306
|
"description": (
|
|
305
307
|
"Read one memory by exact memory_id in pages. Each page is capped at 2000 body "
|
|
306
|
-
"characters
|
|
308
|
+
"characters with bounded content, state, and paging/version fields; "
|
|
307
309
|
"retrieval_id is required, and the retrieval turn must have a current FOUND search; "
|
|
308
310
|
"NO_MATCH, ERROR, and DEGRADED turns are rejected. "
|
|
311
|
+
"Read historical or retracted records with include_history=true; lifecycle distinguishes past invalidation from current validity. "
|
|
309
312
|
"Prefer returned body_continuation: pass only continue=true and retrieval_id to read the next unfinished body, with its ID/version/offset held by the server. "
|
|
310
313
|
"On memory_version_changed, add restart=true to that continuation to restart the same bound memory at offset 0 with its new version. "
|
|
311
314
|
"continue with next_offset only when has_more is true, passing the returned version "
|
|
@@ -693,6 +696,9 @@ def _read_page_result(value: Any) -> dict[str, Any] | None:
|
|
|
693
696
|
result[name] = field
|
|
694
697
|
if "due_anchor" in value:
|
|
695
698
|
result["due_anchor"] = _jsonable(value.get("due_anchor"))
|
|
699
|
+
if "lifecycle" in value:
|
|
700
|
+
from .retrieval_lifecycle import project_lifecycle
|
|
701
|
+
result["lifecycle"] = project_lifecycle(value["lifecycle"])
|
|
696
702
|
result.update(_query_metadata(value))
|
|
697
703
|
if "read_accounting" in value:
|
|
698
704
|
if not isinstance(value["read_accounting"], str) or value["read_accounting"] not in {"counted", "not_counted", "unavailable"}:
|
|
@@ -734,7 +740,7 @@ def _search_result(value: Any) -> dict[str, Any]:
|
|
|
734
740
|
entries = value.get("results")
|
|
735
741
|
if not isinstance(entries, list):
|
|
736
742
|
raise ValueError("invalid search results")
|
|
737
|
-
results: list[dict[str,
|
|
743
|
+
results: list[dict[str, Any]] = []
|
|
738
744
|
for entry in entries:
|
|
739
745
|
if not isinstance(entry, Mapping):
|
|
740
746
|
raise ValueError("invalid search entry")
|
|
@@ -744,7 +750,11 @@ def _search_result(value: Any) -> dict[str, Any]:
|
|
|
744
750
|
raise ValueError("invalid search identifiers")
|
|
745
751
|
# Search is intentionally narrower than the legacy directory helper:
|
|
746
752
|
# do not leak per-memory scope/tags/aliases/keywords into candidates.
|
|
747
|
-
|
|
753
|
+
row = {"memory_id": memory_id, "title": title}
|
|
754
|
+
if "lifecycle" in entry:
|
|
755
|
+
from .retrieval_lifecycle import project_lifecycle
|
|
756
|
+
row["lifecycle"] = project_lifecycle(entry["lifecycle"])
|
|
757
|
+
results.append(row)
|
|
748
758
|
if bool(results) != (value["status"] == "found"):
|
|
749
759
|
raise ValueError("inconsistent search results")
|
|
750
760
|
paging = _paging_fields(value)
|
|
@@ -762,7 +762,9 @@ def guarded_read(
|
|
|
762
762
|
elif prior is not None:
|
|
763
763
|
pending.pop(prior)
|
|
764
764
|
entry["body_continuations"] = pending
|
|
765
|
-
|
|
765
|
+
# A verified empty tombstone still supplies lifecycle facts and must
|
|
766
|
+
# remain available to the captured turn's comparison context.
|
|
767
|
+
if body or isinstance(result.get("lifecycle"), Mapping):
|
|
766
768
|
if memory_id not in read_ids:
|
|
767
769
|
read_ids.append(memory_id)
|
|
768
770
|
entry["read_ids"] = read_ids
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Bounded lifecycle facts for explicitly requested historical retrieval."""
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .query_scan import QueryScan, ScanRecord
|
|
6
|
+
from .vault import safe_component
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def current_validities(snapshot: QueryScan) -> dict[str, str]:
|
|
10
|
+
# Only validated, unambiguous current records can establish current state.
|
|
11
|
+
unavailable = snapshot.ambiguous | {i.identity.casefold() for i in snapshot.issues if i.identity}
|
|
12
|
+
return {r.memory.memory_id: r.memory.validity
|
|
13
|
+
for r in snapshot.area("knowledge")
|
|
14
|
+
if r.memory.memory_id.casefold() not in unavailable}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def lifecycle(record: ScanRecord, current: Mapping[str, str]) -> dict[str, Any]:
|
|
18
|
+
historical = record.area == "history"
|
|
19
|
+
result = {"historical": historical,
|
|
20
|
+
"current_validity": None if historical else record.memory.validity}
|
|
21
|
+
if not historical:
|
|
22
|
+
return result
|
|
23
|
+
extra = record.memory.extra
|
|
24
|
+
links = [extra[k] for k in ("active_memory_id", "original_memory_id") if k in extra]
|
|
25
|
+
if links and all(isinstance(v, str) and len(v) <= 800 and v == links[0] for v in links):
|
|
26
|
+
try:
|
|
27
|
+
identity = safe_component(links[0], "active memory id")
|
|
28
|
+
except ValueError:
|
|
29
|
+
pass
|
|
30
|
+
else:
|
|
31
|
+
result.update(active_memory_id=identity, current_validity=current.get(identity))
|
|
32
|
+
reason = extra.get("invalidated_reason")
|
|
33
|
+
if isinstance(reason, str) and 0 < len(reason) <= 120:
|
|
34
|
+
result["invalidated_reason"] = reason
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def project_lifecycle(value: Any) -> dict[str, Any]:
|
|
39
|
+
"""Do not pass arbitrary archive extras through the MCP boundary."""
|
|
40
|
+
if (not isinstance(value, Mapping)
|
|
41
|
+
or not {"historical", "current_validity"} <= set(value)
|
|
42
|
+
or set(value) - {"historical", "current_validity", "active_memory_id", "invalidated_reason"}
|
|
43
|
+
or type(value["historical"]) is not bool
|
|
44
|
+
or value["current_validity"] not in (None, "valid", "retracted")):
|
|
45
|
+
raise ValueError("invalid retrieval lifecycle")
|
|
46
|
+
result = dict(value)
|
|
47
|
+
if not value["historical"] and (value["current_validity"] is None
|
|
48
|
+
or set(value) != {"historical", "current_validity"}):
|
|
49
|
+
raise ValueError("invalid current lifecycle")
|
|
50
|
+
if value["historical"] and value["current_validity"] is not None and "active_memory_id" not in value:
|
|
51
|
+
raise ValueError("current validity requires an exact archive link")
|
|
52
|
+
if "active_memory_id" in value:
|
|
53
|
+
if not isinstance(value["active_memory_id"], str) or len(value["active_memory_id"]) > 800:
|
|
54
|
+
raise ValueError("invalid active memory id")
|
|
55
|
+
safe_component(value["active_memory_id"], "active memory id")
|
|
56
|
+
if "invalidated_reason" in value:
|
|
57
|
+
reason = value["invalidated_reason"]
|
|
58
|
+
if not isinstance(reason, str) or not 0 < len(reason) <= 120:
|
|
59
|
+
raise ValueError("invalid invalidation reason")
|
|
60
|
+
return result
|
|
@@ -58,6 +58,7 @@ from .scope_state import (
|
|
|
58
58
|
)
|
|
59
59
|
from .vault import Vault, safe_component
|
|
60
60
|
from .query_scan import QueryScan, ScanRecord, scan_memories, ensure_scan_current
|
|
61
|
+
from .retrieval_lifecycle import current_validities, lifecycle
|
|
61
62
|
from .query_progress import observe_progress
|
|
62
63
|
|
|
63
64
|
|
|
@@ -989,7 +990,9 @@ class Memleaf:
|
|
|
989
990
|
revision=revision_digest(memory),
|
|
990
991
|
structured_fields=memory.extra,
|
|
991
992
|
record=record,
|
|
992
|
-
), **
|
|
993
|
+
), **({"lifecycle": lifecycle(record, current_validities(snapshot))}
|
|
994
|
+
if include_history else {}),
|
|
995
|
+
**self._query_envelope(snapshot, scope=memory.scopes)}
|
|
993
996
|
|
|
994
997
|
from .native_index import NativeIndexer
|
|
995
998
|
|
|
@@ -1157,8 +1160,8 @@ class Memleaf:
|
|
|
1157
1160
|
|
|
1158
1161
|
@staticmethod
|
|
1159
1162
|
def _candidate_directory(record: _Record) -> dict[str, Any]:
|
|
1160
|
-
#
|
|
1161
|
-
#
|
|
1163
|
+
# Ordinary candidate entries remain memory_id + title. Explicit
|
|
1164
|
+
# historical queries add lifecycle separately. Scope selection happened
|
|
1162
1165
|
# before search via the bounded Scope Map and search input.
|
|
1163
1166
|
display = directory_entry(record.memory)
|
|
1164
1167
|
return {
|
|
@@ -1259,6 +1262,8 @@ class Memleaf:
|
|
|
1259
1262
|
) -> dict[str, Any]:
|
|
1260
1263
|
"""Search and return only a bounded directory of candidate memories."""
|
|
1261
1264
|
|
|
1265
|
+
if type(include_history) is not bool:
|
|
1266
|
+
raise ValueError("include_history must be a boolean")
|
|
1262
1267
|
page_limit = self._page_limit(
|
|
1263
1268
|
limit,
|
|
1264
1269
|
MAX_SEARCH_CANDIDATE_ITEMS,
|
|
@@ -1289,6 +1294,7 @@ class Memleaf:
|
|
|
1289
1294
|
limit=None,
|
|
1290
1295
|
stable=True,
|
|
1291
1296
|
strict_candidates=True,
|
|
1297
|
+
include_retracted=include_history,
|
|
1292
1298
|
query_snapshot=snapshot,
|
|
1293
1299
|
)
|
|
1294
1300
|
records = [
|
|
@@ -1296,7 +1302,10 @@ class Memleaf:
|
|
|
1296
1302
|
for record in records
|
|
1297
1303
|
if candidate_matches_query(record.memory, query_value)
|
|
1298
1304
|
]
|
|
1299
|
-
|
|
1305
|
+
current = current_validities(snapshot) if include_history else {}
|
|
1306
|
+
candidates = [{**self._candidate_directory(record),
|
|
1307
|
+
**({"lifecycle": lifecycle(record, current)} if include_history else {})}
|
|
1308
|
+
for record in records]
|
|
1300
1309
|
fingerprint = self._search_fingerprint(
|
|
1301
1310
|
records,
|
|
1302
1311
|
query=query_value,
|
|
@@ -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`。行动及相关字段经过有界语义复核,提炼与复核共用每份工作的最多五次模型请求额度,成功即停止;复核失败不写入。来源时间用于解析相对日期,不补造事实日期。
|
|
@@ -135,6 +135,7 @@ src/memleaf/remember_route.py
|
|
|
135
135
|
src/memleaf/retention.py
|
|
136
136
|
src/memleaf/retrieval.py
|
|
137
137
|
src/memleaf/retrieval_gate.py
|
|
138
|
+
src/memleaf/retrieval_lifecycle.py
|
|
138
139
|
src/memleaf/runtime_retention.py
|
|
139
140
|
src/memleaf/scope_maintenance.py
|
|
140
141
|
src/memleaf/scope_state.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|