memleaf 0.2.66__tar.gz → 0.2.68__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.66 → memleaf-0.2.68}/CHANGELOG.md +30 -0
- {memleaf-0.2.66/src/memleaf.egg-info → memleaf-0.2.68}/PKG-INFO +1 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/acceptance-evidence-guide.md +11 -10
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/automatic-processing-route.md +26 -26
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/migration-preflight.md +6 -5
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/remember-incremental-route.md +19 -19
- {memleaf-0.2.66 → memleaf-0.2.68}/pyproject.toml +1 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/cli.py +2 -2
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/config.py +8 -6
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/_provider.py +15 -3
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/_shared.py +20 -2
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_protocol.py +29 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/installer.py +71 -6
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/mcp_server.py +2 -2
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/migration.py +12 -5
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/process_jobs.py +18 -12
- memleaf-0.2.68/src/memleaf/processing.py +22 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/processing_route.py +31 -12
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/service.py +17 -46
- {memleaf-0.2.66 → memleaf-0.2.68/src/memleaf.egg-info}/PKG-INFO +1 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_a083_regressions.py +73 -73
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_a47_regressions.py +14 -8
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_artifact_verification.py +35 -6
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_control_absence_contract.py +12 -6
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_hermes_source_metadata.py +84 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_execution.py +7 -3
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_protocol.py +34 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_migration_backup.py +18 -1
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_processed_state_contract.py +15 -9
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_processing_entrypoints.py +23 -19
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_processing_route.py +25 -17
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_provider_compatibility.py +53 -2
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_remember_route.py +24 -19
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_source_followup_contract.py +60 -42
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/verify_distribution.py +70 -0
- memleaf-0.2.66/src/memleaf/processing.py +0 -535
- {memleaf-0.2.66 → memleaf-0.2.68}/LICENSE +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/MANIFEST.in +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/README.en.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/README.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/body-compaction.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/candidate-reconciliation.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/capture-budget-design.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/config-migrations.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/core-refactor.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/development-closeout.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/explicit-memory-update.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/extraction-latency.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/general-processing.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/hermes-provider-compatibility.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/host-acceptance-runbook.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/immutable-release-assets.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-commit-contract.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-execution.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-native-comparison.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-partial-recovery.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-planner-preview.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-runtime-contract.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-scope-registration.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/incremental-selected-retention.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/installed-artifact-verification.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/memory-state-contract.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/public-query-integrity.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/query-performance.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/runtime-state-retention.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/semantic-extraction-protocol.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/source-work-contract.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/examples/README.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/examples/basic_usage.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/examples/incremental_acceptance.json +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/examples/query_benchmark.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/install.ps1 +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/install.sh +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/setup.cfg +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/acceptance.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/adapters/base.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/batch_review.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/capture.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/compaction.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/explicit_text_source.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_commit.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_dates.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_execution.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_journal.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_native.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_partial.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_preview.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_prompts.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_run_state.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_runtime.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_scopes.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/incremental_selection.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/index.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/inspection.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/legacy_update_journal.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/memory_commit.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/memory_planner.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/memory_retraction.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/memory_update.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/model_execution.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/models.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/parallel_model.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/planning_context.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/process_common.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/process_journal.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/prompts.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/provider_compatibility.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/query_clock.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/query_progress.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/query_scan.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/receipt_codec.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/remember_route.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/retention.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/retrieval_gate.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/runtime_retention.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/semantic_maintenance.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/semantic_protocol.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/single_pass_memory_planner.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/single_pass_plan.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/source_policy.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/state_layout.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/summary_batch.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/update_review.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/validation.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf/vault.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf.egg-info/SOURCES.txt +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/src/memleaf.egg-info/top_level.txt +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/acceptance_oracles.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/candidate_update_fixtures.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/incremental_test_support.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/platform_smoke.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/release_assets.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/run_contracts.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_acceptance.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_acceptance_boundaries.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_body_compaction.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_candidate_reconciliation.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_contract_reporter.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_create_memory_contract.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_development_closeout.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_explicit_memory_update.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_explicit_update_contract.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_g1_memory_contract.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_g2_source_contract.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_hermes_native_provision.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_commit.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_configured_runtime.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_dates.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_execution_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_native.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_partial.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_partial_boundaries.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_partial_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_preview.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_remember.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_remember_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_scope_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_incremental_scopes.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_inspection_read_budget.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_mcp_retrieval_fallback.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_migration_runtime.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_native_read_stability.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_planner_provenance_contract.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_platform_contracts.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_processing_route_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_public_query_integrity.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_query_benchmark.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_query_clock.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_query_progress.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_query_scan_reuse.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_release_assets.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_remember_route_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_revision_target_integrity.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_runtime_retention.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_runtime_retention_observation.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_runtime_retention_recovery.py +0 -0
- {memleaf-0.2.66 → memleaf-0.2.68}/tests_public/test_vault_binding_contract.py +0 -0
|
@@ -4,6 +4,36 @@ All notable changes to memleaf are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.2.68 — 2026-09-20
|
|
8
|
+
|
|
9
|
+
### Hermes live-path hardening
|
|
10
|
+
|
|
11
|
+
- Observe Hermes deferred `tool_call` single-entry `calls[]` wrappers so real
|
|
12
|
+
memleaf search results are recorded as `NO_MATCH`/found instead of
|
|
13
|
+
`SEARCH_UNKNOWN`.
|
|
14
|
+
- Deterministically normalize only unambiguous flattened CREATE rows back into
|
|
15
|
+
the required `memory` object before the unchanged strict incremental validator;
|
|
16
|
+
malformed, incomplete and unknown-field variants remain fail-closed.
|
|
17
|
+
- Strip the trailing Hermes `<!--CLEAN-->` control marker at the Provider capture
|
|
18
|
+
boundary so it does not enter Inbox or memory extraction input.
|
|
19
|
+
- Validate the fixes with a real Hermes + DeepSeek Flash turn: retrieval audit
|
|
20
|
+
`NO_MATCH`, completed incremental extraction, three settled CREATE operations,
|
|
21
|
+
zero unresolved evidence and no CLEAN marker in Inbox.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## 0.2.67 — 2026-09-20
|
|
25
|
+
|
|
26
|
+
### Incremental-only processing and release integrity
|
|
27
|
+
|
|
28
|
+
- Remove the executable legacy processing/remember engine. New Vaults use the
|
|
29
|
+
incremental runner for automatic processing, explicit remember, MCP/CLI and
|
|
30
|
+
detached jobs; retained legacy settings/jobs remain inspectable but fail closed.
|
|
31
|
+
- Fix the v0.2.66 Hermes Provider manifest drift by aligning Core and Provider at
|
|
32
|
+
0.2.67 and rejecting an internally inconsistent package before host/Vault changes.
|
|
33
|
+
- Gate source and wheel/sdist verification on one version identity across package
|
|
34
|
+
metadata, Core `__version__`, Hermes `plugin.yaml` and sdist project metadata.
|
|
35
|
+
|
|
36
|
+
|
|
7
37
|
## 0.2.66 — 2026-09-20
|
|
8
38
|
|
|
9
39
|
### Reconcile divergent local candidates
|
|
@@ -24,16 +24,17 @@ the suite count or from module-name references.
|
|
|
24
24
|
|
|
25
25
|
## Current route semantics
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
failed receipts or request counters to
|
|
27
|
+
The v0.2.67 candidate has one processing engine: incremental. New Vaults default
|
|
28
|
+
both `process.automatic_pipeline` and `process.remember_pipeline` to
|
|
29
|
+
`incremental`. A retained `legacy` value from an older Vault is not executable;
|
|
30
|
+
migration preflight reports it as `legacy_pipeline_configuration`, and runtime
|
|
31
|
+
entry points reject it with `legacy_pipeline_removed`.
|
|
32
|
+
|
|
33
|
+
A successfully installed candidate is still not proof that production migration is
|
|
34
|
+
safe. Existing legacy queue/control records retain their identity for diagnosis and
|
|
35
|
+
must be settled or explicitly reviewed; they are not reinterpreted as incremental
|
|
36
|
+
work. Do not erase old work, failed receipts or request counters to obtain a clean
|
|
37
|
+
preflight result.
|
|
37
38
|
|
|
38
39
|
## Preparation with no paid calls
|
|
39
40
|
|
|
@@ -1,31 +1,29 @@
|
|
|
1
|
-
# Automatic processing
|
|
1
|
+
# Automatic processing on the incremental engine
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
`process_incremental` and selected `remember_incremental` remain
|
|
7
|
-
|
|
3
|
+
As of the v0.2.67 candidate, `Memleaf.process()`, CLI `process`, MCP `process`
|
|
4
|
+
and the detached job worker all execute through the existing incremental runner.
|
|
5
|
+
There is no executable legacy processing engine and no runtime fallback to it.
|
|
6
|
+
`process_incremental` and selected `remember_incremental` remain facades on the
|
|
7
|
+
same runner, request budget and commit journal.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
selection; see `remember-incremental-route.md`. Switching automatic processing
|
|
14
|
-
alone does not enable the text route. Both use the same incremental runner.
|
|
9
|
+
New Vaults default to incremental processing. Existing Vaults that still contain a
|
|
10
|
+
`legacy` pipeline setting remain readable for migration inspection, but processing
|
|
11
|
+
fails closed with `legacy_pipeline_removed` until the operator explicitly updates
|
|
12
|
+
the configuration after the required stop/backup/preflight procedure.
|
|
15
13
|
|
|
16
|
-
##
|
|
14
|
+
## One engine, compatibility selector only
|
|
17
15
|
|
|
18
|
-
The
|
|
16
|
+
The configuration field is retained for migration visibility:
|
|
19
17
|
|
|
20
18
|
```yaml
|
|
21
19
|
process:
|
|
22
|
-
automatic_pipeline:
|
|
20
|
+
automatic_pipeline: incremental
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
A missing field
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
A missing field resolves to incremental. `incremental` is the only executable
|
|
24
|
+
value. A retained `legacy` value is a migration blocker; unknown names, nonstrings
|
|
25
|
+
and malformed configuration fail explicitly. Nothing silently reinterprets an old
|
|
26
|
+
legacy queue or configuration as new incremental work.
|
|
29
27
|
|
|
30
28
|
The optional per-call override affects that call only:
|
|
31
29
|
|
|
@@ -134,9 +132,11 @@ concurrent planners and stale responses.
|
|
|
134
132
|
|
|
135
133
|
## Queue binding and cutover
|
|
136
134
|
|
|
137
|
-
An accepted job binds
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
An accepted job binds the incremental engine, normalized scope and recover flag.
|
|
136
|
+
A same-session request with different controls cannot silently coalesce into it.
|
|
137
|
+
Historical queue records that are missing the incremental route, or explicitly
|
|
138
|
+
name `legacy`, are retained for diagnosis but blocked with
|
|
139
|
+
`legacy_pipeline_removed`; they are never executed by a compatibility engine.
|
|
140
140
|
Malformed stored controls fail validation without resetting the queue.
|
|
141
141
|
|
|
142
142
|
A waiting job refuses execution when the configured default has changed since its
|
|
@@ -185,8 +185,8 @@ model configuration absent. Queue errors do not become successful NO_MEMORY.
|
|
|
185
185
|
|
|
186
186
|
## Validation and outstanding work
|
|
187
187
|
|
|
188
|
-
Public tests cover
|
|
189
|
-
batch caps, redacted ID selection, CLI/MCP/queue controls,
|
|
188
|
+
Public tests cover incremental defaults, explicit rejection of legacy execution,
|
|
189
|
+
exact scope, source ordering, batch caps, redacted ID selection, CLI/MCP/queue controls,
|
|
190
190
|
old queue compatibility, safe summaries, native NO_CHANGE, index/cleanup failures,
|
|
191
191
|
source/target edits, Forget, concurrent dispatch, and a real child process exiting
|
|
192
192
|
after its saved response. All Vaults and model responses in tests are synthetic.
|
|
@@ -207,8 +207,8 @@ Native installed-artifact results must come from the exact candidate's CI
|
|
|
207
207
|
reports, not from a phase document or a prior green build. A native package test
|
|
208
208
|
with a host-interface stub is not actual host acceptance.
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
Automatic processing and text remember both use the incremental engine by default.
|
|
211
|
+
`--dry-run` may still call the configured model;
|
|
212
212
|
acceptance plan-only mode is the no-model preparation path. See
|
|
213
213
|
[the acceptance evidence guide](acceptance-evidence-guide.md) for the remaining
|
|
214
214
|
gates. The fixed 1,861-codepoint/3,587-byte main prompt is unchanged.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Migration preflight and private local backup (G4e)
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
contract (FS178-179, FS188, FS192 and FS196). It
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
This command implements the preparation portion of the controlled migration
|
|
4
|
+
contract (FS178-179, FS188, FS192 and FS196). It does not stop processes, rewrite
|
|
5
|
+
business records, reset budgets, restore a Vault, or rewrite pipeline settings.
|
|
6
|
+
The v0.2.67 candidate itself has only the incremental engine; a retained
|
|
7
|
+
`legacy` setting is reported as `legacy_pipeline_configuration` and must be
|
|
8
|
+
handled explicitly after writers are stopped and a verified backup exists.
|
|
8
9
|
|
|
9
10
|
## Inspect before changing anything
|
|
10
11
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Text remember on the incremental engine
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
incremental execution. This
|
|
5
|
-
budget, native comparator, compiler and shared
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
As of the v0.2.67 candidate, `Memleaf.remember()` and the MCP `remember` tool use
|
|
4
|
+
incremental execution as their only engine. This remains an adapter to the same
|
|
5
|
+
selected-retention runner, budget, native comparator, compiler and shared
|
|
6
|
+
MemoryWriter; it does not create a second planner. There is no executable legacy
|
|
7
|
+
remember path and no fallback to it.
|
|
8
8
|
|
|
9
9
|
## Selection and compatibility
|
|
10
10
|
|
|
@@ -26,13 +26,13 @@ if result["retry_available"]:
|
|
|
26
26
|
)
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
The
|
|
30
|
-
`
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
The retained `process.remember_pipeline` field defaults to `incremental`.
|
|
30
|
+
`incremental` is the only executable value; an old `legacy` value is preserved
|
|
31
|
+
for migration inspection but is rejected with `legacy_pipeline_removed`. The
|
|
32
|
+
per-call `pipeline` argument is now only a compatibility selector for
|
|
33
|
+
`incremental`. Unknown values fail explicitly. The MCP tool exposes only
|
|
34
|
+
`incremental` plus the existing `recover` and `source_time` controls. A
|
|
35
|
+
supplied model/router still must meet the existing single-dispatch contract.
|
|
36
36
|
|
|
37
37
|
`content` and its `text` alias contain the actual submitted user material. Supplying
|
|
38
38
|
both with different values is rejected on the incremental route. One text request
|
|
@@ -145,12 +145,12 @@ consent or report a damaged state as NO_MEMORY.
|
|
|
145
145
|
|
|
146
146
|
## Cutover and validation
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
live acceptance. Older
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
Production migration still requires controlled stop/backup/upgrade/restart and
|
|
149
|
+
live acceptance. Older binaries must be stopped before changing a retained legacy
|
|
150
|
+
configuration; changing a config key does not kill an old process or make
|
|
151
|
+
mixed-version writes safe. This candidate changes the package default to
|
|
152
|
+
incremental but does not itself mutate a production Vault, run a real model, or
|
|
153
|
+
prove native Hermes lifecycle acceptance.
|
|
154
154
|
|
|
155
155
|
Deterministic tests cover one-user projection, origin provenance, same/new intents,
|
|
156
156
|
legacy-route collisions, explicit and configured selection, unchanged originating
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.68"
|
|
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
|
|
@@ -94,8 +94,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
94
94
|
process.add_argument("--source", default=None)
|
|
95
95
|
process.add_argument("--session-id", default=None)
|
|
96
96
|
process.add_argument("--scope", default=None)
|
|
97
|
-
process.add_argument("--pipeline", choices=("
|
|
98
|
-
help="
|
|
97
|
+
process.add_argument("--pipeline", choices=("incremental",), default=None,
|
|
98
|
+
help="compatibility override; incremental is the only processing engine")
|
|
99
99
|
process.add_argument("--recover", action="store_true",
|
|
100
100
|
help="allow remaining incremental transport recovery, not partial replan")
|
|
101
101
|
process.add_argument("--dry-run", action="store_true", help="no source Vault writes; may call the configured model")
|
|
@@ -89,8 +89,8 @@ DEFAULT_CONFIG: dict[str, Any] = {
|
|
|
89
89
|
"memory_compact_threshold_tokens": 100000,
|
|
90
90
|
"memory_compact_candidate_ratio": 0.30,
|
|
91
91
|
"inbox_cleanup_hours": 24,
|
|
92
|
-
"automatic_pipeline": "
|
|
93
|
-
"remember_pipeline": "
|
|
92
|
+
"automatic_pipeline": "incremental",
|
|
93
|
+
"remember_pipeline": "incremental",
|
|
94
94
|
# Compatibility key: closed todo heads remain current identities.
|
|
95
95
|
"closed_todo_retention_days": 30,
|
|
96
96
|
"model_concurrency": DEFAULT_MODEL_CONCURRENCY,
|
|
@@ -225,10 +225,12 @@ def load_config(path: Path | str, *, vault: Path | str | None = None) -> dict[st
|
|
|
225
225
|
cleanup_hours = process.get("inbox_cleanup_hours") if isinstance(process, Mapping) else None
|
|
226
226
|
if type(cleanup_hours) is not int or cleanup_hours < 0:
|
|
227
227
|
raise ValueError("invalid memleaf process.inbox_cleanup_hours")
|
|
228
|
-
|
|
228
|
+
# Legacy values remain parseable only so old Vaults can be inspected and migrated.
|
|
229
|
+
# Runtime selection rejects them; new/default configurations are incremental-only.
|
|
230
|
+
automatic_pipeline = process.get("automatic_pipeline", "incremental")
|
|
229
231
|
if not isinstance(automatic_pipeline, str) or automatic_pipeline not in {"legacy", "incremental"}:
|
|
230
232
|
raise ValueError("invalid memleaf process.automatic_pipeline")
|
|
231
|
-
remember_pipeline = process.get("remember_pipeline", "
|
|
233
|
+
remember_pipeline = process.get("remember_pipeline", "incremental")
|
|
232
234
|
if not isinstance(remember_pipeline, str) or remember_pipeline not in {"legacy", "incremental"}:
|
|
233
235
|
raise ValueError("invalid memleaf process.remember_pipeline")
|
|
234
236
|
closed_todo_days = process.get("closed_todo_retention_days") if isinstance(process, Mapping) else None
|
|
@@ -296,11 +298,11 @@ def save_config(path: Path | str, config: Mapping[str, Any]) -> None:
|
|
|
296
298
|
if not isinstance(process, Mapping):
|
|
297
299
|
raise ValueError("invalid memleaf process settings")
|
|
298
300
|
normalized_process = dict(process)
|
|
299
|
-
pipeline = normalized_process.get("automatic_pipeline", "
|
|
301
|
+
pipeline = normalized_process.get("automatic_pipeline", "incremental")
|
|
300
302
|
if not isinstance(pipeline, str) or pipeline not in {"legacy", "incremental"}:
|
|
301
303
|
raise ValueError("invalid memleaf process.automatic_pipeline")
|
|
302
304
|
normalized_process["automatic_pipeline"] = pipeline
|
|
303
|
-
remember_pipeline = normalized_process.get("remember_pipeline", "
|
|
305
|
+
remember_pipeline = normalized_process.get("remember_pipeline", "incremental")
|
|
304
306
|
if not isinstance(remember_pipeline, str) or remember_pipeline not in {"legacy", "incremental"}:
|
|
305
307
|
raise ValueError("invalid memleaf process.remember_pipeline")
|
|
306
308
|
normalized_process["remember_pipeline"] = remember_pipeline
|
|
@@ -19,6 +19,16 @@ except (ImportError, ValueError):
|
|
|
19
19
|
_spec.loader.exec_module(_module)
|
|
20
20
|
globals().update({name: getattr(_module, name) for name in getattr(_module, "__all__", ())})
|
|
21
21
|
|
|
22
|
+
def _capture_assistant_text(value: str) -> str:
|
|
23
|
+
"""Remove Hermes' terminal internal cleanliness marker from memory input."""
|
|
24
|
+
import re
|
|
25
|
+
|
|
26
|
+
if not isinstance(value, str):
|
|
27
|
+
return value
|
|
28
|
+
return re.sub(r"(?:\r?\n)?\s*<!--\s*CLEAN\s*-->\s*$", "", value,
|
|
29
|
+
flags=re.IGNORECASE)
|
|
30
|
+
|
|
31
|
+
|
|
22
32
|
def _sync_source_metadata(messages: Any, user_content: str, assistant_content: str) -> dict[str, dict[str, Any]]:
|
|
23
33
|
"""Read optional host metadata only from the exact visible tail pair.
|
|
24
34
|
|
|
@@ -1618,9 +1628,11 @@ class MemleafMemoryProvider(MemoryProvider):
|
|
|
1618
1628
|
# Never capture the raw message list as business conversation content.
|
|
1619
1629
|
if not self._write_enabled or self._client is None:
|
|
1620
1630
|
return
|
|
1631
|
+
raw_assistant_content = assistant_content
|
|
1632
|
+
captured_assistant_content = _capture_assistant_text(assistant_content)
|
|
1621
1633
|
visible_events = [
|
|
1622
1634
|
("user", user_content),
|
|
1623
|
-
("assistant",
|
|
1635
|
+
("assistant", captured_assistant_content),
|
|
1624
1636
|
]
|
|
1625
1637
|
if any(not isinstance(content, str) or not content.strip() for _, content in visible_events):
|
|
1626
1638
|
return
|
|
@@ -1640,9 +1652,9 @@ class MemleafMemoryProvider(MemoryProvider):
|
|
|
1640
1652
|
effective_session,
|
|
1641
1653
|
resolved_turn_number,
|
|
1642
1654
|
user_content,
|
|
1643
|
-
|
|
1655
|
+
captured_assistant_content,
|
|
1644
1656
|
)
|
|
1645
|
-
source_metadata = _sync_source_metadata(messages, user_content,
|
|
1657
|
+
source_metadata = _sync_source_metadata(messages, user_content, raw_assistant_content)
|
|
1646
1658
|
user_source_id = source_metadata.get("user", {}).get("message_id")
|
|
1647
1659
|
if user_source_id:
|
|
1648
1660
|
# Stable host identity survives a revised body/final reply.
|
|
@@ -568,8 +568,26 @@ def _tool_call_parts(value: Any) -> Optional[dict[str, Any]]:
|
|
|
568
568
|
wrapper = _decode_tool_value(arguments)
|
|
569
569
|
if not isinstance(wrapper, Mapping):
|
|
570
570
|
return None
|
|
571
|
-
|
|
572
|
-
|
|
571
|
+
# Hermes' deferred-tool bridge advertises a batch envelope even for one
|
|
572
|
+
# local tool: {"calls": [{"name": ..., "arguments": ...}]}. Local
|
|
573
|
+
# deferred tools are one entry per outer tool_call, so only unwrap a
|
|
574
|
+
# single-entry batch. Multi-entry wrapper results cannot be correlated
|
|
575
|
+
# safely to one inner call and therefore remain diagnostic-unknown.
|
|
576
|
+
raw_calls = _decode_tool_value(wrapper.get("calls")) if "calls" in wrapper else None
|
|
577
|
+
if raw_calls is not None:
|
|
578
|
+
if isinstance(raw_calls, Mapping):
|
|
579
|
+
raw_calls = [raw_calls]
|
|
580
|
+
if not isinstance(raw_calls, list) or len(raw_calls) != 1 or not isinstance(raw_calls[0], Mapping):
|
|
581
|
+
return None
|
|
582
|
+
inner = raw_calls[0]
|
|
583
|
+
name = inner.get("name")
|
|
584
|
+
arguments = inner.get("arguments")
|
|
585
|
+
inner_id = inner.get("id") or inner.get("call_id")
|
|
586
|
+
if isinstance(inner_id, str) and inner_id:
|
|
587
|
+
call_id = inner_id
|
|
588
|
+
else:
|
|
589
|
+
name = wrapper.get("name")
|
|
590
|
+
arguments = wrapper.get("arguments")
|
|
573
591
|
if not isinstance(name, str):
|
|
574
592
|
return None
|
|
575
593
|
arguments = _decode_tool_value(arguments)
|
|
@@ -36,6 +36,30 @@ _BRANCHES = {
|
|
|
36
36
|
"NO_MEMORY": (set(), set()),
|
|
37
37
|
"DEFERRED": ({"reason", "need"}, {"reason", "need"}),
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
_CREATE_ROW_META = frozenset(("action", "evidence", "at", "effective"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _normalize_flat_create_row(row: Any) -> tuple[Any, bool]:
|
|
44
|
+
"""Repair one unambiguous model-only CREATE wrapper mistake.
|
|
45
|
+
|
|
46
|
+
This is structural normalization only: it never invents fields, changes
|
|
47
|
+
values, widens allowed keys, or bypasses the strict CREATE validator.
|
|
48
|
+
"""
|
|
49
|
+
if not isinstance(row, dict) or "memory" in row:
|
|
50
|
+
return row, False
|
|
51
|
+
action = row.get("action")
|
|
52
|
+
if not isinstance(action, str) or action.strip().upper() != "CREATE":
|
|
53
|
+
return row, False
|
|
54
|
+
memory_keys = set(row) & set(_CREATE)
|
|
55
|
+
required = {"type", "scope", "title", "body"}
|
|
56
|
+
if not required <= memory_keys:
|
|
57
|
+
return row, False
|
|
58
|
+
if set(row) - (_CREATE_ROW_META | set(_CREATE)):
|
|
59
|
+
return row, False
|
|
60
|
+
normalized = {key: deepcopy(value) for key, value in row.items() if key not in _CREATE}
|
|
61
|
+
normalized["memory"] = {key: deepcopy(row[key]) for key in row if key in _CREATE}
|
|
62
|
+
return normalized, True
|
|
39
63
|
_GROUP = {"status": "status", "validity": "validity", "scopes": "scope",
|
|
40
64
|
"assignee": "responsibility", "waiting_on": "responsibility",
|
|
41
65
|
"deadline": "deadline", "title": "content", "body": "content"}
|
|
@@ -553,7 +577,11 @@ def compile_incremental(raw: str, snapshot: PlanningSnapshot) -> dict[str, Any]:
|
|
|
553
577
|
issues.append({"row": index, "code": code, "evidence": list(dict.fromkeys(refs))})
|
|
554
578
|
for index, row in enumerate(value["items"]):
|
|
555
579
|
try:
|
|
556
|
-
|
|
580
|
+
normalized_row, normalized = _normalize_flat_create_row(row)
|
|
581
|
+
parsed_row = _parse_row(normalized_row, state)
|
|
582
|
+
if normalized:
|
|
583
|
+
parsed_row.setdefault("warnings", []).append("create_memory_wrapper_normalized")
|
|
584
|
+
parsed.append((index, parsed_row))
|
|
557
585
|
except (ValueError, TypeError) as error:
|
|
558
586
|
problem(index, str(error) if isinstance(error, ValueError) else "invalid_fields", row)
|
|
559
587
|
if isinstance(row, dict) and isinstance(row.get("target"), str) and row["target"].strip() in state["targets"]:
|
|
@@ -902,6 +902,44 @@ _MODEL_ROUTE_INCOMPATIBLE_ACTION = (
|
|
|
902
902
|
)
|
|
903
903
|
|
|
904
904
|
|
|
905
|
+
_PIPELINE_MIGRATION_REQUIRED_ACTION = (
|
|
906
|
+
"This Vault still selects the removed legacy processing engine. Stop all supported "
|
|
907
|
+
"writers, run migration-check, create and verify a migration backup, then set both "
|
|
908
|
+
"process.automatic_pipeline and process.remember_pipeline to incremental before "
|
|
909
|
+
"relying on automatic extraction or remember."
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
_PIPELINE_CONFIGURATION_INVALID_ACTION = (
|
|
913
|
+
"Repair the Vault process configuration before relying on memory processing. "
|
|
914
|
+
"v0.2.67 supports only the incremental execution engine."
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def _pipeline_route_outcome(vault: Any) -> tuple[bool, str, str | None]:
|
|
919
|
+
"""Report whether this Vault can execute the sole incremental engine."""
|
|
920
|
+
try:
|
|
921
|
+
config = vault.config()
|
|
922
|
+
except Exception:
|
|
923
|
+
return False, "pipeline_configuration_invalid", _PIPELINE_CONFIGURATION_INVALID_ACTION
|
|
924
|
+
process = config.get("process") if isinstance(config, Mapping) else None
|
|
925
|
+
if not isinstance(process, Mapping):
|
|
926
|
+
return False, "pipeline_configuration_invalid", _PIPELINE_CONFIGURATION_INVALID_ACTION
|
|
927
|
+
values = (
|
|
928
|
+
process.get("automatic_pipeline", "incremental"),
|
|
929
|
+
process.get("remember_pipeline", "incremental"),
|
|
930
|
+
)
|
|
931
|
+
if any(value == "legacy" for value in values):
|
|
932
|
+
return False, "pipeline_migration_required", _PIPELINE_MIGRATION_REQUIRED_ACTION
|
|
933
|
+
if any(value != "incremental" for value in values):
|
|
934
|
+
return False, "pipeline_configuration_invalid", _PIPELINE_CONFIGURATION_INVALID_ACTION
|
|
935
|
+
return True, "ready", None
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def _join_user_actions(*actions: str | None) -> str | None:
|
|
939
|
+
values = [action.strip() for action in actions if isinstance(action, str) and action.strip()]
|
|
940
|
+
return " ".join(values) if values else None
|
|
941
|
+
|
|
942
|
+
|
|
905
943
|
def _route_can_extract(vault: Any) -> bool:
|
|
906
944
|
"""Ask the production router whether this Vault's route can speak B3.
|
|
907
945
|
|
|
@@ -965,6 +1003,22 @@ def install_hermes(
|
|
|
965
1003
|
if mcp_runtime not in _MCP_RUNTIME_POLICIES:
|
|
966
1004
|
raise ValueError(f"unsupported Hermes MCP runtime policy: {mcp_runtime}")
|
|
967
1005
|
|
|
1006
|
+
# Reject an internally inconsistent package before reading or changing any
|
|
1007
|
+
# Hermes/Vault state. The post-copy check below remains as defense in depth.
|
|
1008
|
+
packaged_provider = Path(__file__).with_name("hermes_provider")
|
|
1009
|
+
packaged_provider_version = _provider_manifest_version(packaged_provider)
|
|
1010
|
+
if packaged_provider_version != core_version:
|
|
1011
|
+
return _failure_result(
|
|
1012
|
+
stage="provider_version",
|
|
1013
|
+
reason=(
|
|
1014
|
+
"packaged Hermes provider version mismatch: "
|
|
1015
|
+
f"core={core_version}, provider={packaged_provider_version or 'unknown'}"
|
|
1016
|
+
),
|
|
1017
|
+
core_version=core_version,
|
|
1018
|
+
provider_version=packaged_provider_version,
|
|
1019
|
+
vault=None,
|
|
1020
|
+
)
|
|
1021
|
+
|
|
968
1022
|
home = _home_from_environment()
|
|
969
1023
|
hermes_home = _hermes_home(home)
|
|
970
1024
|
try:
|
|
@@ -1082,11 +1136,16 @@ def install_hermes(
|
|
|
1082
1136
|
# here used to leave core and provider versions out of step. A *present*
|
|
1083
1137
|
# route that cannot speak B3 is reported separately, because advertising
|
|
1084
1138
|
# ``ready`` for it would promise extraction that can never run.
|
|
1085
|
-
model_ready,
|
|
1139
|
+
model_ready, model_processing_status, model_action = _model_route_outcome(
|
|
1086
1140
|
model, can_extract=_route_can_extract(vault)
|
|
1087
1141
|
)
|
|
1088
|
-
|
|
1089
|
-
|
|
1142
|
+
pipeline_ready, pipeline_processing_status, pipeline_action = _pipeline_route_outcome(vault)
|
|
1143
|
+
processing_status = (
|
|
1144
|
+
model_processing_status if pipeline_ready else pipeline_processing_status
|
|
1145
|
+
)
|
|
1146
|
+
install_action = _join_user_actions(pipeline_action, model_action)
|
|
1147
|
+
# Host integration is configured even when processing still needs an explicit
|
|
1148
|
+
# model route or a controlled legacy->incremental Vault migration.
|
|
1090
1149
|
|
|
1091
1150
|
provider_target = hermes_home / "plugins" / "memleaf"
|
|
1092
1151
|
provider_config = hermes_home / "memleaf.json"
|
|
@@ -1298,8 +1357,8 @@ def install_hermes(
|
|
|
1298
1357
|
"native_sources": native_registration,
|
|
1299
1358
|
"capture": capture_policy_status(vault.config()),
|
|
1300
1359
|
"processing_status": processing_status,
|
|
1301
|
-
"user_action_required": bool(
|
|
1302
|
-
"user_action":
|
|
1360
|
+
"user_action_required": bool(install_action),
|
|
1361
|
+
"user_action": install_action,
|
|
1303
1362
|
}
|
|
1304
1363
|
|
|
1305
1364
|
|
|
@@ -1357,9 +1416,12 @@ def install_codex(*, vault_path: Path | None = None) -> dict[str, Any]:
|
|
|
1357
1416
|
"codex": configured.to_dict(),
|
|
1358
1417
|
}
|
|
1359
1418
|
model_ready = model.get("status") in {"configured", "already_configured"}
|
|
1419
|
+
pipeline_ready, pipeline_processing_status, pipeline_action = _pipeline_route_outcome(vault)
|
|
1360
1420
|
actions: list[str] = []
|
|
1361
1421
|
if configured.user_action_required and configured.user_action:
|
|
1362
1422
|
actions.append(configured.user_action)
|
|
1423
|
+
if pipeline_action:
|
|
1424
|
+
actions.append(pipeline_action)
|
|
1363
1425
|
if not model_ready:
|
|
1364
1426
|
actions.append(
|
|
1365
1427
|
"Configure an independent memleaf Model Route for this Vault before relying on "
|
|
@@ -1372,7 +1434,10 @@ def install_codex(*, vault_path: Path | None = None) -> dict[str, Any]:
|
|
|
1372
1434
|
"vault": str(vault.root),
|
|
1373
1435
|
"vault_source": vault_source,
|
|
1374
1436
|
"model": model,
|
|
1375
|
-
"processing_status":
|
|
1437
|
+
"processing_status": (
|
|
1438
|
+
("ready" if model_ready else "model_route_required")
|
|
1439
|
+
if pipeline_ready else pipeline_processing_status
|
|
1440
|
+
),
|
|
1376
1441
|
"codex": configured.to_dict(),
|
|
1377
1442
|
"user_action_required": bool(actions),
|
|
1378
1443
|
"user_action": " ".join(actions) if actions else None,
|
|
@@ -303,7 +303,7 @@ _TOOLS: tuple[dict[str, Any], ...] = (
|
|
|
303
303
|
"session_id": {"type": "string"},
|
|
304
304
|
"scope": _text_or_texts_schema(),
|
|
305
305
|
"background": {"type": "boolean"},
|
|
306
|
-
"pipeline": {"type": "string", "enum": ["
|
|
306
|
+
"pipeline": {"type": "string", "enum": ["incremental"]},
|
|
307
307
|
"recover": {"type": "boolean"},
|
|
308
308
|
}
|
|
309
309
|
),
|
|
@@ -332,7 +332,7 @@ _TOOLS: tuple[dict[str, Any], ...] = (
|
|
|
332
332
|
"event_id": {"type": "string"},
|
|
333
333
|
"intent_id": {"type": "string"},
|
|
334
334
|
"scopes": _text_or_texts_schema(),
|
|
335
|
-
"pipeline": {"type": "string", "enum": ["
|
|
335
|
+
"pipeline": {"type": "string", "enum": ["incremental"]},
|
|
336
336
|
"recover": {"type": "boolean"},
|
|
337
337
|
"source_time": {"type": "string"},
|
|
338
338
|
},
|
|
@@ -12,7 +12,7 @@ import os
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
import platform
|
|
14
14
|
import re
|
|
15
|
-
from typing import Any
|
|
15
|
+
from typing import Any, Mapping
|
|
16
16
|
|
|
17
17
|
from . import __version__
|
|
18
18
|
from .inspection import _checked_snapshot, _snapshot, _fingerprint, existing_root
|
|
@@ -94,6 +94,7 @@ def _checks(service: Any, snapshot: dict[str, bytes]) -> dict:
|
|
|
94
94
|
warnings: set[str] = set()
|
|
95
95
|
counts: dict[str, int] = {}
|
|
96
96
|
config = None
|
|
97
|
+
configured: dict[str, Any] = {}
|
|
97
98
|
binding = {"status": "unknown"}
|
|
98
99
|
try:
|
|
99
100
|
binding = service.vault.identity_status()
|
|
@@ -106,6 +107,14 @@ def _checks(service: Any, snapshot: dict[str, bytes]) -> dict:
|
|
|
106
107
|
except (OSError, ValueError, UnicodeError, TypeError):
|
|
107
108
|
blockers.add("invalid_configuration")
|
|
108
109
|
if config is not None:
|
|
110
|
+
process_config = config.get("process", {}) if isinstance(config, Mapping) else {}
|
|
111
|
+
configured = {
|
|
112
|
+
key: process_config.get(key, "incremental") if isinstance(process_config, Mapping) else None
|
|
113
|
+
for key in ("automatic_pipeline", "remember_pipeline")
|
|
114
|
+
}
|
|
115
|
+
counts["legacy_pipeline_settings"] = sum(value == "legacy" for value in configured.values())
|
|
116
|
+
if counts["legacy_pipeline_settings"]:
|
|
117
|
+
blockers.add("legacy_pipeline_configuration")
|
|
109
118
|
native_unavailable, native_blockers = _native_source_readiness(service, config)
|
|
110
119
|
counts["unavailable_native_sources"] = native_unavailable
|
|
111
120
|
blockers.update(native_blockers)
|
|
@@ -187,10 +196,8 @@ def _checks(service: Any, snapshot: dict[str, bytes]) -> dict:
|
|
|
187
196
|
elif progress["status"] != "current":
|
|
188
197
|
blockers.add("unsettled_sources_require_review")
|
|
189
198
|
warnings.update(_EXTERNAL_CHECKS)
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
configured = {k: config["process"].get(k, "legacy") for k in
|
|
193
|
-
("automatic_pipeline", "remember_pipeline")} if config else {}
|
|
199
|
+
# Historical route settings are reported as migration facts. They are not
|
|
200
|
+
# executable by this release and are never silently rewritten here.
|
|
194
201
|
return {"local_status": "blocked" if blockers else "clear",
|
|
195
202
|
"blockers": sorted(blockers),
|
|
196
203
|
"backup_blockers": sorted(blockers & {"processing_owner_live", "queue_not_quiescent"}),
|