modusbrain eval suspected-contradictions (v0.32.6)
The contradiction probe samples retrieval results, asks an LLM judge whether any pair contradicts on a factual claim relevant to the user’s query, and aggregates into a calibrated report. The output is data — the operator decides what to act on. This doc covers the architecture, severity rubric, how to interpret the headline number, and when to act.Why this exists
modusbrain handles contradictions for curated pages via compiled-truth-plus- timeline and source-boost: whencompanies/acme.md says MRR is 50K, the curated page outranks the
chat. takes.active filtering hides explicitly-superseded takes. Recency
decay biases ranking toward fresher content per source-tier.
What none of those mechanisms measure: how often do unmarked semantic
contradictions actually surface in retrieval? Without a probe, every
“should we build the bigger swing (chunk-level revises field + ranking
change)” decision is vibes. The probe produces evidence.
Architecture
Severity rubric
The judge assigns severity per finding:
Doctor sorts findings by severity DESC. The MCP op accepts a severity filter
so agents can fetch just the high-priority items.
How to interpret the headline number
The probe outputsqueries_with_contradiction / queries_evaluated with a
Wilson 95% confidence interval:
small_sample_note fires when n < 30 — at that scale the CI is
too wide to act on.
Decision criteria for the bigger swing (chunk-level revises field):
When to act on findings
Each finding ships with aresolution_command field — paste-ready:
modusbrain takes supersede <slug> --row N— newer take should replace the older chunk text on the same page (intra_page kind).modusbrain dream --phase synthesize --slug <slug>— compiled_truth for the curated entity needs an update (cross_slug curated-vs-bulk).modusbrain takes mark-debate <slug> --row N— intentional disagreement (e.g., two opinions you want to keep both of).# manual review: <a> vs <b>— judge wasn’t sure; operator decides.
modusbrain eval suspected-contradictions review --severity high to
inspect findings without re-running the probe.
Cost model
Default judge isclaude-haiku-4-5 at ~5/Mtok out. With
the v0.32.6 truncation at 1500 chars per pair, ~500 input + 80 output
tokens per judge call. Budget cap defaults to 1 non-TTY.
- ~$0.0006 per judge call
- ~$0.005 per query (after date pre-filter + cache hits)
- ~$0.50 per 100 queries
Trust posture
- Probe never mutates the brain. Runs only read pages/takes/chunks.
Writes go only to
eval_contradictions_runsandeval_contradictions_cache. - MCP
find_contradictionsis read-scope. NOT in the subagent allowlist — user-initiated only, not autonomous-action surface. - Build-fixture script is local-only. The redactor +
isCleanForCommitgate makes accidental private-data commits hard, but the operator MUST inspect every redaction before commit.
See also
- Plan:
~/.claude/plans/system-instruction-you-are-working-hashed-dewdrop.md - CHANGELOG:
## [0.32.6]entry covers the whole release. - Cost discipline:
docs/eval-bench.mdfor the recommended nightly cadence- trend-tracking workflow.
- Temporal axis follow-on (v0.35.3.1 + v0.35.7): v0.35.3.1 added a
six-member verdict enum (
no_contradiction | contradiction | temporal_supersession | temporal_regression | temporal_evolution | negation_artifact) and threadedpages.effective_dateinto the judge prompt so the probe stops crying wolf on legitimate change-over-time. v0.35.7 lands the trajectory substrate the probe pointed at:modusbrain eval trajectory <entity>shows the chronological typed-claim history with regressions flagged inline;modusbrain founder scorecard <entity>rolls up four signals (accuracy, consistency, growth direction, red flags) into a stable JSON contract. MCP opfind_trajectory(read scope, visibility-filtered for remote callers) exposes the same data to agents. The probe’stemporal_supersessionverdict and the consolidate phase’svalid_untilwriteback both preserve theauto-supersession.ts:4“NEVER auto-applies” invariant — the probe still emits paste-ready commands, onlyconsolidatewritesvalid_until(R1+R8 grep guard pins this).