feat(consolidation): semantic dedup of near-duplicate observations (create + update)#1977
Merged
Merged
Conversation
babee52 to
fc3c476
Compare
…reate + update) Weak consolidation models (e.g. gemini-2.5-flash-lite) emit near-duplicate observations even when the twin is in context, and an UPDATE that rewrites + re-embeds an observation can drift it into a near-twin of a different existing observation. When consolidation_dedup_threshold < 1.0, an observation that is >= the threshold cosine to an existing one is reconciled by a focused 1-by-1 LLM "merge or keep" call (anchored on the observation text, not the source fact, so it is the correct obs<->obs comparison): - CREATE path: on "merge", fold the new source facts + synthesized text into the existing twin and skip the insert. - UPDATE path: after the rewrite+re-embed, probe the new vector (excluding the row itself); on "merge", fold the updated observation's sources into the twin and delete the now-redundant updated row. Default 1.0 disables it (no behaviour change). Postgres only. On the English hermes obs benchmark with flash-lite at 1/4 scale, residual >=0.97 near-dups drop from ~7% to 0-1%.
fc3c476 to
929bfff
Compare
This was referenced Jun 5, 2026
nicoloboschi
added a commit
that referenced
this pull request
Jun 5, 2026
… on Oracle (#2000) The create+update semantic dedup added in #1977 shipped opt-in (threshold 1.0). Enable it by default at 0.97 so observations are deduplicated out of the box. The merge path uses Postgres-only SQL, so consolidation skips dedup entirely on Oracle (via _dedup_active) — it behaves exactly as before there, regardless of the configured threshold. This is what lets the default flip without breaking Oracle deployments. Also fix MockLLM to return a valid keep-decision for the consolidation_dedup scope, so mock-LLM consolidation tests (which now exercise the enabled-by-default path) don't crash on the structured response and never spuriously merge.
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.
Problem
Weak consolidation models (e.g.
gemini-2.5-flash-lite) emit near-duplicate observations even when the twin is shown to them. Interleave-fusion recall (#1907) fixed this for strong models, but at 1/4 scale on the English hermes benchmark flash-lite still left ~7% residual near-duplicates (cosine ≥0.97).Root cause
Instrumented the consolidation create path (HNSW probe vs an exact Python cosine over every observation) and ran flash-lite at 1/4 scale: 0 probe misses across the entire run — the create-time probe never fails to surface an existing twin. The residual duplicates are instead born on the UPDATE path:
_execute_update_actionrewrites an observation's text and re-embeds it to fold in a new fact, and the rewritten vector can drift to ≥0.97 of a different existing observation. Nothing reconciled that — dedup only ran on CREATE. Every residual pair had ≥1 member that had been updated (history>0/proof_count>1).It is not a recall, HNSW-recall, pgvector-freshness, transaction-visibility, or caching problem (all ruled out with data: deterministic embeddings, autocommit connections, per-bank HNSW index present from row 0).
Fix
consolidation_dedup_threshold(default1.0= disabled, Postgres only). When set below 1.0, an observation that is ≥ the threshold cosine to an existing one is reconciled via a focused 1-by-1 LLM "merge or keep" call (anchored on the observation text — the correct obs↔obs comparison — scopeconsolidation_dedup; the model reads both texts so a number/negation/entity difference is respected):Shared probe+adjudicate logic is factored into
_dedup_adjudicate;_execute_update_actionreturns its freshly-computed embedding so the update-path probe doesn't re-embed.Result
English hermes obs benchmark, flash-lite, 1/4 scale: residual ≥0.97 near-dups ~7% → 0–1% (UPDATE-path merges observed firing alongside CREATE-path merges). Default
1.0keeps behaviour unchanged.Tests
tests/test_consolidation_dedup.py(13, no LLM/DB): exact-text guard, create-path (no-twin / keep / merge-folds / picks-highest), update-path (merge folds+deletes the updated row / keep / self-exclusion / no-twin).