Probe the RAG-dedup gate with the distilled title, not the full body - #134
Closed
jpr5 wants to merge 1 commit into
Closed
Probe the RAG-dedup gate with the distilled title, not the full body#134jpr5 wants to merge 1 commit into
jpr5 wants to merge 1 commit into
Conversation
The rag-dedup gate probed `GET /api/search` with the candidate's full title+body (truncated to ~2 KB). That endpoint is lexical `plainto_tsquery`, which ANDs every query lexeme — a long, memory-sourced candidate body ANDs hundreds of terms and matches no single indexed chunk, so the probe returns 0 hits. With an empty containment denominator the gate never marked a real corpus overlap for long facts: the "must not duplicate the existing RAG corpus" rule was silently unenforced for exactly the candidates most likely to duplicate it. Send a short, high-signal query built from the candidate's already-distilled `title` instead. The title is the short topical phrase the tsvector engine returns strong hits for. It is the title ALONE — appending the structural key components (subsystem/claimSlugHint, synthetic slugs rarely present verbatim in a prose chunk) ANDs the lexical match back down to zero, re-opening the same gap. The overlap DECISION is unchanged: containment is still computed over the FULL candidate token set; only the query used to FIND corpus passages is shortened. The char/byte/surrogate-safety machinery is retained for a pathological title. Verified RED->GREEN against the live prod search surface: a genuine long corpus duplicate came back NOVEL under the full-body probe (0 hits) and is correctly MARKED under the title probe (5 hits, containment 1.0).
Contributor
Author
|
Closing this — it's superseded by the semantic pgvector dedup that landed in #135. Rather than working around the lexical probe (distilled title vs full body), the harvest gate now does real embedding-based similarity retrieval, so the full-body-vs-tsvector mismatch this PR was patching no longer exists. Thanks for the initial dig here; the direction moved to semantic. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
dedupAgainstRagCorpus(src/atlas/rag-dedup.ts) probed the live RAG search endpoint (GET /api/searchonmcp.copilotkit.ai) with the candidate's full distilledtitle+contentbody (truncated to ~2 KB) as a single query.That endpoint is a lexical search:
textSearchChunks(src/db/queries.ts) runstsv @@ plainto_tsquery('english', $1), andplainto_tsqueryANDs every query lexeme — a chunk matches only if it contains all of them. A long, memory-sourced candidate body has hundreds of distinct lexemes spread across many sources, so no single indexed chunk contains them all → 0 hits. With zero hits the containment denominator is empty, so the gate classified the candidate NOVEL and never marked the overlap.Net: the "must not duplicate the existing RAG corpus" bar (spec §6.2 / §10 bar 6) was silently unenforced for long (memory-sourced) facts — exactly the candidates most likely to re-index already-indexed prose. Short topical queries were unaffected (a distilled title matches fine).
The fix
Send a short, high-signal query built from the candidate's already-distilled
titleinstead of the full body. The title is the short topical phrase the tsvector engine returns strong hits for.subsystem/claimSlugHint— synthetic slugs likecpk-runtime, rarely present verbatim in a prose chunk) ANDs the lexical match back down to zero, re-opening the same gap. Verified against prod:"AWS AgentCore"→ 5 hits,"AWS AgentCore cpk-runtime"→ 0 hits.candidateFullText); only the query used to find corpus passages is shortened.Deterministic, no new dependencies, no prod writes.
RED → GREEN proof (live prod search surface)
Exercised the real
AtlasHttpClient+ realdedupAgainstRagCorpusagainsthttps://mcp.copilotkit.ai/api/search(read-only). The candidate is a genuine long corpus duplicate: a real indexed chunk (AWS AgentCore,docs.copilotkit.ai/deploy/agentcore) restated with a short synthesizing reframe — the shape of a distilled memory fact. Same candidate, same endpoint, only the code differs.RED (before — full-body probe):
GREEN (after — title probe):
The live probe was run via a temporary harness (deleted before commit; not in the diff).
Files changed
src/atlas/rag-dedup.ts— newcandidateProbeSource(title-only);candidateProbeQueryTextbuilds the probe from it; header/inline comments updated.src/__tests__/atlas-rag-dedup.test.ts— new regression tests (title probe used, subsystem/claimSlugHint NOT appended, and a long candidate that would be missed by the full-body probe is marked via the title probe); byte/surrogate-safety tests re-pointed to a pathological title (the new probe source).Tests / quality gate
atlas-*suite: 33 files, 825 tests pass (30 inatlas-rag-dedup).npm test).npm run build(tsc): clean.npx tsc --noEmit -p tsconfig.scripts.json: clean. Prettier: my two files are clean.prettierCI job is already failing onmain(11 pre-existing unformatted files, none touched here) — a broken-main condition unrelated to this change; left out of scope to keep the diff focused.🤖 Generated with Claude Code