memleaf 0.2.67__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.67 → memleaf-0.2.68}/CHANGELOG.md +17 -0
- {memleaf-0.2.67/src/memleaf.egg-info → memleaf-0.2.68}/PKG-INFO +1 -1
- {memleaf-0.2.67 → memleaf-0.2.68}/pyproject.toml +1 -1
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/_provider.py +15 -3
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/_shared.py +20 -2
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_protocol.py +29 -1
- {memleaf-0.2.67 → memleaf-0.2.68/src/memleaf.egg-info}/PKG-INFO +1 -1
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_hermes_source_metadata.py +84 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_protocol.py +34 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_provider_compatibility.py +1 -1
- {memleaf-0.2.67 → memleaf-0.2.68}/LICENSE +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/MANIFEST.in +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/README.en.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/README.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/acceptance-evidence-guide.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/automatic-processing-route.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/body-compaction.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/candidate-reconciliation.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/capture-budget-design.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/config-migrations.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/core-refactor.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/development-closeout.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/explicit-memory-update.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/extraction-latency.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/general-processing.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/hermes-provider-compatibility.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/host-acceptance-runbook.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/immutable-release-assets.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-commit-contract.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-execution.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-native-comparison.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-partial-recovery.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-planner-preview.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-runtime-contract.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-scope-registration.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/incremental-selected-retention.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/installed-artifact-verification.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/memory-state-contract.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/migration-preflight.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/public-query-integrity.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/query-performance.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/remember-incremental-route.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/runtime-state-retention.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/semantic-extraction-protocol.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/source-work-contract.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/examples/README.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/examples/basic_usage.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/examples/incremental_acceptance.json +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/examples/query_benchmark.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/install.ps1 +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/install.sh +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/setup.cfg +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/acceptance.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/adapters/base.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/batch_review.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/capture.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/cli.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/compaction.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/config.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/explicit_text_source.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_provider/provider_compatibility.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_commit.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_dates.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_execution.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_journal.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_native.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_partial.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_preview.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_prompts.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_run_state.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_runtime.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_scopes.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/incremental_selection.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/index.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/inspection.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/installer.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/legacy_update_journal.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/mcp_server.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/memory_commit.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/memory_planner.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/memory_retraction.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/memory_update.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/migration.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/model_execution.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/models.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/parallel_model.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/planning_context.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/process_common.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/process_jobs.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/process_journal.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/processing.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/processing_route.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/prompts.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/provider_compatibility.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/query_clock.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/query_progress.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/query_scan.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/receipt_codec.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/remember_route.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/retention.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/retrieval_gate.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/runtime_retention.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/semantic_maintenance.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/semantic_protocol.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/service.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/single_pass_memory_planner.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/single_pass_plan.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/source_policy.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/state_layout.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/summary_batch.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/update_review.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/validation.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf/vault.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf.egg-info/SOURCES.txt +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/src/memleaf.egg-info/top_level.txt +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/acceptance_oracles.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/candidate_update_fixtures.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/incremental_test_support.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/platform_smoke.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/release_assets.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/run_contracts.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_a083_regressions.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_a47_regressions.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_acceptance.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_acceptance_boundaries.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_artifact_verification.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_body_compaction.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_candidate_reconciliation.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_contract_reporter.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_control_absence_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_create_memory_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_development_closeout.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_explicit_memory_update.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_explicit_update_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_g1_memory_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_g2_source_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_hermes_native_provision.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_commit.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_configured_runtime.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_dates.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_execution.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_execution_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_native.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_partial.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_partial_boundaries.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_partial_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_preview.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_remember.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_remember_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_scope_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_incremental_scopes.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_inspection_read_budget.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_mcp_retrieval_fallback.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_migration_backup.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_migration_runtime.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_native_read_stability.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_planner_provenance_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_platform_contracts.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_processed_state_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_processing_entrypoints.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_processing_route.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_processing_route_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_public_query_integrity.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_query_benchmark.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_query_clock.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_query_progress.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_query_scan_reuse.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_release_assets.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_remember_route.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_remember_route_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_revision_target_integrity.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_runtime_retention.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_runtime_retention_observation.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_runtime_retention_recovery.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_source_followup_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/test_vault_binding_contract.py +0 -0
- {memleaf-0.2.67 → memleaf-0.2.68}/tests_public/verify_distribution.py +0 -0
|
@@ -4,6 +4,23 @@ 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
|
+
|
|
7
24
|
## 0.2.67 — 2026-09-20
|
|
8
25
|
|
|
9
26
|
### Incremental-only processing and release integrity
|
|
@@ -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
|
|
@@ -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"]:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import importlib
|
|
5
|
+
import json
|
|
5
6
|
import sys
|
|
6
7
|
import tempfile
|
|
7
8
|
import types
|
|
@@ -99,6 +100,89 @@ class HermesMetadataTests(unittest.TestCase):
|
|
|
99
100
|
for invocation in call.call_args_list:
|
|
100
101
|
self.assertNotIn('source_time', invocation.args[1])
|
|
101
102
|
|
|
103
|
+
def test_single_entry_tool_call_batch_is_observed_as_no_match(self):
|
|
104
|
+
retrieval_id = 'retrieval-1'
|
|
105
|
+
messages = [
|
|
106
|
+
{
|
|
107
|
+
'role': 'assistant',
|
|
108
|
+
'tool_calls': [{
|
|
109
|
+
'id': 'bridge-call',
|
|
110
|
+
'function': {
|
|
111
|
+
'name': 'tool_call',
|
|
112
|
+
'arguments': json.dumps({
|
|
113
|
+
'calls': [{
|
|
114
|
+
'name': 'mcp__memleaf__search',
|
|
115
|
+
'arguments': {'query': 'mail status', 'retrieval_id': retrieval_id},
|
|
116
|
+
}]
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
}],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
'role': 'tool',
|
|
123
|
+
'tool_call_id': 'bridge-call',
|
|
124
|
+
'content': json.dumps({'status': 'no_match', 'results': []}),
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
audit = {}
|
|
128
|
+
status = self.module.MemleafMemoryProvider._observe_search_messages(
|
|
129
|
+
messages, retrieval_id, audit_state=audit,
|
|
130
|
+
)
|
|
131
|
+
self.assertEqual(status, 'no_match')
|
|
132
|
+
self.assertEqual(audit['status'], 'NO_MATCH')
|
|
133
|
+
|
|
134
|
+
def test_multi_entry_tool_call_batch_is_not_misattributed(self):
|
|
135
|
+
retrieval_id = 'retrieval-1'
|
|
136
|
+
messages = [
|
|
137
|
+
{
|
|
138
|
+
'role': 'assistant',
|
|
139
|
+
'tool_calls': [{
|
|
140
|
+
'id': 'bridge-call',
|
|
141
|
+
'function': {
|
|
142
|
+
'name': 'tool_call',
|
|
143
|
+
'arguments': json.dumps({
|
|
144
|
+
'calls': [
|
|
145
|
+
{'name': 'mcp__memleaf__search',
|
|
146
|
+
'arguments': {'query': 'mail status', 'retrieval_id': retrieval_id}},
|
|
147
|
+
{'name': 'another_tool', 'arguments': {}},
|
|
148
|
+
]
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
}],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
'role': 'tool',
|
|
155
|
+
'tool_call_id': 'bridge-call',
|
|
156
|
+
'content': json.dumps({'status': 'no_match', 'results': []}),
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
audit = {}
|
|
160
|
+
status = self.module.MemleafMemoryProvider._observe_search_messages(
|
|
161
|
+
messages, retrieval_id, audit_state=audit,
|
|
162
|
+
)
|
|
163
|
+
self.assertEqual(status, 'unknown')
|
|
164
|
+
self.assertEqual(audit['status'], 'SEARCH_UNKNOWN')
|
|
165
|
+
|
|
166
|
+
def test_clean_marker_is_removed_before_assistant_capture(self):
|
|
167
|
+
with tempfile.TemporaryDirectory() as root:
|
|
168
|
+
service = Memleaf.initialize(Path(root) / 'vault')
|
|
169
|
+
provider = self.module.MemleafMemoryProvider()
|
|
170
|
+
provider._client = object(); provider._auto_process = False
|
|
171
|
+
raw_final = 'No new mail.\n\n<!--CLEAN-->'
|
|
172
|
+
rows = [
|
|
173
|
+
dict(role='user', content='Check mail', id='u', source_sequence=1),
|
|
174
|
+
dict(role='assistant', content=raw_final, id='a', source_sequence=2),
|
|
175
|
+
]
|
|
176
|
+
def call(name, args, **kw):
|
|
177
|
+
self.assertEqual(name, 'capture')
|
|
178
|
+
result = service.capture(**args)
|
|
179
|
+
return {'stored': result.stored, 'duplicate': result.duplicate}
|
|
180
|
+
with patch.object(provider, '_call', side_effect=call):
|
|
181
|
+
provider.sync_turn('Check mail', raw_final, session_id='s', turn_number=3, messages=rows)
|
|
182
|
+
turn = parse_inbox(service.vault)[0]
|
|
183
|
+
self.assertEqual(turn.events[1].content, 'No new mail.')
|
|
184
|
+
self.assertNotIn('<!--CLEAN-->', turn.events[1].content)
|
|
185
|
+
|
|
102
186
|
|
|
103
187
|
if __name__ == '__main__':
|
|
104
188
|
unittest.main()
|
|
@@ -117,6 +117,40 @@ class IncrementalProtocolTests(unittest.TestCase):
|
|
|
117
117
|
op = self.run_rows(self.create(type="todo", status="active", assignee="user:1", waiting_on=None))["operations"][0]
|
|
118
118
|
self.assertEqual(op["memory"]["assignee"], "user:1")
|
|
119
119
|
|
|
120
|
+
def test_flattened_create_wrapper_is_normalized_before_strict_validation(self):
|
|
121
|
+
row = {
|
|
122
|
+
"action": "CREATE", "evidence": ["e1"],
|
|
123
|
+
"type": "fact", "scope": "s1",
|
|
124
|
+
"title": "Known requirement", "body": "Keep this requirement.",
|
|
125
|
+
}
|
|
126
|
+
out = self.run_rows(row)
|
|
127
|
+
self.assertFalse(out["issues"])
|
|
128
|
+
self.assertEqual(len(out["operations"]), 1)
|
|
129
|
+
op = out["operations"][0]
|
|
130
|
+
self.assertEqual(op["memory"]["type"], "fact")
|
|
131
|
+
self.assertEqual(op["memory"]["scopes"], ["project:Atlas"])
|
|
132
|
+
self.assertIn("create_memory_wrapper_normalized", op["warnings"])
|
|
133
|
+
|
|
134
|
+
def test_flattened_create_with_unknown_field_stays_rejected(self):
|
|
135
|
+
row = {
|
|
136
|
+
"action": "CREATE", "evidence": ["e1"],
|
|
137
|
+
"type": "fact", "scope": "s1",
|
|
138
|
+
"title": "Known requirement", "body": "Keep this requirement.",
|
|
139
|
+
"unexpected": "must not be accepted",
|
|
140
|
+
}
|
|
141
|
+
out = self.run_rows(row)
|
|
142
|
+
self.assertFalse(out["operations"])
|
|
143
|
+
self.assertEqual(out["issues"][0]["code"], "invalid_fields")
|
|
144
|
+
|
|
145
|
+
def test_flattened_create_missing_required_field_stays_rejected(self):
|
|
146
|
+
row = {
|
|
147
|
+
"action": "CREATE", "evidence": ["e1"],
|
|
148
|
+
"type": "fact", "scope": "s1", "title": "Known requirement",
|
|
149
|
+
}
|
|
150
|
+
out = self.run_rows(row)
|
|
151
|
+
self.assertFalse(out["operations"])
|
|
152
|
+
self.assertEqual(out["issues"][0]["code"], "invalid_fields")
|
|
153
|
+
|
|
120
154
|
def test_target_type_cannot_be_reclassified_in_patch(self):
|
|
121
155
|
out = self.run_rows(self.update(type="fact", status="completed"))
|
|
122
156
|
self.assertFalse(out["operations"])
|
|
@@ -283,7 +283,7 @@ class ServerCompatibilityTests(unittest.TestCase):
|
|
|
283
283
|
with patch.object(self.service,'stats',side_effect=AssertionError('must not scan')):
|
|
284
284
|
result=self.hello(self.good)['result']
|
|
285
285
|
self.assertEqual(result['_meta'][BUILD_META],self.good)
|
|
286
|
-
self.assertEqual(result['serverInfo']['version'],'0.2.
|
|
286
|
+
self.assertEqual(result['serverInfo']['version'],'0.2.68')
|
|
287
287
|
|
|
288
288
|
def test_compatible_bridge_dispatches_capture(self):
|
|
289
289
|
self.hello(self.good)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|