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.
Files changed (395) hide show
  1. {memorymaster-3.21.0/memorymaster.egg-info → memorymaster-3.22.0}/PKG-INFO +4 -2
  2. {memorymaster-3.21.0 → memorymaster-3.22.0}/README.md +3 -1
  3. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/__init__.py +1 -1
  4. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli.py +8 -0
  5. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli_handlers_basic.py +21 -0
  6. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli_handlers_curation.py +30 -0
  7. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config.py +6 -0
  8. memorymaster-3.22.0/memorymaster/contradiction_probe.py +326 -0
  9. memorymaster-3.22.0/memorymaster/migrations/0003_contradiction_verdicts.py +42 -0
  10. memorymaster-3.22.0/memorymaster/migrations/0004_query_cache.py +114 -0
  11. memorymaster-3.22.0/memorymaster/query_cache.py +129 -0
  12. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/retrieval.py +91 -30
  13. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/service.py +59 -1
  14. {memorymaster-3.21.0 → memorymaster-3.22.0/memorymaster.egg-info}/PKG-INFO +4 -2
  15. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/SOURCES.txt +8 -0
  16. {memorymaster-3.21.0 → memorymaster-3.22.0}/pyproject.toml +1 -1
  17. memorymaster-3.22.0/tests/test_contradiction_probe.py +153 -0
  18. memorymaster-3.22.0/tests/test_floor_gate.py +90 -0
  19. memorymaster-3.22.0/tests/test_qrels_regression.py +88 -0
  20. memorymaster-3.22.0/tests/test_query_cache.py +127 -0
  21. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_freshness.py +4 -1
  22. {memorymaster-3.21.0 → memorymaster-3.22.0}/LICENSE +0 -0
  23. {memorymaster-3.21.0 → memorymaster-3.22.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
  24. {memorymaster-3.21.0 → memorymaster-3.22.0}/benchmarks/longmemeval_runner.py +0 -0
  25. {memorymaster-3.21.0 → memorymaster-3.22.0}/benchmarks/longmemeval_vector_runner.py +0 -0
  26. {memorymaster-3.21.0 → memorymaster-3.22.0}/benchmarks/perf_smoke.py +0 -0
  27. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/__main__.py +0 -0
  28. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_lifecycle.py +0 -0
  29. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_read.py +0 -0
  30. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_schema.py +0 -0
  31. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_shared.py +0 -0
  32. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_sources.py +0 -0
  33. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/_storage_write_claims.py +0 -0
  34. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/access_control.py +0 -0
  35. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/action_exporters.py +0 -0
  36. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/action_extractor.py +0 -0
  37. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/atlas_claim_extractor.py +0 -0
  38. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/atlas_contract.py +0 -0
  39. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/auto_extractor.py +0 -0
  40. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/auto_resolver.py +0 -0
  41. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/candidate_dedupe.py +0 -0
  42. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/claim_edges.py +0 -0
  43. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/claim_verifier.py +0 -0
  44. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/cli_helpers.py +0 -0
  45. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/closets.py +0 -0
  46. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/claude-md-append.md +0 -0
  47. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
  48. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +0 -0
  49. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
  50. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
  51. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
  52. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
  53. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
  54. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
  55. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
  56. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/conflict_resolver.py +0 -0
  57. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/connectors/__init__.py +0 -0
  58. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/connectors/whatsapp.py +0 -0
  59. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/context_hook.py +0 -0
  60. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/context_optimizer.py +0 -0
  61. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/daily_notes.py +0 -0
  62. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/dashboard.py +0 -0
  63. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/dashboard_auth.py +0 -0
  64. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/db_merge.py +0 -0
  65. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/delta_sync.py +0 -0
  66. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/dream_bridge.py +0 -0
  67. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/embeddings.py +0 -0
  68. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/entity_extractor.py +0 -0
  69. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/entity_graph.py +0 -0
  70. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/entity_registry.py +0 -0
  71. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/federated_graphify.py +0 -0
  72. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/feedback.py +0 -0
  73. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/graph_store.py +0 -0
  74. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/hook_log.py +0 -0
  75. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/__init__.py +0 -0
  76. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/calibration.py +0 -0
  77. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/compact_summaries.py +0 -0
  78. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/compactor.py +0 -0
  79. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/daydream_ingest.py +0 -0
  80. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/decay.py +0 -0
  81. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/dedup.py +0 -0
  82. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/deterministic.py +0 -0
  83. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/entity_graph_export.py +0 -0
  84. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/extractor.py +0 -0
  85. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/staleness.py +0 -0
  86. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/jobs/validator.py +0 -0
  87. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/key_rotator.py +0 -0
  88. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/lifecycle.py +0 -0
  89. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_budget.py +0 -0
  90. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_provider.py +0 -0
  91. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_rerank.py +0 -0
  92. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/llm_steward.py +0 -0
  93. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/mcp_path_policy.py +0 -0
  94. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/mcp_server.py +0 -0
  95. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/mcp_usage.py +0 -0
  96. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/media_processing.py +0 -0
  97. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/media_providers.py +0 -0
  98. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/metrics_exporter.py +0 -0
  99. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/0001_initial.py +0 -0
  100. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/0002_miner_state.py +0 -0
  101. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/__init__.py +0 -0
  102. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/migrations/runner.py +0 -0
  103. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/models.py +0 -0
  104. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/observability.py +0 -0
  105. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/operator.py +0 -0
  106. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/operator_queue.py +0 -0
  107. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/plugins.py +0 -0
  108. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/policy.py +0 -0
  109. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/postgres_store.py +0 -0
  110. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/qdrant_backend.py +0 -0
  111. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/qdrant_recall_fallback.py +0 -0
  112. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/qmd_bridge.py +0 -0
  113. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/query_classifier.py +0 -0
  114. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/query_expansion.py +0 -0
  115. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/recall_fusion.py +0 -0
  116. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/recall_tokenizer.py +0 -0
  117. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/retry.py +0 -0
  118. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/review.py +0 -0
  119. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/rl_trainer.py +0 -0
  120. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/rule_miner.py +0 -0
  121. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/rules.py +0 -0
  122. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/scheduler.py +0 -0
  123. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/schema.py +0 -0
  124. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/schema.sql +0 -0
  125. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/schema_postgres.sql +0 -0
  126. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/scope_utils.py +0 -0
  127. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/security.py +0 -0
  128. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/session_tracker.py +0 -0
  129. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/setup_hooks.py +0 -0
  130. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/skill_evolver.py +0 -0
  131. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/snapshot.py +0 -0
  132. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/steward.py +0 -0
  133. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/steward_classifier.py +0 -0
  134. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/steward_features.py +0 -0
  135. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/storage.py +0 -0
  136. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/store_factory.py +0 -0
  137. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/transcript_miner.py +0 -0
  138. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/turn_schema.py +0 -0
  139. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_bases.py +0 -0
  140. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_curator.py +0 -0
  141. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_exporter.py +0 -0
  142. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_linter.py +0 -0
  143. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_log.py +0 -0
  144. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_query_capture.py +0 -0
  145. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/vault_synthesis.py +0 -0
  146. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/verbatim_recall.py +0 -0
  147. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/verbatim_store.py +0 -0
  148. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/webhook.py +0 -0
  149. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_engine.py +0 -0
  150. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_freshness.py +0 -0
  151. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_similarity.py +0 -0
  152. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_suggest.py +0 -0
  153. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster/wiki_validate.py +0 -0
  154. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/dependency_links.txt +0 -0
  155. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/entry_points.txt +0 -0
  156. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/requires.txt +0 -0
  157. {memorymaster-3.21.0 → memorymaster-3.22.0}/memorymaster.egg-info/top_level.txt +0 -0
  158. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/agg_recall_latency.py +0 -0
  159. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/alert_operator_metrics.py +0 -0
  160. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/audit_dedupe_precision.py +0 -0
  161. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/autoresearch_daemon.py +0 -0
  162. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backfill_entity_extraction.py +0 -0
  163. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backfill_graph_store.py +0 -0
  164. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backfill_stop_hook_citations.py +0 -0
  165. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/backtest_steward_classifier.py +0 -0
  166. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/build_steward_training_set.py +0 -0
  167. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/check_hook_template_drift.py +0 -0
  168. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/claude_to_turns.py +0 -0
  169. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/codex_live_to_turns.py +0 -0
  170. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/compaction_edge_cases.py +0 -0
  171. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/compaction_trace_report.py +0 -0
  172. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/compaction_trace_validate.py +0 -0
  173. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/confusion_matrix_eval.py +0 -0
  174. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/conversation_importer.py +0 -0
  175. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/conversation_to_turns.py +0 -0
  176. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/e2e_operator.py +0 -0
  177. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/email_live_to_turns.py +0 -0
  178. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_bm25_sweep.py +0 -0
  179. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_classify_f1.py +0 -0
  180. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_memorymaster.py +0 -0
  181. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_recall_precision_at_5.py +0 -0
  182. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_recall_quality.py +0 -0
  183. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_steward_pareto.py +0 -0
  184. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/eval_verbatim_recall.py +0 -0
  185. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/expand_recall_eval.py +0 -0
  186. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/generate_drill_signoff.py +0 -0
  187. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/git_to_turns.py +0 -0
  188. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/github_live_to_turns.py +0 -0
  189. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/gitnexus_to_claims.py +0 -0
  190. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/grid_recall_weights.py +0 -0
  191. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/index_claims_to_qdrant.py +0 -0
  192. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/ingest_planning_docs.py +0 -0
  193. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/jira_live_to_turns.py +0 -0
  194. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/label_prompts_with_judge.py +0 -0
  195. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/llm_benchmark.py +0 -0
  196. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/measure_dedupe_thresholds.py +0 -0
  197. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/merge_scope_variants.py +0 -0
  198. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/messages_to_turns.py +0 -0
  199. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/operator_metrics.py +0 -0
  200. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/precompute_candidates.py +0 -0
  201. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/recurring_incident_drill.py +0 -0
  202. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/release_readiness.py +0 -0
  203. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/run_codex_autologger.py +0 -0
  204. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/run_incident_drill.py +0 -0
  205. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/scheduled_ingest.py +0 -0
  206. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/setup-hooks.py +0 -0
  207. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/slack_live_to_turns.py +0 -0
  208. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/sync_hook_templates.py +0 -0
  209. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/tickets_to_turns.py +0 -0
  210. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/train_steward_classifier.py +0 -0
  211. {memorymaster-3.21.0 → memorymaster-3.22.0}/scripts/webhook_to_turns.py +0 -0
  212. {memorymaster-3.21.0 → memorymaster-3.22.0}/setup.cfg +0 -0
  213. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/bench_longmemeval.py +0 -0
  214. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/conftest.py +0 -0
  215. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
  216. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_access_control.py +0 -0
  217. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_action_exporters.py +0 -0
  218. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_action_extractor.py +0 -0
  219. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_atlas_claim_extractor.py +0 -0
  220. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_atlas_contract.py +0 -0
  221. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_atlas_source_schema.py +0 -0
  222. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_extractor.py +0 -0
  223. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_ingest_hook_citations.py +0 -0
  224. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_ingest_hook_schema.py +0 -0
  225. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_resolver.py +0 -0
  226. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_auto_validate.py +0 -0
  227. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_backend_parity.py +0 -0
  228. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_bm25_per_field.py +0 -0
  229. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_calibration.py +0 -0
  230. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_calibration_priors_applied.py +0 -0
  231. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_candidate_dedupe.py +0 -0
  232. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claim_edges.py +0 -0
  233. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claim_links.py +0 -0
  234. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claim_type_ranking.py +0 -0
  235. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_classify_hook_f1.py +0 -0
  236. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_classify_hook_latency.py +0 -0
  237. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_claude_to_turns.py +0 -0
  238. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_dry_run.py +0 -0
  239. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_json_flag.py +0 -0
  240. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_ready.py +0 -0
  241. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_review_queue.py +0 -0
  242. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_cli_subcommands.py +0 -0
  243. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_closets.py +0 -0
  244. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_closets_recall_integration.py +0 -0
  245. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compact_summaries.py +0 -0
  246. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compact_summaries_sensitivity.py +0 -0
  247. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compaction_trace.py +0 -0
  248. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_compactor_artifact_order.py +0 -0
  249. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_config.py +0 -0
  250. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_conflict_resolver.py +0 -0
  251. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_confusion_matrix_eval.py +0 -0
  252. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_connection_retry.py +0 -0
  253. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_connectors.py +0 -0
  254. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_context_hook.py +0 -0
  255. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_context_optimizer.py +0 -0
  256. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_context_optimizer_provider.py +0 -0
  257. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_conversation_to_turns.py +0 -0
  258. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard.py +0 -0
  259. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_auth.py +0 -0
  260. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_coverage.py +0 -0
  261. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_latency.py +0 -0
  262. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_lineage.py +0 -0
  263. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dashboard_review_queue.py +0 -0
  264. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_daydream_ingest.py +0 -0
  265. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_db_merge_confidence_conflict.py +0 -0
  266. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_db_merge_coverage_v2.py +0 -0
  267. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_decay_coverage.py +0 -0
  268. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_decay_respects_pinned.py +0 -0
  269. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dedup.py +0 -0
  270. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dedup_cli.py +0 -0
  271. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dedup_conflict_disambiguation.py +0 -0
  272. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_delta_sync.py +0 -0
  273. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_deterministic_predicates.py +0 -0
  274. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dream_bridge_coverage_v2.py +0 -0
  275. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_dream_bridge_sensitivity.py +0 -0
  276. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_embeddings_coverage.py +0 -0
  277. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_extractor.py +0 -0
  278. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_extractor_llm.py +0 -0
  279. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_graph.py +0 -0
  280. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_graph_export.py +0 -0
  281. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_new_kinds.py +0 -0
  282. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_regex_v3.py +0 -0
  283. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_entity_registry.py +0 -0
  284. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_eval_harness.py +0 -0
  285. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_events_schema.py +0 -0
  286. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_extract_llm_ollama.py +0 -0
  287. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_federated_graphify_mcp.py +0 -0
  288. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_federated_query_safety.py +0 -0
  289. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_feedback.py +0 -0
  290. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_fts5_search.py +0 -0
  291. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_graph_distance.py +0 -0
  292. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_graph_store.py +0 -0
  293. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_handler_regressions.py +0 -0
  294. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_hook_env_isolation.py +0 -0
  295. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_human_id.py +0 -0
  296. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_incident_drill_runner.py +0 -0
  297. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_integration_workflows.py +0 -0
  298. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_key_rotator.py +0 -0
  299. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_lifecycle.py +0 -0
  300. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_lifecycle_supersede_invariant.py +0 -0
  301. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_budget.py +0 -0
  302. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_fallback.py +0 -0
  303. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_provider_claude_cli.py +0 -0
  304. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_provider_key_rotation.py +0 -0
  305. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_steward_coverage.py +0 -0
  306. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_llm_steward_key_rotation.py +0 -0
  307. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_filter_bypass.py +0 -0
  308. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_helpers.py +0 -0
  309. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_path_policy.py +0 -0
  310. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_rate_limit.py +0 -0
  311. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_server_validation.py +0 -0
  312. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_mcp_usage.py +0 -0
  313. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_media_processing.py +0 -0
  314. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_meta_decisions.py +0 -0
  315. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_metrics_exporter.py +0 -0
  316. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_migrations.py +0 -0
  317. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_observability.py +0 -0
  318. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_obsidian_mind_patterns.py +0 -0
  319. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_operator.py +0 -0
  320. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_operator_queue.py +0 -0
  321. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_perf_smoke_config.py +0 -0
  322. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_plugins.py +0 -0
  323. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_policy_coverage.py +0 -0
  324. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_policy_mode_env.py +0 -0
  325. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_postgres_parity.py +0 -0
  326. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_qdrant_backend.py +0 -0
  327. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_qmd_bridge.py +0 -0
  328. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_query_classifier.py +0 -0
  329. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_query_expansion.py +0 -0
  330. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_entity_fanout.py +0 -0
  331. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_fusion.py +0 -0
  332. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_latency.py +0 -0
  333. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_precision_at_5.py +0 -0
  334. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_tokenizer.py +0 -0
  335. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_recall_vector_fallback.py +0 -0
  336. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_reliability_hardening.py +0 -0
  337. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_resolvers_concurrent_supersede.py +0 -0
  338. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_profile.py +0 -0
  339. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_profiles.py +0 -0
  340. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_rrf_tiebreaker.py +0 -0
  341. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_retrieval_weights.py +0 -0
  342. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_review.py +0 -0
  343. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rl_trainer.py +0 -0
  344. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rrf_auto_gate.py +0 -0
  345. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rule_claims.py +0 -0
  346. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_rule_miner.py +0 -0
  347. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_scheduler.py +0 -0
  348. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_schema.py +0 -0
  349. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_scope_boost.py +0 -0
  350. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_scope_utils.py +0 -0
  351. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_security_access.py +0 -0
  352. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_security_patterns.py +0 -0
  353. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
  354. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
  355. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sensitivity_filter_t07.py +0 -0
  356. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_service_coverage.py +0 -0
  357. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_session_tracker.py +0 -0
  358. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_snapshot.py +0 -0
  359. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_snapshot_roundtrip.py +0 -0
  360. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_sqlite_core.py +0 -0
  361. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_staleness.py +0 -0
  362. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_stealth_mode.py +0 -0
  363. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward.py +0 -0
  364. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_classifier.py +0 -0
  365. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_daydream_hook.py +0 -0
  366. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_features.py +0 -0
  367. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_features_v3.py +0 -0
  368. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_steward_resolution_parity.py +0 -0
  369. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_storage_parity.py +0 -0
  370. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_store_factory.py +0 -0
  371. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_tenant_isolation.py +0 -0
  372. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_turn_schema.py +0 -0
  373. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_two_pass_recall.py +0 -0
  374. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v311_fixes.py +0 -0
  375. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v313_e2e.py +0 -0
  376. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v313_run_cycle_dedupe.py +0 -0
  377. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v390_e2e.py +0 -0
  378. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_v391_strict_warnings.py +0 -0
  379. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_vault_exporter.py +0 -0
  380. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_vault_linter_orphan.py +0 -0
  381. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_vector_search.py +0 -0
  382. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_dedup.py +0 -0
  383. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_recall.py +0 -0
  384. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_store.py +0 -0
  385. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_verbatim_store_qdrant.py +0 -0
  386. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_webhook.py +0 -0
  387. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_webhook_hmac.py +0 -0
  388. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_whatsapp_importer.py +0 -0
  389. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_autopromote.py +0 -0
  390. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_binding.py +0 -0
  391. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_engine_idempotency.py +0 -0
  392. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_explored_and_contradictions.py +0 -0
  393. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_similarity_multiscope.py +0 -0
  394. {memorymaster-3.21.0 → memorymaster-3.22.0}/tests/test_wiki_suggest.py +0 -0
  395. {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.21.0
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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
54
54
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
55
- [![Tests](https://img.shields.io/badge/tests-2194-green.svg)]()
55
+ [![Tests](https://img.shields.io/badge/tests-2214-green.svg)]()
56
56
  [![MCP Tools](https://img.shields.io/badge/MCP%20tools-24-purple.svg)]()
57
57
  [![CLI Commands](https://img.shields.io/badge/CLI%20commands-86-orange.svg)]()
58
58
  [![PyPI](https://img.shields.io/pypi/v/memorymaster.svg)](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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
8
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
9
- [![Tests](https://img.shields.io/badge/tests-2194-green.svg)]()
9
+ [![Tests](https://img.shields.io/badge/tests-2214-green.svg)]()
10
10
  [![MCP Tools](https://img.shields.io/badge/MCP%20tools-24-purple.svg)]()
11
11
  [![CLI Commands](https://img.shields.io/badge/CLI%20commands-86-orange.svg)]()
12
12
  [![PyPI](https://img.shields.io/pypi/v/memorymaster.svg)](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
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "3.21.0"
5
+ __version__ = "3.22.0"
@@ -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()