memleaf 1.0.1__tar.gz → 1.0.2__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-1.0.1 → memleaf-1.0.2}/CHANGELOG.md +24 -0
- {memleaf-1.0.1/src/memleaf.egg-info → memleaf-1.0.2}/PKG-INFO +2 -2
- {memleaf-1.0.1 → memleaf-1.0.2}/README.en.md +1 -1
- {memleaf-1.0.1 → memleaf-1.0.2}/README.md +1 -1
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/stability.md +16 -2
- {memleaf-1.0.1 → memleaf-1.0.2}/pyproject.toml +1 -1
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/__init__.py +1 -1
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/base.py +20 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/codex.py +6 -2
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/frontmatter.py +7 -3
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_events.py +5 -1
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2.py +91 -9
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_owner.py +10 -1
- {memleaf-1.0.1 → memleaf-1.0.2/src/memleaf.egg-info}/PKG-INFO +2 -2
- {memleaf-1.0.1 → memleaf-1.0.2}/LICENSE +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/MANIFEST.in +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/acceptance-evidence-guide.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/automatic-processing-route.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/body-compaction.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/candidate-reconciliation.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/capture-budget-design.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/config-migrations.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/core-refactor.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/development-closeout.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/evidence-retention.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/explicit-memory-update.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/extraction-latency.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/failed-run-recovery.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/general-processing.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/hermes-provider-compatibility.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/hermes-source-comparison-context.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/host-acceptance-runbook.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/host-v2-contract.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/immutable-release-assets.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-commit-contract.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-execution.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-native-comparison.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-partial-recovery.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-planner-preview.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-runtime-contract.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-scope-registration.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/incremental-selected-retention.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/installed-artifact-verification.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/memory-lifecycle-tools.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/memory-state-contract.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/migration-preflight.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/public-query-integrity.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/query-performance.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/remember-incremental-route.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/runtime-state-retention.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/semantic-extraction-protocol.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/source-work-contract.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/examples/README.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/examples/basic_usage.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/install.ps1 +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/install.sh +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/setup.cfg +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/__main__.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/admission.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/batch_review.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/body_preservation.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/budget.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/capture.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/cli.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/compaction.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/config.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/credentials.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/explicit_text_source.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/_provider.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/_shared.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/historical_context.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_retention.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_runtime.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_turn_identity.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_auth.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_cli.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_common.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/schemas/admin-records.schema.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/schemas/memleaf-host-v2.schema.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/schemas/read-integrity.schema.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/cancel_memory.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/forget_memory.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/memory_capabilities.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/memory_work.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/prepare_memory.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/resume_memory.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_contracts/tools/submit_memory.json +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_mcp.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_proposal.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/host_v2_schema.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/inbox.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_commit.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_dates.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_execution.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_explicit.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_failed_recovery.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_journal.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_merge.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_native.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_partial.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_preview.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_prompts.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_protocol.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_recovery.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_run_state.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_runtime.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_scopes.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_selection.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/incremental_semantics.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/index.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/inspection.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/installer.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/legacy_update_journal.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/lifecycle_maintenance.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/base.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/router.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/locking.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/mcp_server.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_commit.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_planner.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_retraction.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_update.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/memory_writer.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/migration.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/model_discovery.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/model_execution.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/models.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/native_index.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/native_registration.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/parallel_model.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/planning_candidates.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/planning_context.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_common.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_jobs.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_journal.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/process_owner.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/processing.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/processing_route.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/prompts.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/provenance.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/provider_compatibility.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/query_clock.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/query_progress.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/query_scan.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/receipt_codec.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/recording_policy.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/redaction.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/remember_route.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retention.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retrieval.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retrieval_gate.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/retrieval_lifecycle.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/runtime_retention.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/scope_state.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/semantic_maintenance.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/semantic_protocol.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/service.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/single_pass_memory_planner.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/single_pass_plan.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/source_policy.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/state_layout.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/summary_batch.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/turn_audit.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/turn_plan.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/update_review.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/validation.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf/vault.py +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/SOURCES.txt +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-1.0.1 → memleaf-1.0.2}/src/memleaf.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,30 @@ All notable changes to memleaf are documented here.
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
## 1.0.2 — 2026-10-08
|
|
9
|
+
|
|
10
|
+
### Reliable host recovery and Codex todo retrieval
|
|
11
|
+
|
|
12
|
+
- Freeze host write groups only after every member is serialized and validated.
|
|
13
|
+
Reject empty/incomplete recovery groups and impossible legacy saved receipts;
|
|
14
|
+
verify actual heads and history before settlement, including Owner accounting.
|
|
15
|
+
- Preserve multiline evidence in Markdown metadata through escaped quoted
|
|
16
|
+
strings, with lossless LF/CR/CRLF and Unicode line-separator round trips.
|
|
17
|
+
- Include `list_todos` in Codex retrieval Hooks and accept its current directory
|
|
18
|
+
fields. Reinstallation safely upgrades known older matchers with backup;
|
|
19
|
+
unrelated or shared third-party Hook groups are preserved.
|
|
20
|
+
- Validate the installed host workflow with multiline evidence in the existing
|
|
21
|
+
cross-platform wheel/source-distribution matrix. All 24 added regressions and
|
|
22
|
+
152 existing host checks passed locally, including real stdio/Hook subprocesses
|
|
23
|
+
and isolated installed-package checks. One additional historical assertion
|
|
24
|
+
failed identically on the original 1.0.1 baseline and remains outside this fix.
|
|
25
|
+
- No model call or retry was added. This patch used deterministic synthetic
|
|
26
|
+
acceptance; the earlier 1.0.1 macOS live-client evidence remains historical,
|
|
27
|
+
and no new real-model or production-Vault acceptance is claimed.
|
|
28
|
+
- Existing Codex model-profile installations need the installer run again to
|
|
29
|
+
update their known old Hook matchers, followed by the existing Hook review and
|
|
30
|
+
trust flow. Updating the Python package alone does not rewrite host settings.
|
|
31
|
+
|
|
8
32
|
## 1.0.1 — 2026-10-08
|
|
9
33
|
|
|
10
34
|
### Optional host-model MCP workflow for Codex
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A local-first Markdown memory core for AI agents
|
|
5
5
|
Author: memleaf contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -24,7 +24,7 @@ Dynamic: license-file
|
|
|
24
24
|
|
|
25
25
|
[English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
|
|
26
26
|
|
|
27
|
-
> **稳定版:1.0.
|
|
27
|
+
> **稳定版:1.0.2。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
|
|
28
28
|
> **Codex 可通过 `--profile host` 使用当前宿主模型完成记忆闭环,无需另配 Memleaf 模型 API Key。** 本地 Owner 仍需配置客户端身份、读写权限及来源信任;客户端主动调用工具,不代表每轮自动记忆。
|
|
29
29
|
> host profile 支持独立于内容类型的 `actionable` 和结构化 `waiting_on`,每份 work 默认最多三次被接受的 submit;Memleaf 负责确定性校验、落盘与恢复。
|
|
30
30
|
> 默认 `model` profile 和 Hermes 原生接入继续使用独立 Model Route,提炼与语义复核共用每份工作的最多五次模型请求。旧接口、现有记忆和默认入口保留,升级无需正式 Vault 迁移。Antigravity(反重力)当前不支持。
|
|
@@ -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
|
-
> **Stable release: 1.0.
|
|
7
|
+
> **Stable release: 1.0.2.** Hermes and Codex are supported; see the [stability policy](docs/stability.md) for compatibility scope.
|
|
8
8
|
> **Codex can complete the memory workflow with its current host model through `--profile host`, without a separate Memleaf model API key.** A local Owner still configures client identity, permissions and source trust. Clients invoke the tools deliberately; this does not enable automatic memory on every turn.
|
|
9
9
|
> The host profile supports `actionable` independently of content type and structured `waiting_on`, with at most three accepted submits per work by default. Memleaf validates, persists and recovers deterministically.
|
|
10
10
|
> The default `model` profile and native Hermes integration retain their independent Model Route and shared five-request extraction/review budget. Existing interfaces, memories and default entry points remain; no formal Vault migration is required. Antigravity is not supported.
|
|
@@ -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
|
-
> **稳定版:1.0.
|
|
7
|
+
> **稳定版:1.0.2。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
|
|
8
8
|
> **Codex 可通过 `--profile host` 使用当前宿主模型完成记忆闭环,无需另配 Memleaf 模型 API Key。** 本地 Owner 仍需配置客户端身份、读写权限及来源信任;客户端主动调用工具,不代表每轮自动记忆。
|
|
9
9
|
> host profile 支持独立于内容类型的 `actionable` 和结构化 `waiting_on`,每份 work 默认最多三次被接受的 submit;Memleaf 负责确定性校验、落盘与恢复。
|
|
10
10
|
> 默认 `model` profile 和 Hermes 原生接入继续使用独立 Model Route,提炼与语义复核共用每份工作的最多五次模型请求。旧接口、现有记忆和默认入口保留,升级无需正式 Vault 迁移。Antigravity(反重力)当前不支持。
|
|
@@ -30,15 +30,24 @@ scope. An internal protocol change may fence pending model responses and require
|
|
|
30
30
|
an explicit recovery action; it must not silently reinterpret saved work, erase
|
|
31
31
|
unresolved outcomes or reset consumed request budgets.
|
|
32
32
|
|
|
33
|
-
## Upgrade to 1.0.
|
|
33
|
+
## Upgrade to 1.0.2
|
|
34
34
|
|
|
35
|
-
The 1.0.
|
|
35
|
+
The 1.0.2 release preserves the 1.0.0 public interfaces, existing Model Routes,
|
|
36
36
|
default entry points and Markdown/history formats. The new host-model MCP route
|
|
37
37
|
is optional and selected with `--profile host`; upgrading does not convert an
|
|
38
38
|
existing Codex or Hermes connection to it. No formal Vault migration is required.
|
|
39
39
|
The host contract identifier remains `memleaf-host-v2.0-rc1`; protocol identity
|
|
40
40
|
and package release numbers are separate.
|
|
41
41
|
|
|
42
|
+
Version 1.0.2 fixes incomplete host freezing/recovery, multiline evidence
|
|
43
|
+
serialization and Codex todo retrieval Hooks. Complete older frozen groups
|
|
44
|
+
remain recoverable; incomplete groups and impossible saved receipts fail closed
|
|
45
|
+
without inventing writes or refunding processing budgets. Existing model-profile
|
|
46
|
+
Codex users should rerun the installer to update known old Hook matchers, then
|
|
47
|
+
follow Codex's Hook review/trust flow. Shared third-party Hook groups are preserved
|
|
48
|
+
and reported for explicit resolution. A package upgrade alone does not alter
|
|
49
|
+
host configuration.
|
|
50
|
+
|
|
42
51
|
The host route uses the running Agent model and does not require a separate
|
|
43
52
|
Memleaf model API key. A local Owner still supplies client credentials,
|
|
44
53
|
read/write scopes, permissions and acceptable source trust. `host-grant` emits
|
|
@@ -96,3 +105,8 @@ independent Memleaf model calls; host requests and token usage remain separate.
|
|
|
96
105
|
Corrections and explicit Owner-approved budgets were part of acceptance. This
|
|
97
106
|
evidence does not certify every model/client or live Windows/Linux model use;
|
|
98
107
|
native installation and package CI are distinct from real-model acceptance.
|
|
108
|
+
|
|
109
|
+
For 1.0.2, 24 new deterministic regressions and all 152 existing host checks
|
|
110
|
+
passed, including isolated installed-package and real stdio/Hook subprocess
|
|
111
|
+
checks. No new real Codex/Hermes model session or production-Vault migration was
|
|
112
|
+
performed for this patch; those scopes remain distinct from native package CI.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "1.0.
|
|
3
|
+
__version__ = "1.0.2"
|
|
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
|
|
@@ -592,6 +592,7 @@ def merge_hook_config(
|
|
|
592
592
|
*,
|
|
593
593
|
container_key: str | None = None,
|
|
594
594
|
dry_run: bool = False,
|
|
595
|
+
matcher_upgrades: Mapping[str, Mapping[str, str]] | None = None,
|
|
595
596
|
) -> HookMergeResult:
|
|
596
597
|
"""Merge command handlers into a host hook JSON file.
|
|
597
598
|
|
|
@@ -599,6 +600,8 @@ def merge_hook_config(
|
|
|
599
600
|
a top-level ``hooks`` object and passes ``container_key="hooks"``.
|
|
600
601
|
Existing handlers are left untouched; a matching memleaf command is
|
|
601
602
|
idempotent and a different memleaf command is treated as a conflict.
|
|
603
|
+
An adapter may explicitly migrate known old matchers on groups owned
|
|
604
|
+
entirely by the matching command; unrelated groups stay unchanged.
|
|
602
605
|
"""
|
|
603
606
|
|
|
604
607
|
target = Path(path)
|
|
@@ -659,6 +662,23 @@ def merge_hook_config(
|
|
|
659
662
|
command = requested_handler.get("command")
|
|
660
663
|
if not isinstance(command, str) or not command:
|
|
661
664
|
return HookMergeResult("diagnostic", "hook command is invalid; unchanged")
|
|
665
|
+
# Upgrade only an explicitly recognized old matcher belonging
|
|
666
|
+
# entirely to this command. Never rewrite a shared host group
|
|
667
|
+
# whose matcher also controls unrelated hooks.
|
|
668
|
+
upgrades = (matcher_upgrades or {}).get(event, {})
|
|
669
|
+
for index, item in enumerate(existing_items):
|
|
670
|
+
if not isinstance(item, Mapping) or not isinstance(item.get("matcher"), str):
|
|
671
|
+
continue
|
|
672
|
+
replacement = upgrades.get(item["matcher"])
|
|
673
|
+
hooks = item.get("hooks") if container_key == "hooks" else None
|
|
674
|
+
if replacement is None or not isinstance(hooks, list) or not hooks:
|
|
675
|
+
continue
|
|
676
|
+
if not any(isinstance(hook, Mapping) and hook.get("command") == command for hook in hooks):
|
|
677
|
+
continue
|
|
678
|
+
if not all(isinstance(hook, Mapping) and hook.get("command") == command for hook in hooks):
|
|
679
|
+
return HookMergeResult("diagnostic", "existing memleaf matcher shares unrelated hooks; unchanged")
|
|
680
|
+
existing_items[index] = {**item, "matcher": replacement}
|
|
681
|
+
updated = True
|
|
662
682
|
matching, conflict = _hook_command_state(existing_items, command, event, container_key)
|
|
663
683
|
if conflict:
|
|
664
684
|
return HookMergeResult("diagnostic", "existing memleaf hook conflicts; unchanged")
|
|
@@ -484,6 +484,10 @@ def _configure_codex_hooks(
|
|
|
484
484
|
definition,
|
|
485
485
|
container_key="hooks",
|
|
486
486
|
dry_run=dry_run,
|
|
487
|
+
matcher_upgrades={
|
|
488
|
+
"PreToolUse": {r"^mcp__memleaf__(search|read)$": r"^mcp__memleaf__(search|list_todos|read)$"},
|
|
489
|
+
"PostToolUse": {r"^mcp__memleaf__search$": r"^mcp__memleaf__(search|list_todos)$"},
|
|
490
|
+
},
|
|
487
491
|
)
|
|
488
492
|
|
|
489
493
|
|
|
@@ -513,13 +517,13 @@ def _codex_hook_definition(
|
|
|
513
517
|
"UserPromptSubmit": [{"hooks": [user_prompt]}],
|
|
514
518
|
"PreToolUse": [
|
|
515
519
|
{
|
|
516
|
-
"matcher": r"^mcp__memleaf__(search|read)$",
|
|
520
|
+
"matcher": r"^mcp__memleaf__(search|list_todos|read)$",
|
|
517
521
|
"hooks": [pre_tool],
|
|
518
522
|
}
|
|
519
523
|
],
|
|
520
524
|
"PostToolUse": [
|
|
521
525
|
{
|
|
522
|
-
"matcher": r"^
|
|
526
|
+
"matcher": r"^mcp__memleaf__(search|list_todos)$",
|
|
523
527
|
"hooks": [post_tool],
|
|
524
528
|
}
|
|
525
529
|
],
|
|
@@ -307,9 +307,13 @@ def _dump_scalar(value: Any) -> str:
|
|
|
307
307
|
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
308
308
|
return str(value)
|
|
309
309
|
if isinstance(value, str):
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
# JSON escapes retain line breaks in a single YAML quoted scalar.
|
|
311
|
+
# Escape the remaining Unicode line separators too: splitlines() is
|
|
312
|
+
# used by the restricted parser and must not split inside a scalar.
|
|
313
|
+
return (json.dumps(value, ensure_ascii=False)
|
|
314
|
+
.replace("\u0085", "\\u0085")
|
|
315
|
+
.replace("\u2028", "\\u2028")
|
|
316
|
+
.replace("\u2029", "\\u2029"))
|
|
313
317
|
raise FrontmatterError("unsupported value type")
|
|
314
318
|
|
|
315
319
|
|
|
@@ -403,7 +403,11 @@ def _codex_todo_result(value: Any) -> tuple[str, bool, str | None]:
|
|
|
403
403
|
return "error", False, None
|
|
404
404
|
if not all(
|
|
405
405
|
isinstance(item, Mapping)
|
|
406
|
-
|
|
406
|
+
# Directory rows also carry status, ownership, scope and history
|
|
407
|
+
# metadata. Only the identity/date fields are required to observe
|
|
408
|
+
# retrieval; rejecting those additive fields would mark real lists
|
|
409
|
+
# as errors and block the following bounded read.
|
|
410
|
+
and {"memory_id", "title", "due_date"}.issubset(item)
|
|
407
411
|
and isinstance(item.get("memory_id"), str) and bool(item.get("memory_id"))
|
|
408
412
|
and isinstance(item.get("title"), str) and bool(item.get("title"))
|
|
409
413
|
and (item.get("due_date") is None or isinstance(item.get("due_date"), str))
|
|
@@ -58,6 +58,18 @@ class HostMemory:
|
|
|
58
58
|
raise ValueError()
|
|
59
59
|
if any(not isinstance(payload[k], dict) for k in ("works", "sources", "requests", "cursors", "plans")):
|
|
60
60
|
raise ValueError()
|
|
61
|
+
# Reject impossible saved receipts left by older interrupted
|
|
62
|
+
# freezing, including cached replay of a false completion.
|
|
63
|
+
for work in payload["works"].values():
|
|
64
|
+
for entry in work["items"].values():
|
|
65
|
+
receipt = entry["receipt"]
|
|
66
|
+
if receipt["status"] == "saved":
|
|
67
|
+
ids = receipt["memory_ids"]
|
|
68
|
+
committed = receipt["committed_revisions"]
|
|
69
|
+
if (not ids or len(ids) != len(set(ids))
|
|
70
|
+
or [row["memory_id"] for row in committed] != ids
|
|
71
|
+
or receipt["applied"] is not True or receipt["settled"] is not True):
|
|
72
|
+
raise ValueError()
|
|
61
73
|
return payload
|
|
62
74
|
except (OSError, ValueError, KeyError, TypeError) as exc:
|
|
63
75
|
raise V2Error("STATE_CORRUPT", "Host work ledger cannot be verified") from exc
|
|
@@ -561,16 +573,20 @@ class HostMemory:
|
|
|
561
573
|
self.auth.check(context, "memory.maintain", work=work)
|
|
562
574
|
if item.get("update_kind") == "retract_memory" or any(p.get("op") == "retract" for p in item.get("body_patch", [])):
|
|
563
575
|
self.auth.check(context, "memory.retract", work=work)
|
|
564
|
-
|
|
576
|
+
frozen_entry = deepcopy(entry)
|
|
577
|
+
self._freeze(frozen_entry, plan, snapshot)
|
|
565
578
|
if item["action"] == "COMPACT":
|
|
566
579
|
approval_plan = plan.get("approval_plan")
|
|
567
580
|
if approval_plan is None:
|
|
568
581
|
approval_plan = {"target": item["target"], "revision": snapshot["targets"][item["target"]]["revision"],
|
|
569
582
|
"new_body": item["new_body"], "mapping": item["mapping"]}
|
|
570
583
|
self.auth.consume_approval(context, item["approval_ref"], "body_compaction", "sha256:" + digest(approval_plan), work_id=work["work_id"], locked=True)
|
|
584
|
+
entry.update(frozen_entry)
|
|
571
585
|
entry["removed_ranges"] = plan.get("removed_ranges", {})
|
|
572
586
|
proposals.append(entry)
|
|
573
|
-
except
|
|
587
|
+
except ValueError as exc:
|
|
588
|
+
if not isinstance(exc, V2Error):
|
|
589
|
+
exc = V2Error("INVALID_SCHEMA", "Memory cannot be serialized into a valid frozen group")
|
|
574
590
|
exc.budget_charged = True
|
|
575
591
|
receipt["error"] = exc.public()
|
|
576
592
|
# Reject implicit dependencies across independently submitted Items.
|
|
@@ -589,12 +605,15 @@ class HostMemory:
|
|
|
589
605
|
if receipt["action"] == "DEFERRED":
|
|
590
606
|
receipt["status"] = "deferred"
|
|
591
607
|
return
|
|
592
|
-
|
|
608
|
+
# Publish prepared only after every group member can be serialized and
|
|
609
|
+
# validated. A later member failure must leave no executable prefix.
|
|
610
|
+
operation_id = _id("op_")
|
|
611
|
+
operations = []
|
|
593
612
|
now = utc_now()
|
|
594
613
|
for change in plan["changes"]:
|
|
595
614
|
before = Memory.from_mapping(change["before"]) if change.get("before") else None
|
|
596
615
|
after = Memory.from_mapping(change["after"])
|
|
597
|
-
after.extra["incremental_operation_id"] =
|
|
616
|
+
after.extra["incremental_operation_id"] = operation_id
|
|
598
617
|
after.updated = now
|
|
599
618
|
if before:
|
|
600
619
|
after.created = before.created
|
|
@@ -604,15 +623,71 @@ class HostMemory:
|
|
|
604
623
|
"message_revision": source["revision"], "trust": source["trust"], "role": source["role"]}
|
|
605
624
|
if evidence not in after.sources:
|
|
606
625
|
after.sources.append(evidence)
|
|
607
|
-
|
|
608
|
-
"operation_id":
|
|
626
|
+
operations.append({"action": "UPDATE" if before else "CREATE", "memory_id": after.memory_id,
|
|
627
|
+
"operation_id": operation_id, "prepared_at": now,
|
|
609
628
|
"before": before.to_markdown() if before else None, "after": after.to_markdown(),
|
|
610
629
|
"expected_revision": revision(before).removeprefix("sha256:") if before else None,
|
|
611
630
|
"replacement_revision": revision(after).removeprefix("sha256:"), "applied": False})
|
|
612
|
-
|
|
631
|
+
frozen_receipt = {**receipt, "operation_id": operation_id,
|
|
632
|
+
"group_id": _id("group_") if operations else None,
|
|
633
|
+
"status": "prepared", "memory_ids": [c["memory_id"] for c in operations]}
|
|
613
634
|
if not plan["changes"]:
|
|
614
|
-
receipt["
|
|
615
|
-
|
|
635
|
+
if receipt["action"] not in {"NO_CHANGE", "NO_MEMORY"}:
|
|
636
|
+
raise V2Error("STATE_CORRUPT", "A write requires a complete frozen operation group")
|
|
637
|
+
frozen_receipt["memory_ids"] = [snapshot["targets"][r]["memory"]["memory_id"] for r in plan.get("target_refs", [])]
|
|
638
|
+
frozen_receipt.update(status="not_recorded" if receipt["action"] == "NO_MEMORY" else "unchanged", settled=True)
|
|
639
|
+
else:
|
|
640
|
+
self._validate_frozen({**entry, "receipt": frozen_receipt, "operations": operations}, snapshot)
|
|
641
|
+
entry.update(operations=operations, frozen_digest=self._frozen_digest(operations))
|
|
642
|
+
receipt.update(frozen_receipt)
|
|
643
|
+
|
|
644
|
+
@staticmethod
|
|
645
|
+
def _frozen_digest(operations: list[dict[str, Any]]) -> str:
|
|
646
|
+
# Application bookkeeping changes during recovery; frozen payloads do not.
|
|
647
|
+
fields = ("action", "memory_id", "operation_id", "prepared_at", "before", "after",
|
|
648
|
+
"expected_revision", "replacement_revision")
|
|
649
|
+
return digest([{key: operation[key] for key in fields} for operation in operations])
|
|
650
|
+
|
|
651
|
+
def _validate_frozen(self, entry: dict[str, Any], snapshot: dict[str, Any]) -> None:
|
|
652
|
+
"""Prove complete membership and payloads before any write or settlement.
|
|
653
|
+
|
|
654
|
+
Old complete groups have no digest but retain their full receipt IDs and
|
|
655
|
+
proposal participants. Old incomplete groups fail closed without repair
|
|
656
|
+
from an inferred/recompiled plan.
|
|
657
|
+
"""
|
|
658
|
+
try:
|
|
659
|
+
receipt, operations = entry["receipt"], entry["operations"]
|
|
660
|
+
ids = [op["memory_id"] for op in operations]
|
|
661
|
+
if not ids or len(ids) != len(set(ids)) or ids != receipt["memory_ids"]:
|
|
662
|
+
raise ValueError()
|
|
663
|
+
proposal = entry["proposal"]
|
|
664
|
+
action = receipt["action"]
|
|
665
|
+
if proposal["action"] != action or action not in {"CREATE", "UPDATE", "MERGE", "COMPACT"}:
|
|
666
|
+
raise ValueError()
|
|
667
|
+
if action == "CREATE":
|
|
668
|
+
if len(operations) != 1 or operations[0]["action"] != "CREATE":
|
|
669
|
+
raise ValueError()
|
|
670
|
+
else:
|
|
671
|
+
refs = [proposal["target"], *proposal.get("duplicates", [])]
|
|
672
|
+
expected = [snapshot["targets"][ref]["memory"]["memory_id"] for ref in refs]
|
|
673
|
+
if ids != expected or any(op["action"] != "UPDATE" for op in operations):
|
|
674
|
+
raise ValueError()
|
|
675
|
+
if "frozen_digest" in entry and entry["frozen_digest"] != self._frozen_digest(operations):
|
|
676
|
+
raise ValueError()
|
|
677
|
+
for op in operations:
|
|
678
|
+
after = Memory.from_markdown(op["after"])
|
|
679
|
+
before = Memory.from_markdown(op["before"]) if op["before"] is not None else None
|
|
680
|
+
if (op["operation_id"] != receipt["operation_id"]
|
|
681
|
+
or after.memory_id != op["memory_id"]
|
|
682
|
+
or after.extra.get("incremental_operation_id") != op["operation_id"]
|
|
683
|
+
or revision(after) != "sha256:" + op["replacement_revision"]
|
|
684
|
+
or (op["action"] == "CREATE" and (before is not None or op["expected_revision"] is not None))
|
|
685
|
+
or (op["action"] == "UPDATE" and (before is None or before.memory_id != after.memory_id
|
|
686
|
+
or revision(before) != "sha256:" + op["expected_revision"]))):
|
|
687
|
+
raise ValueError()
|
|
688
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
689
|
+
raise V2Error("RECOVERY_REQUIRED", "Frozen operation group is incomplete or invalid",
|
|
690
|
+
next_action="contact_owner") from exc
|
|
616
691
|
|
|
617
692
|
def _verify_source(self, state: dict[str, Any], work: dict[str, Any]) -> None:
|
|
618
693
|
for source in work["snapshot"]["sources"].values():
|
|
@@ -661,19 +736,26 @@ class HostMemory:
|
|
|
661
736
|
try:
|
|
662
737
|
if entry.get("deleted_dependency_ids"):
|
|
663
738
|
raise V2Error("RECOVERY_REQUIRED", "A deleted dependency requires an exact new owner disposition", next_action="contact_owner")
|
|
739
|
+
self._validate_frozen(entry, work["snapshot"])
|
|
664
740
|
self.auth.check(context, "memory.write", work=work)
|
|
665
741
|
# Guard every member before beginning a multi-file dependency group.
|
|
666
742
|
for op in entry["operations"]:
|
|
667
743
|
actual = self._applied(op)
|
|
668
744
|
if actual is None:
|
|
669
745
|
raise V2Error("REVISION_CONFLICT", retryable=True, next_action="contact_owner")
|
|
746
|
+
if actual is False and op.get("applied") is True:
|
|
747
|
+
raise V2Error("RECOVERY_REQUIRED", "Previously applied content is no longer present", next_action="contact_owner")
|
|
670
748
|
self.auth.check(context, "memory.write", scopes=Memory.from_markdown(op["after"]).scopes, work=work)
|
|
671
749
|
for op in entry["operations"]:
|
|
672
750
|
self.auth.check(context, "memory.write", work=work)
|
|
673
751
|
if self._applied(op) is not True:
|
|
674
752
|
writer.write_frozen_unlocked(op)
|
|
753
|
+
if self._applied(op) is not True:
|
|
754
|
+
raise V2Error("IO_INTERRUPTED", "Frozen write cannot be confirmed on disk", retryable=True, next_action="resume")
|
|
675
755
|
op["applied"] = True
|
|
676
756
|
self._save(state)
|
|
757
|
+
if not all(self._applied(op) is True for op in entry["operations"]):
|
|
758
|
+
raise V2Error("RECOVERY_REQUIRED", "Complete group persistence cannot be confirmed", next_action="contact_owner")
|
|
677
759
|
r.update(status="saved", applied=True, settled=True, error=None,
|
|
678
760
|
committed_revisions=[{"memory_id": o["memory_id"], "revision": "sha256:" + o["replacement_revision"]} for o in entry["operations"]])
|
|
679
761
|
for key, ranges in entry.get("removed_ranges", {}).items():
|
|
@@ -56,12 +56,19 @@ def account_work(host: Any, work_id: str) -> dict[str, Any]:
|
|
|
56
56
|
continue
|
|
57
57
|
operations = entry.get("operations", [])
|
|
58
58
|
actual = [host._applied(operation) for operation in operations]
|
|
59
|
+
complete = False
|
|
60
|
+
if operations and not entry.get("deleted_dependency_ids"):
|
|
61
|
+
try:
|
|
62
|
+
host._validate_frozen(entry, work["snapshot"])
|
|
63
|
+
complete = True
|
|
64
|
+
except V2Error:
|
|
65
|
+
pass
|
|
59
66
|
for operation, applied in zip(operations, actual):
|
|
60
67
|
# Keep a remembered application true even if a later external
|
|
61
68
|
# deletion makes the current CREATE head absent. Do not revive.
|
|
62
69
|
if applied is not False or not operation.get("applied"):
|
|
63
70
|
operation["applied"] = applied
|
|
64
|
-
if
|
|
71
|
+
if complete and all(applied is True for applied in actual):
|
|
65
72
|
receipt.update(status="saved", applied=True, settled=True, error=None,
|
|
66
73
|
committed_revisions=[{"memory_id": operation["memory_id"],
|
|
67
74
|
"revision": "sha256:" + operation["replacement_revision"]}
|
|
@@ -170,6 +177,8 @@ def _check_recovery_resources(host: Any, state: dict[str, Any], context: dict[st
|
|
|
170
177
|
for entry in work["items"].values():
|
|
171
178
|
if entry["receipt"]["status"] in _FINAL:
|
|
172
179
|
continue
|
|
180
|
+
if entry.get("operations"):
|
|
181
|
+
host._validate_frozen(entry, work["snapshot"])
|
|
173
182
|
if entry["receipt"]["action"] in {"MERGE", "COMPACT"}:
|
|
174
183
|
host.auth.check(context, "memory.maintain")
|
|
175
184
|
proposal = entry.get("proposal", {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A local-first Markdown memory core for AI agents
|
|
5
5
|
Author: memleaf contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -24,7 +24,7 @@ Dynamic: license-file
|
|
|
24
24
|
|
|
25
25
|
[English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
|
|
26
26
|
|
|
27
|
-
> **稳定版:1.0.
|
|
27
|
+
> **稳定版:1.0.2。** 支持 Hermes 和 Codex;版本与兼容范围见[稳定性约定](docs/stability.md)。
|
|
28
28
|
> **Codex 可通过 `--profile host` 使用当前宿主模型完成记忆闭环,无需另配 Memleaf 模型 API Key。** 本地 Owner 仍需配置客户端身份、读写权限及来源信任;客户端主动调用工具,不代表每轮自动记忆。
|
|
29
29
|
> host profile 支持独立于内容类型的 `actionable` 和结构化 `waiting_on`,每份 work 默认最多三次被接受的 submit;Memleaf 负责确定性校验、落盘与恢复。
|
|
30
30
|
> 默认 `model` profile 和 Hermes 原生接入继续使用独立 Model Route,提炼与语义复核共用每份工作的最多五次模型请求。旧接口、现有记忆和默认入口保留,升级无需正式 Vault 迁移。Antigravity(反重力)当前不支持。
|
|
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
|