Inspiration

Impeaching a witness with their own prior statement is one of the most effective moves at trial. The night before, an associate finds those statements by re-reading hundreds of pages of deposition and trial transcript with a highlighter. We wanted that first pass to take a minute instead of a night, without asking anyone to trust a model's word for it.

What it does

Upload a witness's deposition, trial testimony, and any exhibits they authored. Crossfire extracts every factual claim they made, finds the pairs that conflict, and shows each one side by side: verbatim quote, page:line cite, severity, a one-line explanation, and a suggested cross-examination question. It exports a Markdown impeachment memo. It distinguishes real contradictions from tensions worth pressing, and from statements that only look inconsistent because they were made at different times.

On our sample case (a fictional slip-and-fall with one witness, three documents, ten planted contradictions and one planted non-contradiction), it surfaces all ten, leaves the non-contradiction out, and catches one we didn't plant on purpose: the witness's own incident report contradicting his trial testimony. About 90 seconds, 31 model calls.

How we built it

Four stages:

  1. Parse. A regex parser turns standard court-reporter layout into statements with exact page:line cites. No model involved, so cites are never hallucinated.
  2. Extract. Claude Haiku turns each statement into atomic factual claims with a topic label, batched and run concurrently.
  3. Screen. A local step pairs claims from the same witness that share a topic or clear a TF-IDF similarity threshold, using the question that elicited each answer as context. ~20,000 possible pairs become under 200.
  4. Judge. Claude Sonnet evaluates each candidate pair against a conservative rubric and returns a verdict (contradiction / tension / consistent), severity, explanation, and a cross question.

FastAPI serves a single-page UI with live progress and a downloadable memo.

Challenges we ran into

Making the judge step conservative. A model asked "do these conflict?" leans toward yes, so the rubric carries explicit negative examples (different points in time, paraphrase, vagueness like "about 7" vs "around 7") and weighs the verbatim quote over the paraphrased claim.

Recall was the other problem. Our first real run missed one planted contradiction because the two answers ("a tenant called the desk" vs "I found it myself") share no words. The fix was to include the question in the pairing step, since both questions were "how did you first learn there was water."

Quotes also have to be real. Every quote the model returns is checked against the parsed statement text and replaced with the statement itself if it doesn't match.

Accomplishments that we're proud of

Every finding is checkable in seconds because it carries the real quote and the real cite. The tool is built to be verified, not trusted. It catches contradictions that span documents, including a witness's own written report against his testimony, and it correctly declines to flag the planted non-contradiction.

What we learned

Cheap similarity and expensive reasoning are good at different jobs. Using similarity to decide what to ask and a reasoning model to decide the answer gave better results than either alone, at a fraction of the cost of judging every pair.

What's next for Crossfire

Cross-witness comparison (same pipeline, different grouping key), a timeline view of a witness's account, OCR for scanned transcripts, and letting the lawyer confirm or reject findings to tune the screening threshold per case.

Disclosures

The sample case, Okafor v. Northgate, is fictional and written with planted contradictions; the answer key is in the README. The "Replay saved run" button replays a real earlier run and labels it with its timestamp. A test-only mock mode exists for the pipeline and shows a banner when on. Claude Code assisted with implementation.

Built With

Share this project:

Updates

Submission history