memorymaster 3.21.0__tar.gz → 3.22.0__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.
- {memorymaster-3.21.0/memorymaster.egg-info → memorymaster-3.22.0}/PKG-INFO +4 -2
- {memorymaster-3.21.0 → memorymaster-3.22.0}/README.md +3 -1
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/__init__.py +1 -1
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli.py +8 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli_handlers_basic.py +21 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli_handlers_curation.py +30 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config.py +6 -0
- memorymaster-3.22.0/memorymaster/contradiction_probe.py +326 -0
- memorymaster-3.22.0/memorymaster/migrations/0003_contradiction_verdicts.py +42 -0
- memorymaster-3.22.0/memorymaster/migrations/0004_query_cache.py +114 -0
- memorymaster-3.22.0/memorymaster/query_cache.py +129 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/retrieval.py +91 -30
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/service.py +59 -1
- {memorymaster-3.21.0 → memorymaster-3.22.0/memorymaster.egg-info}/PKG-INFO +4 -2
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/SOURCES.txt +8 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/pyproject.toml +1 -1
- memorymaster-3.22.0/tests/test_contradiction_probe.py +153 -0
- memorymaster-3.22.0/tests/test_floor_gate.py +90 -0
- memorymaster-3.22.0/tests/test_qrels_regression.py +88 -0
- memorymaster-3.22.0/tests/test_query_cache.py +127 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_freshness.py +4 -1
- {memorymaster-3.21.0 → memorymaster-3.22.0}/LICENSE +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/benchmarks/longmemeval_runner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/benchmarks/longmemeval_vector_runner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/benchmarks/perf_smoke.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/__main__.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_lifecycle.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_read.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_shared.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_sources.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_write_claims.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/access_control.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/action_exporters.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/action_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/atlas_claim_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/atlas_contract.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/auto_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/auto_resolver.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/candidate_dedupe.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/claim_edges.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/claim_verifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli_helpers.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/closets.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/claude-md-append.md +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/conflict_resolver.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/connectors/__init__.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/connectors/whatsapp.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/context_hook.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/context_optimizer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/daily_notes.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/dashboard.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/dashboard_auth.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/db_merge.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/delta_sync.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/dream_bridge.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/embeddings.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/entity_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/entity_graph.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/entity_registry.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/federated_graphify.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/feedback.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/graph_store.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/hook_log.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/__init__.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/calibration.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/compact_summaries.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/compactor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/daydream_ingest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/decay.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/dedup.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/deterministic.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/entity_graph_export.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/staleness.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/validator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/key_rotator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/lifecycle.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_budget.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_provider.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_rerank.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_steward.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/mcp_path_policy.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/mcp_server.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/mcp_usage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/media_processing.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/media_providers.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/metrics_exporter.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/0001_initial.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/0002_miner_state.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/__init__.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/runner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/models.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/observability.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/operator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/operator_queue.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/plugins.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/policy.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/postgres_store.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/qdrant_backend.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/qdrant_recall_fallback.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/qmd_bridge.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/query_classifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/query_expansion.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/recall_fusion.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/recall_tokenizer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/retry.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/review.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/rl_trainer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/rule_miner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/rules.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/scheduler.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/schema.sql +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/schema_postgres.sql +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/scope_utils.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/security.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/session_tracker.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/setup_hooks.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/skill_evolver.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/snapshot.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/steward.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/steward_classifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/steward_features.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/storage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/store_factory.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/transcript_miner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/turn_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_bases.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_curator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_exporter.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_linter.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_log.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_query_capture.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_synthesis.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/verbatim_recall.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/verbatim_store.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/webhook.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_engine.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_freshness.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_similarity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_suggest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_validate.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/dependency_links.txt +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/entry_points.txt +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/requires.txt +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/top_level.txt +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/agg_recall_latency.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/alert_operator_metrics.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/audit_dedupe_precision.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/autoresearch_daemon.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backfill_entity_extraction.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backfill_graph_store.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backfill_stop_hook_citations.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backtest_steward_classifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/build_steward_training_set.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/check_hook_template_drift.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/claude_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/codex_live_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/compaction_edge_cases.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/compaction_trace_report.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/compaction_trace_validate.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/confusion_matrix_eval.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/conversation_importer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/conversation_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/e2e_operator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/email_live_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_bm25_sweep.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_classify_f1.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_memorymaster.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_recall_precision_at_5.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_recall_quality.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_steward_pareto.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_verbatim_recall.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/expand_recall_eval.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/generate_drill_signoff.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/git_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/github_live_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/gitnexus_to_claims.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/grid_recall_weights.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/index_claims_to_qdrant.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/ingest_planning_docs.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/jira_live_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/label_prompts_with_judge.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/llm_benchmark.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/measure_dedupe_thresholds.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/merge_scope_variants.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/messages_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/operator_metrics.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/precompute_candidates.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/recurring_incident_drill.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/release_readiness.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/run_codex_autologger.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/run_incident_drill.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/scheduled_ingest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/setup-hooks.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/slack_live_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/sync_hook_templates.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/tickets_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/train_steward_classifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/webhook_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/setup.cfg +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/bench_longmemeval.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/conftest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_access_control.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_action_exporters.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_action_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_atlas_claim_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_atlas_contract.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_atlas_source_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_ingest_hook_citations.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_ingest_hook_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_resolver.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_validate.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_backend_parity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_bm25_per_field.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_calibration.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_calibration_priors_applied.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_candidate_dedupe.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claim_edges.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claim_links.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claim_type_ranking.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_classify_hook_f1.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_classify_hook_latency.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claude_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_dry_run.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_json_flag.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_ready.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_review_queue.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_subcommands.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_closets.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_closets_recall_integration.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compact_summaries.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compact_summaries_sensitivity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compaction_trace.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compactor_artifact_order.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_config.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_conflict_resolver.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_confusion_matrix_eval.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_connection_retry.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_connectors.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_context_hook.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_context_optimizer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_context_optimizer_provider.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_conversation_to_turns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_auth.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_coverage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_latency.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_lineage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_review_queue.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_daydream_ingest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_db_merge_confidence_conflict.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_db_merge_coverage_v2.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_decay_coverage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_decay_respects_pinned.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dedup.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dedup_cli.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dedup_conflict_disambiguation.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_delta_sync.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_deterministic_predicates.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dream_bridge_coverage_v2.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dream_bridge_sensitivity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_embeddings_coverage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_extractor.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_extractor_llm.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_graph.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_graph_export.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_new_kinds.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_regex_v3.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_registry.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_eval_harness.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_events_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_extract_llm_ollama.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_federated_graphify_mcp.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_federated_query_safety.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_feedback.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_fts5_search.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_graph_distance.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_graph_store.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_handler_regressions.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_hook_env_isolation.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_human_id.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_incident_drill_runner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_integration_workflows.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_key_rotator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_lifecycle.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_lifecycle_supersede_invariant.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_budget.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_fallback.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_provider_claude_cli.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_provider_key_rotation.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_steward_coverage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_steward_key_rotation.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_filter_bypass.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_helpers.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_path_policy.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_rate_limit.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_server_validation.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_usage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_media_processing.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_meta_decisions.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_metrics_exporter.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_migrations.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_observability.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_obsidian_mind_patterns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_operator.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_operator_queue.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_perf_smoke_config.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_plugins.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_policy_coverage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_policy_mode_env.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_postgres_parity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_qdrant_backend.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_qmd_bridge.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_query_classifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_query_expansion.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_entity_fanout.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_fusion.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_latency.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_precision_at_5.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_tokenizer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_vector_fallback.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_reliability_hardening.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_resolvers_concurrent_supersede.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_profile.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_profiles.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_rrf_tiebreaker.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_weights.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_review.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rl_trainer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rrf_auto_gate.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rule_claims.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rule_miner.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_scheduler.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_scope_boost.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_scope_utils.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_security_access.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_security_patterns.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sensitivity_filter_t07.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_service_coverage.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_session_tracker.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_snapshot.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_snapshot_roundtrip.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sqlite_core.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_staleness.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_stealth_mode.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_classifier.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_daydream_hook.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_features.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_features_v3.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_resolution_parity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_storage_parity.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_store_factory.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_tenant_isolation.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_turn_schema.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_two_pass_recall.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v311_fixes.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v313_e2e.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v313_run_cycle_dedupe.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v390_e2e.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v391_strict_warnings.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_vault_exporter.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_vault_linter_orphan.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_vector_search.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_dedup.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_recall.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_store.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_store_qdrant.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_webhook.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_webhook_hmac.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_whatsapp_importer.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_autopromote.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_binding.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_engine_idempotency.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_explored_and_contradictions.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_similarity_multiscope.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_suggest.py +0 -0
- {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_validate_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memorymaster
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.22.0
|
|
4
4
|
Summary: Production-grade memory reliability system for AI coding agents. Lifecycle-managed claims with citations, conflict detection, steward governance, and MCP integration.
|
|
5
5
|
Author: wolverin0
|
|
6
6
|
License: MIT
|
|
@@ -52,7 +52,7 @@ Lifecycle-managed claims with citations, conflict detection, steward governance,
|
|
|
52
52
|
|
|
53
53
|
[](LICENSE)
|
|
54
54
|
[](https://www.python.org/downloads/)
|
|
55
|
-
[]()
|
|
56
56
|
[]()
|
|
57
57
|
[]()
|
|
58
58
|
[](https://pypi.org/project/memorymaster/)
|
|
@@ -90,6 +90,8 @@ recent PR status, and sensitivity-filter invariants.
|
|
|
90
90
|
- **Rule-shaped claims** (new in v3.21.0): prescriptive `when <trigger>, do <action> because <rationale>` claims (`ingest_rule` / `query_rules`) — the shape an agent needs to actually change behaviour next time, not just recall a fact
|
|
91
91
|
- **Correction mining** (new in v3.21.0): `mine-rules` scans the verbatim transcript archive for user corrections and distills them into rule claims; the Stop hook also mines each session's latest correction automatically
|
|
92
92
|
- **Versioned schema migrations** (new in v3.20.0): `migrate` applies SQLite/Postgres migrations with sha256 drift detection; incremental `export-delta` ships small claim deltas for cheap cross-machine sync
|
|
93
|
+
- **Retrieval quality** (new in v3.22.0): floor-ratio boost gate (`MEMORYMASTER_BOOST_FLOOR_RATIO`) stops fresh-but-wrong claims outranking the true match; `query --explain` shows per-stage score attribution; an opt-in correctness-safe query cache (`MEMORYMASTER_QUERY_CACHE`) with a generation gate
|
|
94
|
+
- **Semantic contradiction probe** (new in v3.22.0): `detect-contradictions` finds claims that genuinely contradict each other (beyond the deterministic same-subject conflict check) via an LLM judge with a Wilson-CI rate and verdict cache
|
|
93
95
|
- **Steward governance**: multi-probe validators (filesystem, format, citation, semantic, tool) with proposal review
|
|
94
96
|
- **Conflict resolution**: 5-tier auto (confidence > freshness > citations > LLM > manual)
|
|
95
97
|
- **Auto-redaction** at ingest: JWT, GitHub tokens, Bearer, AWS keys, SSH keys, custom patterns
|
|
@@ -6,7 +6,7 @@ Lifecycle-managed claims with citations, conflict detection, steward governance,
|
|
|
6
6
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://www.python.org/downloads/)
|
|
9
|
-
[]()
|
|
10
10
|
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[](https://pypi.org/project/memorymaster/)
|
|
@@ -44,6 +44,8 @@ recent PR status, and sensitivity-filter invariants.
|
|
|
44
44
|
- **Rule-shaped claims** (new in v3.21.0): prescriptive `when <trigger>, do <action> because <rationale>` claims (`ingest_rule` / `query_rules`) — the shape an agent needs to actually change behaviour next time, not just recall a fact
|
|
45
45
|
- **Correction mining** (new in v3.21.0): `mine-rules` scans the verbatim transcript archive for user corrections and distills them into rule claims; the Stop hook also mines each session's latest correction automatically
|
|
46
46
|
- **Versioned schema migrations** (new in v3.20.0): `migrate` applies SQLite/Postgres migrations with sha256 drift detection; incremental `export-delta` ships small claim deltas for cheap cross-machine sync
|
|
47
|
+
- **Retrieval quality** (new in v3.22.0): floor-ratio boost gate (`MEMORYMASTER_BOOST_FLOOR_RATIO`) stops fresh-but-wrong claims outranking the true match; `query --explain` shows per-stage score attribution; an opt-in correctness-safe query cache (`MEMORYMASTER_QUERY_CACHE`) with a generation gate
|
|
48
|
+
- **Semantic contradiction probe** (new in v3.22.0): `detect-contradictions` finds claims that genuinely contradict each other (beyond the deterministic same-subject conflict check) via an LLM judge with a Wilson-CI rate and verdict cache
|
|
47
49
|
- **Steward governance**: multi-probe validators (filesystem, format, citation, semantic, tool) with proposal review
|
|
48
50
|
- **Conflict resolution**: 5-tier auto (confidence > freshness > citations > LLM > manual)
|
|
49
51
|
- **Auto-redaction** at ingest: JWT, GitHub tokens, Bearer, AWS keys, SSH keys, custom patterns
|
|
@@ -185,6 +185,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
185
185
|
query.add_argument("--scope-allowlist", default="", help="Comma-separated scopes to include (e.g. project,team_x)")
|
|
186
186
|
query.add_argument("--as-of", default="", help="Temporal query: show claims valid at this ISO timestamp")
|
|
187
187
|
query.add_argument("--auto-classify", action="store_true", help="Auto-classify query type and use optimal retrieval mode")
|
|
188
|
+
query.add_argument("--explain", action="store_true", help="Show per-stage score attribution (relevance vs. boosts, floor-gate status) for each result")
|
|
188
189
|
|
|
189
190
|
context = sub.add_parser("context", help="Pack relevant claims into a token-budgeted context block for AI agents")
|
|
190
191
|
context.add_argument("text", help="Query text describing what context is needed")
|
|
@@ -463,6 +464,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
463
464
|
mine_rules_cmd.add_argument("--provider", default="claude_cli", help="LLM provider for this run (default: claude_cli)")
|
|
464
465
|
mine_rules_cmd.add_argument("--reset", action="store_true", help="Clear the stored watermark before running (re-scan from the start)")
|
|
465
466
|
|
|
467
|
+
detect_contra = sub.add_parser("detect-contradictions", help="Find semantic contradictions between topically-similar claims via an LLM judge (v3.22)")
|
|
468
|
+
detect_contra.add_argument("--limit", type=int, default=200, help="Max claims to load for pair sampling")
|
|
469
|
+
detect_contra.add_argument("--sample", type=int, default=50, help="Max candidate pairs to judge this run (caps LLM calls)")
|
|
470
|
+
detect_contra.add_argument("--sim-low", dest="sim_low", type=float, default=0.60, help="Lower similarity-band bound (below = unrelated)")
|
|
471
|
+
detect_contra.add_argument("--sim-high", dest="sim_high", type=float, default=0.92, help="Upper similarity-band bound (at/above = near-duplicates, dedup's job)")
|
|
472
|
+
detect_contra.add_argument("--apply", action="store_true", help="Flag the lower-confidence claim of each contradicting pair as conflicted (reversible)")
|
|
473
|
+
|
|
466
474
|
verify_cmd = sub.add_parser("verify-claims", help="Cross-check claims against current codebase")
|
|
467
475
|
verify_cmd.add_argument("--scope", default="", help="Scope filter")
|
|
468
476
|
verify_cmd.add_argument("--limit", type=int, default=200, help="Max claims to check")
|
|
@@ -832,6 +832,25 @@ def _handle_run_cycle(args: argparse.Namespace, service, parser: argparse.Argume
|
|
|
832
832
|
return 0
|
|
833
833
|
|
|
834
834
|
|
|
835
|
+
def _print_score_explanation(breakdown: dict | None) -> None:
|
|
836
|
+
"""Render per-stage score attribution for `query --explain`.
|
|
837
|
+
|
|
838
|
+
Shows query-relevance vs. the metadata boost terms and whether the
|
|
839
|
+
floor-ratio gate suppressed the boosts for this result.
|
|
840
|
+
"""
|
|
841
|
+
if not breakdown:
|
|
842
|
+
print(" explain: (no breakdown — legacy retrieval mode)")
|
|
843
|
+
return
|
|
844
|
+
terms = breakdown.get("boost_terms", {})
|
|
845
|
+
w = breakdown.get("weights", (0, 0, 0, 0))
|
|
846
|
+
applied = breakdown.get("boosts_applied", True)
|
|
847
|
+
gate = "applied" if applied else f"GATED (relevance < floor={breakdown.get('floor', 0.0):.3f})"
|
|
848
|
+
term_str = " ".join(f"{k}={v:+.3f}" for k, v in terms.items())
|
|
849
|
+
print(f" explain: relevance={breakdown.get('relevance', 0.0):.3f} "
|
|
850
|
+
f"boosts={breakdown.get('boosts_total', 0.0):+.3f} [{gate}] -> final={breakdown.get('final', 0.0):.3f}")
|
|
851
|
+
print(f" weights(l,c,f,v)={tuple(round(x, 2) for x in w)} boost_terms: {term_str}")
|
|
852
|
+
|
|
853
|
+
|
|
835
854
|
def _handle_query(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
|
|
836
855
|
resolve_allow_sensitive_access(allow_sensitive=args.allow_sensitive, context="cli.query")
|
|
837
856
|
if getattr(args, "as_of", ""):
|
|
@@ -876,6 +895,8 @@ def _handle_query(args: argparse.Namespace, service, parser: argparse.ArgumentPa
|
|
|
876
895
|
f"vec={sc['vector_score']:.3f} "
|
|
877
896
|
f"active={int(bool(ann.get('active')))} stale={int(bool(ann.get('stale')))} "
|
|
878
897
|
f"conflicted={int(bool(ann.get('conflicted')))} pinned={int(bool(ann.get('pinned')))}")
|
|
898
|
+
if getattr(args, "explain", False):
|
|
899
|
+
_print_score_explanation(row.get("breakdown"))
|
|
879
900
|
print(f"rows={len(rows_data)}")
|
|
880
901
|
return 0
|
|
881
902
|
|
|
@@ -368,6 +368,35 @@ def _handle_mine_rules(args: argparse.Namespace, service, parser: argparse.Argum
|
|
|
368
368
|
return 0
|
|
369
369
|
|
|
370
370
|
|
|
371
|
+
def _handle_detect_contradictions(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
|
|
372
|
+
from memorymaster.contradiction_probe import run_probe
|
|
373
|
+
t0 = time.perf_counter()
|
|
374
|
+
result = run_probe(
|
|
375
|
+
effective_db, service,
|
|
376
|
+
limit=getattr(args, "limit", 200),
|
|
377
|
+
sample=getattr(args, "sample", 50),
|
|
378
|
+
sim_low=getattr(args, "sim_low", 0.60),
|
|
379
|
+
sim_high=getattr(args, "sim_high", 0.92),
|
|
380
|
+
apply=getattr(args, "apply", False),
|
|
381
|
+
)
|
|
382
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
383
|
+
if args.json_output:
|
|
384
|
+
print(_json_envelope(result, query_ms=elapsed_ms))
|
|
385
|
+
else:
|
|
386
|
+
ci = result["rate_ci"]
|
|
387
|
+
abort = f", ABORTED ({result['aborted_reason']})" if result.get("aborted_reason") else ""
|
|
388
|
+
print(
|
|
389
|
+
f"Contradictions: {result['contradictions']}/{result['judged']} judged "
|
|
390
|
+
f"(rate={result['rate']:.2f} CI95=[{ci[0]:.2f},{ci[1]:.2f}]) from "
|
|
391
|
+
f"{result['candidate_pairs']} candidate pairs; cache_hits={result['cache_hits']}, "
|
|
392
|
+
f"errors={result['judge_errors']}, flagged={result['flagged_conflicted']}{abort} ({elapsed_ms:.0f}ms)"
|
|
393
|
+
)
|
|
394
|
+
for f in result["found"][:20]:
|
|
395
|
+
print(f" [{f['severity']}] claims {f['claim_a_id']} <> {f['claim_b_id']} "
|
|
396
|
+
f"(sim={f['similarity']}): {f['reason']}")
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
|
|
371
400
|
def _handle_wiki_breakdown(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
|
|
372
401
|
from memorymaster.wiki_engine import breakdown
|
|
373
402
|
t0 = time.perf_counter()
|
|
@@ -729,6 +758,7 @@ COMMAND_HANDLERS: dict[str, object] = {
|
|
|
729
758
|
"bases-generate": _handle_bases_generate,
|
|
730
759
|
"mine-transcript": _handle_mine_transcript,
|
|
731
760
|
"mine-rules": _handle_mine_rules,
|
|
761
|
+
"detect-contradictions": _handle_detect_contradictions,
|
|
732
762
|
"verify-claims": _handle_verify_claims,
|
|
733
763
|
"extract-entities": _handle_extract_entities,
|
|
734
764
|
"entity-stats": _handle_entity_stats,
|
|
@@ -222,6 +222,11 @@ class Config:
|
|
|
222
222
|
llm_rerank: bool = False
|
|
223
223
|
rrf_tiebreaker_enabled: bool = False
|
|
224
224
|
rrf_tiebreaker_threshold: float = 0.01
|
|
225
|
+
# Floor-ratio gate (gbrain v0.35.6 "hybrid.floor_ratio"): metadata boosts
|
|
226
|
+
# (confidence/freshness/tier/pinned) only apply to candidates whose
|
|
227
|
+
# query-relevance (lexical+vector) is >= boost_floor_ratio * top relevance.
|
|
228
|
+
# 0.0 = disabled (boosts always apply) — preserves pre-v3.22 behaviour.
|
|
229
|
+
boost_floor_ratio: float = 0.0
|
|
225
230
|
|
|
226
231
|
# --- Initial confidence priors calibrated from validator outcomes ---
|
|
227
232
|
default_initial_confidence: float = DEFAULT_INITIAL_CONFIDENCE
|
|
@@ -397,6 +402,7 @@ def load_config(config_path: str | Path | None = None) -> Config:
|
|
|
397
402
|
_apply_env_bool(overrides, "MEMORYMASTER_LLM_RERANK", "llm_rerank")
|
|
398
403
|
_apply_env_bool(overrides, "MEMORYMASTER_RRF_TIEBREAKER", "rrf_tiebreaker_enabled")
|
|
399
404
|
_apply_env_float(overrides, "MEMORYMASTER_RRF_TIEBREAKER_THRESHOLD", "rrf_tiebreaker_threshold")
|
|
405
|
+
_apply_env_float(overrides, "MEMORYMASTER_BOOST_FLOOR_RATIO", "boost_floor_ratio")
|
|
400
406
|
_apply_env_retrieval_profiles(overrides)
|
|
401
407
|
|
|
402
408
|
# Filter to only valid Config fields
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Suspected-contradictions probe (v3.22, ported from gbrain v0.32.6).
|
|
2
|
+
|
|
3
|
+
MemoryMaster's deterministic conflict detection (conflict_resolver,
|
|
4
|
+
jobs/dedup.find_conflicts) only catches claims with the SAME subject+predicate
|
|
5
|
+
and a different object_value. It misses *semantic* contradictions phrased
|
|
6
|
+
differently — e.g. "the API is rate-limited at 100 req/min" vs "there is no
|
|
7
|
+
rate limit on the API". This probe finds those:
|
|
8
|
+
|
|
9
|
+
1. Sample topically-similar claim pairs via embedding cosine similarity in a
|
|
10
|
+
band (similar enough to be about the same thing, not near-duplicates).
|
|
11
|
+
2. Cheap pre-filter to skip pairs the deterministic path owns or that are
|
|
12
|
+
already linked by supersession.
|
|
13
|
+
3. Ask an LLM whether the pair genuinely contradicts (severity-scored), with a
|
|
14
|
+
persistent verdict cache so re-runs don't re-pay.
|
|
15
|
+
4. Report a contradiction rate with a Wilson 95% confidence interval (judge
|
|
16
|
+
errors counted in the denominator so the rate stays honest).
|
|
17
|
+
|
|
18
|
+
It does NOT auto-resolve. Default is a dry-run report; ``apply=True`` flags the
|
|
19
|
+
lower-confidence claim of each contradicting pair as ``conflicted`` (the
|
|
20
|
+
needs-human-arbitration state) via the lifecycle helper — never raw SQL, never
|
|
21
|
+
archive/supersede.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
import math
|
|
27
|
+
import os
|
|
28
|
+
import sqlite3
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from memorymaster import llm_budget, llm_provider
|
|
33
|
+
from memorymaster.embeddings import EmbeddingProvider, cosine_similarity, create_best_provider
|
|
34
|
+
from memorymaster.lifecycle import transition_claim
|
|
35
|
+
from memorymaster.models import Claim
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
PROMPT_VERSION = "v1"
|
|
40
|
+
_SKIP_STATUSES = {"superseded", "archived"}
|
|
41
|
+
|
|
42
|
+
_PROMPT = """You compare two memory claims and decide if they CONTRADICT each other.
|
|
43
|
+
A contradiction means both cannot be true at the same time about the same thing.
|
|
44
|
+
Topically related but compatible claims do NOT contradict. Different subjects do
|
|
45
|
+
NOT contradict.
|
|
46
|
+
|
|
47
|
+
Output ONE JSON object and nothing else:
|
|
48
|
+
{"contradicts": true|false, "severity": "low"|"medium"|"high", "reason": "<one short clause>"}
|
|
49
|
+
|
|
50
|
+
If they do not contradict, return {"contradicts": false, "severity": "low", "reason": ""}.
|
|
51
|
+
No markdown, no commentary."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Verdict cache
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
_VERDICT_DDL = """
|
|
59
|
+
CREATE TABLE IF NOT EXISTS contradiction_verdicts (
|
|
60
|
+
claim_a_id INTEGER NOT NULL,
|
|
61
|
+
claim_b_id INTEGER NOT NULL,
|
|
62
|
+
model TEXT NOT NULL,
|
|
63
|
+
prompt_version TEXT NOT NULL,
|
|
64
|
+
contradicts INTEGER NOT NULL,
|
|
65
|
+
severity TEXT,
|
|
66
|
+
reason TEXT,
|
|
67
|
+
created_at TEXT NOT NULL,
|
|
68
|
+
PRIMARY KEY (claim_a_id, claim_b_id, model, prompt_version)
|
|
69
|
+
)
|
|
70
|
+
""".strip()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _canonical_pair(a_id: int, b_id: int) -> tuple[int, int]:
|
|
74
|
+
"""Order a pair so the symmetric (a,b)/(b,a) cache to one row."""
|
|
75
|
+
return (a_id, b_id) if a_id <= b_id else (b_id, a_id)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _ensure_verdict_table(conn: sqlite3.Connection) -> None:
|
|
79
|
+
conn.execute(_VERDICT_DDL)
|
|
80
|
+
conn.commit()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _cache_get(conn: sqlite3.Connection, a_id: int, b_id: int, model: str) -> dict | None:
|
|
84
|
+
lo, hi = _canonical_pair(a_id, b_id)
|
|
85
|
+
row = conn.execute(
|
|
86
|
+
"""SELECT contradicts, severity, reason FROM contradiction_verdicts
|
|
87
|
+
WHERE claim_a_id = ? AND claim_b_id = ? AND model = ? AND prompt_version = ?""",
|
|
88
|
+
(lo, hi, model, PROMPT_VERSION),
|
|
89
|
+
).fetchone()
|
|
90
|
+
if row is None:
|
|
91
|
+
return None
|
|
92
|
+
return {"contradicts": bool(row[0]), "severity": row[1] or "low", "reason": row[2] or "", "cached": True}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _cache_put(conn: sqlite3.Connection, a_id: int, b_id: int, model: str, verdict: dict) -> None:
|
|
96
|
+
lo, hi = _canonical_pair(a_id, b_id)
|
|
97
|
+
conn.execute(
|
|
98
|
+
"""INSERT OR REPLACE INTO contradiction_verdicts
|
|
99
|
+
(claim_a_id, claim_b_id, model, prompt_version, contradicts, severity, reason, created_at)
|
|
100
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
101
|
+
(lo, hi, model, PROMPT_VERSION, int(bool(verdict.get("contradicts"))),
|
|
102
|
+
verdict.get("severity", "low"), verdict.get("reason", ""),
|
|
103
|
+
datetime.now(timezone.utc).isoformat()),
|
|
104
|
+
)
|
|
105
|
+
conn.commit()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Pair sampling
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _embed_text(claim: Claim) -> str:
|
|
114
|
+
if claim.subject and claim.predicate:
|
|
115
|
+
return f"{claim.subject} {claim.predicate} {claim.object_value or ''} {claim.text}"
|
|
116
|
+
return claim.text
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _same_subject_predicate(a: Claim, b: Claim) -> bool:
|
|
120
|
+
return (
|
|
121
|
+
bool(a.subject) and bool(a.predicate)
|
|
122
|
+
and (a.subject or "").strip().lower() == (b.subject or "").strip().lower()
|
|
123
|
+
and (a.predicate or "").strip().lower() == (b.predicate or "").strip().lower()
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _already_linked(a: Claim, b: Claim) -> bool:
|
|
128
|
+
"""Pair is already resolved by supersession — the deterministic path owns it."""
|
|
129
|
+
return (
|
|
130
|
+
a.supersedes_claim_id == b.id or b.supersedes_claim_id == a.id
|
|
131
|
+
or a.replaced_by_claim_id == b.id or b.replaced_by_claim_id == a.id
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _prefiltered(a: Claim, b: Claim) -> bool:
|
|
136
|
+
"""Cheap skip BEFORE the LLM: deterministic-domain or already-resolved pairs."""
|
|
137
|
+
if a.status in _SKIP_STATUSES or b.status in _SKIP_STATUSES:
|
|
138
|
+
return True
|
|
139
|
+
if _same_subject_predicate(a, b):
|
|
140
|
+
return True # conflict_resolver / find_conflicts already own these
|
|
141
|
+
if _already_linked(a, b):
|
|
142
|
+
return True
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def sample_candidate_pairs(
|
|
147
|
+
claims: list[Claim],
|
|
148
|
+
provider: EmbeddingProvider,
|
|
149
|
+
*,
|
|
150
|
+
sim_low: float = 0.60,
|
|
151
|
+
sim_high: float = 0.92,
|
|
152
|
+
limit: int | None = None,
|
|
153
|
+
) -> list[tuple[Claim, Claim, float]]:
|
|
154
|
+
"""Return (a, b, similarity) pairs in the [sim_low, sim_high) band.
|
|
155
|
+
|
|
156
|
+
The band is the key idea: below ``sim_low`` the claims are unrelated (can't
|
|
157
|
+
contradict); at/above ``sim_high`` they're near-duplicates (dedup's job).
|
|
158
|
+
In between is where genuine contradictions live.
|
|
159
|
+
"""
|
|
160
|
+
usable = [c for c in claims if c.status not in _SKIP_STATUSES]
|
|
161
|
+
if len(usable) < 2:
|
|
162
|
+
return []
|
|
163
|
+
embeddings = [provider.embed(_embed_text(c)) for c in usable]
|
|
164
|
+
pairs: list[tuple[Claim, Claim, float]] = []
|
|
165
|
+
for i in range(len(usable)):
|
|
166
|
+
for j in range(i + 1, len(usable)):
|
|
167
|
+
if _prefiltered(usable[i], usable[j]):
|
|
168
|
+
continue
|
|
169
|
+
sim = cosine_similarity(embeddings[i], embeddings[j])
|
|
170
|
+
if sim_low <= sim < sim_high:
|
|
171
|
+
pairs.append((usable[i], usable[j], round(sim, 4)))
|
|
172
|
+
pairs.sort(key=lambda p: -p[2])
|
|
173
|
+
if limit is not None:
|
|
174
|
+
pairs = pairs[:limit]
|
|
175
|
+
return pairs
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
# LLM judge
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _judge_llm(a: Claim, b: Claim) -> dict | None:
|
|
184
|
+
"""Ask the LLM whether a and b contradict. Returns a verdict dict or None
|
|
185
|
+
on parse/empty failure. May raise LLMBudgetExceeded."""
|
|
186
|
+
body = f"Claim A: {a.text}\nClaim B: {b.text}"
|
|
187
|
+
raw = llm_provider.call_llm(_PROMPT, body)
|
|
188
|
+
if not raw or not raw.strip():
|
|
189
|
+
return None
|
|
190
|
+
for item in llm_provider.parse_json_response(raw):
|
|
191
|
+
if isinstance(item, dict) and "contradicts" in item:
|
|
192
|
+
return {
|
|
193
|
+
"contradicts": bool(item.get("contradicts")),
|
|
194
|
+
"severity": (item.get("severity") or "low").strip().lower(),
|
|
195
|
+
"reason": (item.get("reason") or "").strip(),
|
|
196
|
+
"cached": False,
|
|
197
|
+
}
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _model_key() -> str:
|
|
202
|
+
provider = os.environ.get("MEMORYMASTER_LLM_PROVIDER", "google").strip().lower()
|
|
203
|
+
model = os.environ.get("MEMORYMASTER_LLM_MODEL", "").strip() or "default"
|
|
204
|
+
return f"{provider}:{model}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Wilson confidence interval
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def wilson_interval(successes: int, n: int, z: float = 1.96) -> tuple[float, float]:
|
|
213
|
+
"""Wilson score 95% CI for a binomial proportion. Returns (low, high).
|
|
214
|
+
|
|
215
|
+
Used for the contradiction rate so a handful of judged pairs doesn't read
|
|
216
|
+
as a precise number. ``n`` includes judge errors (counted as non-success).
|
|
217
|
+
"""
|
|
218
|
+
if n <= 0:
|
|
219
|
+
return (0.0, 0.0)
|
|
220
|
+
phat = successes / n
|
|
221
|
+
denom = 1.0 + z * z / n
|
|
222
|
+
center = (phat + z * z / (2 * n)) / denom
|
|
223
|
+
margin = (z * math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) / denom
|
|
224
|
+
return (max(0.0, center - margin), min(1.0, center + margin))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
# Public API
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def run_probe(
|
|
233
|
+
db_path: str,
|
|
234
|
+
service: Any,
|
|
235
|
+
*,
|
|
236
|
+
limit: int | None = 200,
|
|
237
|
+
sample: int | None = 50,
|
|
238
|
+
sim_low: float = 0.60,
|
|
239
|
+
sim_high: float = 0.92,
|
|
240
|
+
apply: bool = False,
|
|
241
|
+
provider: EmbeddingProvider | None = None,
|
|
242
|
+
) -> dict[str, Any]:
|
|
243
|
+
"""Sample similar claim pairs, judge contradictions (cached + budget-capped),
|
|
244
|
+
and report a Wilson-bounded contradiction rate.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
limit: max claims to load for pair sampling (oldest-first cap upstream).
|
|
248
|
+
sample: max candidate pairs to judge this run.
|
|
249
|
+
apply: if True, flag the lower-confidence claim of each contradicting
|
|
250
|
+
pair as ``conflicted`` (reversible; never archives/supersedes).
|
|
251
|
+
"""
|
|
252
|
+
if "://" in str(db_path):
|
|
253
|
+
raise ValueError("contradiction probe is SQLite-only")
|
|
254
|
+
|
|
255
|
+
stats: dict[str, Any] = {
|
|
256
|
+
"claims_scanned": 0,
|
|
257
|
+
"candidate_pairs": 0,
|
|
258
|
+
"judged": 0,
|
|
259
|
+
"cache_hits": 0,
|
|
260
|
+
"llm_calls": 0,
|
|
261
|
+
"judge_errors": 0,
|
|
262
|
+
"contradictions": 0,
|
|
263
|
+
"flagged_conflicted": 0,
|
|
264
|
+
"aborted_reason": None,
|
|
265
|
+
"rate": 0.0,
|
|
266
|
+
"rate_ci": [0.0, 0.0],
|
|
267
|
+
"found": [],
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
claims = service.store.list_claims(limit=limit or 1000, include_citations=False)
|
|
271
|
+
stats["claims_scanned"] = len(claims)
|
|
272
|
+
prov = provider or create_best_provider()
|
|
273
|
+
pairs = sample_candidate_pairs(claims, prov, sim_low=sim_low, sim_high=sim_high, limit=sample)
|
|
274
|
+
stats["candidate_pairs"] = len(pairs)
|
|
275
|
+
if not pairs:
|
|
276
|
+
return stats
|
|
277
|
+
|
|
278
|
+
model = _model_key()
|
|
279
|
+
conn = sqlite3.connect(db_path)
|
|
280
|
+
try:
|
|
281
|
+
_ensure_verdict_table(conn)
|
|
282
|
+
with llm_budget.cycle_scope() as budget:
|
|
283
|
+
for a, b, sim in pairs:
|
|
284
|
+
verdict = _cache_get(conn, a.id, b.id, model)
|
|
285
|
+
if verdict is not None:
|
|
286
|
+
stats["cache_hits"] += 1
|
|
287
|
+
else:
|
|
288
|
+
try:
|
|
289
|
+
verdict = _judge_llm(a, b)
|
|
290
|
+
except llm_budget.LLMBudgetExceeded as exc:
|
|
291
|
+
stats["aborted_reason"] = exc.reason
|
|
292
|
+
break
|
|
293
|
+
stats["llm_calls"] += 1
|
|
294
|
+
if verdict is None:
|
|
295
|
+
stats["judge_errors"] += 1
|
|
296
|
+
stats["judged"] += 1
|
|
297
|
+
continue
|
|
298
|
+
_cache_put(conn, a.id, b.id, model, verdict)
|
|
299
|
+
|
|
300
|
+
stats["judged"] += 1
|
|
301
|
+
if verdict["contradicts"]:
|
|
302
|
+
stats["contradictions"] += 1
|
|
303
|
+
loser, winner = (a, b) if a.confidence <= b.confidence else (b, a)
|
|
304
|
+
stats["found"].append({
|
|
305
|
+
"claim_a_id": a.id, "claim_b_id": b.id, "similarity": sim,
|
|
306
|
+
"severity": verdict["severity"], "reason": verdict["reason"],
|
|
307
|
+
"flag_candidate_id": loser.id,
|
|
308
|
+
})
|
|
309
|
+
if apply:
|
|
310
|
+
transition_claim(
|
|
311
|
+
service.store, loser.id, "conflicted",
|
|
312
|
+
reason=f"contradiction_probe: contradicts claim {winner.id} ({verdict['reason']})",
|
|
313
|
+
event_type="transition",
|
|
314
|
+
)
|
|
315
|
+
stats["flagged_conflicted"] += 1
|
|
316
|
+
if budget.aborted_reason and not stats["aborted_reason"]:
|
|
317
|
+
stats["aborted_reason"] = budget.aborted_reason
|
|
318
|
+
finally:
|
|
319
|
+
conn.close()
|
|
320
|
+
|
|
321
|
+
n = stats["judged"]
|
|
322
|
+
if n > 0:
|
|
323
|
+
stats["rate"] = round(stats["contradictions"] / n, 4)
|
|
324
|
+
lo, hi = wilson_interval(stats["contradictions"], n)
|
|
325
|
+
stats["rate_ci"] = [round(lo, 4), round(hi, 4)]
|
|
326
|
+
return stats
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""0003_contradiction_verdicts — LLM verdict cache for the contradiction probe.
|
|
2
|
+
|
|
3
|
+
The suspected-contradictions probe (:mod:`memorymaster.contradiction_probe`)
|
|
4
|
+
asks an LLM whether two topically-similar claims contradict. Judging is the
|
|
5
|
+
expensive step, so verdicts are cached keyed on the (canonical-ordered) claim
|
|
6
|
+
pair + model + prompt_version: re-running the probe never re-pays for a pair
|
|
7
|
+
already judged by the same model/prompt. A prompt_version bump invalidates the
|
|
8
|
+
cache for that pair automatically (new key).
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
VERSION = 3
|
|
13
|
+
DESCRIPTION = "contradiction_verdicts cache for the suspected-contradictions probe"
|
|
14
|
+
|
|
15
|
+
_DDL = """
|
|
16
|
+
CREATE TABLE IF NOT EXISTS contradiction_verdicts (
|
|
17
|
+
claim_a_id INTEGER NOT NULL,
|
|
18
|
+
claim_b_id INTEGER NOT NULL,
|
|
19
|
+
model TEXT NOT NULL,
|
|
20
|
+
prompt_version TEXT NOT NULL,
|
|
21
|
+
contradicts INTEGER NOT NULL,
|
|
22
|
+
severity TEXT,
|
|
23
|
+
reason TEXT,
|
|
24
|
+
created_at TEXT NOT NULL,
|
|
25
|
+
PRIMARY KEY (claim_a_id, claim_b_id, model, prompt_version)
|
|
26
|
+
)
|
|
27
|
+
""".strip()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def apply_sqlite(conn) -> None:
|
|
31
|
+
conn.execute(_DDL)
|
|
32
|
+
commit = getattr(conn, "commit", None)
|
|
33
|
+
if callable(commit):
|
|
34
|
+
commit()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def apply_postgres(conn) -> None:
|
|
38
|
+
cur = conn.cursor()
|
|
39
|
+
cur.execute(_DDL)
|
|
40
|
+
commit = getattr(conn, "commit", None)
|
|
41
|
+
if callable(commit):
|
|
42
|
+
commit()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""0004_query_cache — correctness-safe result cache for retrieval (gbrain v0.40.3).
|
|
2
|
+
|
|
3
|
+
Two tables plus write-triggers on ``claims``:
|
|
4
|
+
|
|
5
|
+
- ``cache_meta`` holds a single monotonic ``corpus_generation`` counter.
|
|
6
|
+
- INSERT/DELETE and column-scoped UPDATE triggers on ``claims`` bump that
|
|
7
|
+
counter, so any *retrieval-relevant* claim write advances the generation. The
|
|
8
|
+
UPDATE trigger deliberately EXCLUDES ``access_count``/``last_accessed`` —
|
|
9
|
+
otherwise recording an access on every query would invalidate the cache it
|
|
10
|
+
just served. A cache row is valid only if the generation it was written at
|
|
11
|
+
still equals the current corpus generation.
|
|
12
|
+
- ``query_cache`` stores serialized retrieval results keyed by a hash that
|
|
13
|
+
folds in the query, params, AND the retrieval config fingerprint (so a
|
|
14
|
+
weight/mode/floor change also invalidates).
|
|
15
|
+
|
|
16
|
+
The cache is opt-in (``MEMORYMASTER_QUERY_CACHE=1``); the triggers are always
|
|
17
|
+
active so the generation stays accurate, but a single-row integer bump per
|
|
18
|
+
claim write is negligible.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
VERSION = 4
|
|
23
|
+
DESCRIPTION = "query_cache + cache_meta + claims generation triggers (correctness-safe recall cache)"
|
|
24
|
+
|
|
25
|
+
_SQLITE_TABLES = """
|
|
26
|
+
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
27
|
+
key TEXT PRIMARY KEY,
|
|
28
|
+
value INTEGER NOT NULL
|
|
29
|
+
);
|
|
30
|
+
INSERT OR IGNORE INTO cache_meta(key, value) VALUES ('corpus_generation', 0);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS query_cache (
|
|
32
|
+
cache_key TEXT PRIMARY KEY,
|
|
33
|
+
result_json TEXT NOT NULL,
|
|
34
|
+
generation INTEGER NOT NULL,
|
|
35
|
+
created_at TEXT NOT NULL
|
|
36
|
+
);
|
|
37
|
+
""".strip()
|
|
38
|
+
|
|
39
|
+
# Triggers depend on the claims table; created only when it exists (it always
|
|
40
|
+
# does in a real DB — baseline schema precedes migrations — but the migration
|
|
41
|
+
# unit tests apply on a bare connection).
|
|
42
|
+
_SQLITE_TRIGGERS = """
|
|
43
|
+
CREATE TRIGGER IF NOT EXISTS claims_gen_ai AFTER INSERT ON claims BEGIN
|
|
44
|
+
UPDATE cache_meta SET value = value + 1 WHERE key = 'corpus_generation';
|
|
45
|
+
END;
|
|
46
|
+
CREATE TRIGGER IF NOT EXISTS claims_gen_au AFTER UPDATE OF
|
|
47
|
+
text, normalized_text, subject, predicate, object_value, scope,
|
|
48
|
+
confidence, status, pinned, tier, volatility, valid_from, valid_until,
|
|
49
|
+
archived_at, updated_at, last_validated_at
|
|
50
|
+
ON claims BEGIN
|
|
51
|
+
UPDATE cache_meta SET value = value + 1 WHERE key = 'corpus_generation';
|
|
52
|
+
END;
|
|
53
|
+
CREATE TRIGGER IF NOT EXISTS claims_gen_ad AFTER DELETE ON claims BEGIN
|
|
54
|
+
UPDATE cache_meta SET value = value + 1 WHERE key = 'corpus_generation';
|
|
55
|
+
END;
|
|
56
|
+
""".strip()
|
|
57
|
+
|
|
58
|
+
_POSTGRES_TABLES = """
|
|
59
|
+
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
60
|
+
key TEXT PRIMARY KEY,
|
|
61
|
+
value BIGINT NOT NULL
|
|
62
|
+
);
|
|
63
|
+
INSERT INTO cache_meta(key, value) VALUES ('corpus_generation', 0)
|
|
64
|
+
ON CONFLICT (key) DO NOTHING;
|
|
65
|
+
CREATE TABLE IF NOT EXISTS query_cache (
|
|
66
|
+
cache_key TEXT PRIMARY KEY,
|
|
67
|
+
result_json TEXT NOT NULL,
|
|
68
|
+
generation BIGINT NOT NULL,
|
|
69
|
+
created_at TEXT NOT NULL
|
|
70
|
+
);
|
|
71
|
+
""".strip()
|
|
72
|
+
|
|
73
|
+
_POSTGRES_TRIGGERS = """
|
|
74
|
+
CREATE OR REPLACE FUNCTION mm_bump_corpus_generation() RETURNS trigger AS $$
|
|
75
|
+
BEGIN
|
|
76
|
+
UPDATE cache_meta SET value = value + 1 WHERE key = 'corpus_generation';
|
|
77
|
+
RETURN NULL;
|
|
78
|
+
END;
|
|
79
|
+
$$ LANGUAGE plpgsql;
|
|
80
|
+
DROP TRIGGER IF EXISTS claims_gen_ins_del ON claims;
|
|
81
|
+
CREATE TRIGGER claims_gen_ins_del
|
|
82
|
+
AFTER INSERT OR DELETE ON claims
|
|
83
|
+
FOR EACH STATEMENT EXECUTE FUNCTION mm_bump_corpus_generation();
|
|
84
|
+
DROP TRIGGER IF EXISTS claims_gen_upd ON claims;
|
|
85
|
+
CREATE TRIGGER claims_gen_upd
|
|
86
|
+
AFTER UPDATE OF text, normalized_text, subject, predicate, object_value,
|
|
87
|
+
scope, confidence, status, pinned, tier, volatility, valid_from,
|
|
88
|
+
valid_until, archived_at, updated_at, last_validated_at ON claims
|
|
89
|
+
FOR EACH STATEMENT EXECUTE FUNCTION mm_bump_corpus_generation();
|
|
90
|
+
""".strip()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def apply_sqlite(conn) -> None:
|
|
94
|
+
conn.executescript(_SQLITE_TABLES)
|
|
95
|
+
has_claims = conn.execute(
|
|
96
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='claims'"
|
|
97
|
+
).fetchone()
|
|
98
|
+
if has_claims:
|
|
99
|
+
conn.executescript(_SQLITE_TRIGGERS)
|
|
100
|
+
commit = getattr(conn, "commit", None)
|
|
101
|
+
if callable(commit):
|
|
102
|
+
commit()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def apply_postgres(conn) -> None:
|
|
106
|
+
cur = conn.cursor()
|
|
107
|
+
cur.execute(_POSTGRES_TABLES)
|
|
108
|
+
cur.execute("SELECT to_regclass('claims')")
|
|
109
|
+
row = cur.fetchone()
|
|
110
|
+
if row and row[0] is not None:
|
|
111
|
+
cur.execute(_POSTGRES_TRIGGERS)
|
|
112
|
+
commit = getattr(conn, "commit", None)
|
|
113
|
+
if callable(commit):
|
|
114
|
+
commit()
|