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