kestrel: Stop asking the model that hallucinates to verify its own hallucinations #5830
grloper
started this conversation in
Show Your Plugins!
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
kestrel
A research engine that can't cite what a source never said.
dsh-deep-research· DeepSeek Harness plugin & standalone library · Zero runtime dependenciesRepository · Quick Start · Live Demo · Catalog PR #361 (Merged)
Repository: https://github.com/grloper/dsh-deep-research
By grloper (@grloper)
Most research tooling optimizes for plausible prose with URLs attached. Two failure modes survive any amount of prompt engineering because they are structural rather than stylistic:
Kestrel attacks both with code rather than with prompting.
1. A fabricated quote fails
indexOfThe entailment judge is required to return a verbatim quote. That quote is then located in the fetched source text by substring match — exact first, then normalized for whitespace and punctuation, with the match projected back to original character offsets.
If the quote cannot be located, the citation is rejected and the claim is demoted to
UNVERIFIED.No model is asked whether the citation is good. Asking the thing that hallucinates to check its own hallucination is circular;
String.indexOfis not. A dishonest judge cannot manufacture support — it can only fail the gate.Here is the demo catching a fabrication that differs from the truth by one digit:
Token overlap is 0.75 — comfortably high enough to slip past any similarity threshold a naive implementation would pick. The exact-match gate rejects it regardless.
2. Corroboration is counted in origins, not documents
3-gram shingles → MinHash → LSH candidate filtering → pairwise Jaccard. Above the syndication threshold a document is a verbatim copy; above the derivation threshold with a shared long quote it is a rewrite. Those edges form a lineage DAG, cycles are condensed with Tarjan's algorithm, and the roots are counted.
Edge direction is fixed by
min(publishedAt, waybackFirstSeen), because publishers rewrite their own dates and the archive timestamp is the one they do not control.On the demo corpus (one press release, six verbatim syndications, four rewrites, one independent regulatory filing, one correction):
Every source-counting tool reports "13 sources agree." Three actually do.
3. A contradiction is not averaged away
Eleven sources "support" the 18% growth claim. One primary filing retracts it. Confidence is capped by the contradiction rather than inflated by the crowd — the opposite of what majority-vote retrieval does.
4. It never silently produces nothing
The three mechanisms above are all gated on a judge returning a verbatim quote that is then anchored mechanically. That gate is right — but it makes the fallback judge load-bearing in a way that is easy to get catastrophically wrong, and this engine got it wrong.
When a host had no
llmservice, the judge fell back to a stub returningNEUTRALwith an empty quote for every document. The Tribunal admits onlySUPPORTED/PARTIAL/CONTRADICTEDevidence, so that stub discarded 100% of everything retrieved. The loop then spent its full round budget, found nothing by construction, and reported "0 resolved". If you activateddeep_researchand felt like nothing happened, this is why.A stub that guarantees zero output is not graceful degradation. It is a silent failure. The LLM-free path is now a real judge:
8,8%and8.0compare equal. (The first cut of this had a real bug: it extracted"8%"and compared it against a claim's"8", so the numeric bonus silently never fired. Caught by reading a failing test's actual values instead of assuming the test was wrong.)debunked,failed to replicate,found no) against affirmation cues, so refutation is captured rather than collapsed into support.NEUTRAL.Alongside it, a capability preflight separates three failures that used to look identical:
unresolved, with the queries that were triedA capability problem is now reported as a capability problem, never as an absence of evidence. Verified end to end: with no LLM at all a run admits real anchored evidence and cites real sources; a lying LLM judge still cannot inject a fabricated quote; and an irrelevant corpus is reported as unresolved rather than invented into support.
Same pass also fixed: question decomposition that severed predicates (
"Compare Rust and Go for backend services"became two corrupted fragments issued verbatim as search queries), status buckets that could print0 resolved · 0 contested · 0 unresolvedfor work that had actually run,deep/forensicmodes quitting at round 2 of 6/12 because follow-up queries never varied, and paywalled sources being dropped entirely despite carrying quotable snippet text.Tools registered in DSH
verify_textdeep_researchquick/standard/deep/forensic)compare_sourcescheck_sourceresearch_recallFreshness is per-claim by volatility class — a mathematical constant and a stock price must not expire on the same schedule (5 years / ~6 months / 24 hours).
Try it in 60 seconds — no API key, no network
git clone https://github.com/grloper/dsh-deep-research.git cd dsh-deep-research npm run demoThe demo runs the real pipeline over a fixed corpus. Every number above is its actual output.
Install into DeepSeek Harness
Engineering highlights
harness.handleRPC back to the host engine and renders only what was proved mechanically..dsh/veritas/evidence to.dsh/kestrel/, preserving SQLite WAL and SHM sidecars so prior findings never get lost across updates.Why "Kestrel"?
A kestrel hunts by hovering — holding station in the air, dead still, until it sees exactly what is there. Then it commits, once.
That is the opposite of how research agents usually behave: grab the first ten results, summarize confidently, attach URLs. This engine is built to hold position over the evidence and only strike when the quote is actually there.
Feedback and issues welcome at github.com/grloper/dsh-deep-research!
All reactions