Rekalldocs
Recipes

Contradiction detection

Surface conflicts across a contract corpus — the query that breaks similarity search. Ingest contracts and policies, follow the two-hop trace, and flag conflicts in code.

Contradiction is the query type that similarity search can't do: the conflicting clause doesn't look like your query — it looks like the clause it contradicts. Rekall follows the chain: hop 1 finds the contract, hop 2 finds the policy that conflicts with it given hop 1.

Where reasoning wins

On contradiction detection, SKIM scores 1.00 where BM25, an 8B embedding model (Qwen3-Embedding-8B), and a 9B agentic loop (Qwen3.5-9B) all score 0.50 — measured on the synthetic reasoning benchmark. It's the thesis in one number: retrieval that reasons finds things similarity cannot.

1 · Ingest the corpus

The demo corpus mixes supplier contracts with company policies. Supplier A's contract sets net-90 payment terms; the 2026 policy caps supplier terms at net-60 — a conflict no keyword overlaps.

rekall ingest ./contracts --store contracts
const store = await rekall.store("contracts");
await store.ingest([
  { id: "supplier_a",  text: "…Supplier A payment terms are net-90 as amended in §4.2…", metadata: { source: "supplier_a.pdf", team: "procurement" } },
  { id: "policy_2026", text: "…all supplier payment terms must not exceed net-60…",       metadata: { source: "policy_2026.md" } },
]);
store = rekall.store("contracts")
store.ingest([
    {"id": "supplier_a",  "text": "…Supplier A payment terms are net-90 as amended in §4.2…", "metadata": {"source": "supplier_a.pdf", "team": "procurement"}},
    {"id": "policy_2026", "text": "…all supplier payment terms must not exceed net-60…",       "metadata": {"source": "policy_2026.md"}},
])

2 · Ask for the conflict

rekall search "which supplier contract conflicts with the 2026 policy?" \
  --store contracts --trace
Output
● 2 passages · 2 hops · confidence 0.87 · 4.2 ms

  hop 1 → supplier_a.pdf   (matches query)
  hop 2 → policy_2026.md   (conflict found, given hop 1)
  ✓ sufficient — stopped after 2 of 4 max hops
const r = await store.search(
  "which supplier contract conflicts with the 2026 policy?",
  { trace: true },
);
r = store.search(
    "which supplier contract conflicts with the 2026 policy?",
    trace=True,
)

The trace shows the reasoning: supplier_a at hop 1 (matches query), then policy_2026 at hop 2 (conflict found, given hop 1).

3 · Flag conflicts programmatically

A conflict is a high-confidence result whose trace spans two different documents with a conflict-shaped why on the later hop. Turn that into a boolean:

detect.py
def is_conflict(r) -> bool:
    if r.confidence < 0.6 or r.hops_used < 2 or not r.trace:
        return False
    docs = {step.doc_id for step in r.trace}
    conflict_words = ("conflict", "contradict", "exceed", "violat")
    later_why = r.trace[-1].why.lower()
    return len(docs) >= 2 and any(w in later_why for w in conflict_words)

if is_conflict(r):
    a, b = r.trace[0].doc_id, r.trace[-1].doc_id
    print(f"⚠ conflict: {a} vs {b} (confidence {r.confidence:.2f})")
detect.ts
function isConflict(r): boolean {
  if (r.confidence < 0.6 || r.hopsUsed < 2 || !r.trace) return false;
  const docs = new Set(r.trace.map((s) => s.docId));
  const conflictWords = ["conflict", "contradict", "exceed", "violat"];
  const laterWhy = r.trace[r.trace.length - 1].why.toLowerCase();
  return docs.size >= 2 && conflictWords.some((w) => laterWhy.includes(w));
}

if (isConflict(r)) {
  const a = r.trace[0].docId, b = r.trace[r.trace.length - 1].docId;
  console.log(`⚠ conflict: ${a} vs ${b} (confidence ${r.confidence})`);
}

Sweep the whole corpus

Pair this with the RAG-eval harness: run one contradiction-shaped query per policy through search_many, and flag every result where is_conflict is true — an auditable conflict report over the entire contract set, in seconds, on a CPU.

On this page