Skip to content

feat(extraction): drop candidates that echo a recent manual store - #963

Draft
gorkem2020 wants to merge 3 commits into
CortexReach:masterfrom
gorkem2020:feat/manual-echo-guard
Draft

feat(extraction): drop candidates that echo a recent manual store#963
gorkem2020 wants to merge 3 commits into
CortexReach:masterfrom
gorkem2020:feat/manual-echo-guard

Conversation

@gorkem2020

Copy link
Copy Markdown
Contributor

Problem

When a user stores a fact manually with memory_store (or memory_update), the same conversation turn usually still flows through auto-capture extraction. The extractor then re-emits the just-stored fact as a candidate, and the pipeline spends judge, dedup, and merge LLM calls deciding what to do with a row that already exists verbatim. In the noisiest case the near-duplicate survives as a second row.

Fix

A small deterministic echo guard:

  • Manual memory_store / memory_update texts are recorded in an in-memory per-agent ring (8 entries, no persistence, no config surface).
  • Extraction candidates near-identical to a recorded text are dropped before the admission judge, with an INFO log line. Matching is deterministic and cheap: normalized containment, token-subset ratio >= 0.9 (both directions), or Jaccard >= 0.75. No LLM calls, no vector reads.
  • The manual row itself is untouched; it was already stored verbatim by the manual-priority lane.

Paraphrase-level echoes (same meaning, different words) are deliberately out of scope: covering them would need embedding lookups and would risk dropping genuinely new memories. The word-level tiers plus the existing dedup layer cover the observed failure shape.

Notes

  • Stacked on #960 (manual-priority supersede) because both touch the same tools.ts region; this diff is the last commit on the branch. Best reviewed after feat(tools): manual-priority supersede for memory_store #960 merges.
  • New unit suite test/manual-echo-guard.test.mjs (17 cases: tier matches, short-text guard, per-agent isolation, ring eviction); registered in the test chain and CI manifest.

🤖 Generated with Claude Code

gorkem2020 and others added 3 commits July 21, 2026 10:21
A manual memory_store call is an explicit decision to remember, but the
duplicate pre-check could reject it outright when a near-identical row
existed, and a contradicting update at ordinary similarity (a new value
for a stored preference at 0.8 similarity) landed as a second live row
beside the old value.

With manualStoreSupersede enabled, a manual store always takes priority:
its text lands verbatim (never mutated, never dropped), and a similar
existing row yields to it. Supersede targets, deterministically and with
no LLM on this lane: the near-identical neighbor the duplicate check
used to reject, then an active neighbor holding the same fact key at any
similarity (the update/contradiction shape), then the existing
0.95-0.98 same-category versioned band. Anything else stores alongside:
a wrong supersede destroys a real fact, while a duplicate is fixable
noise. force keeps bypassing the pre-check entirely, and a
manual-priority supersede writes a fresh overview rather than inheriting
the superseded value's stale one.

Off by default: the classic duplicate check is preserved exactly unless
the knob is set.
Manual memory_store/memory_update texts are recorded in an in-memory
per-agent ring (8 entries); extraction candidates near-identical to a
recorded text (normalized containment, token-subset >= 0.9, or Jaccard
>= 0.75) are dropped before the admission judge with an INFO line.
Deterministic, no LLM calls, no vector reads; the manual row itself is
already stored verbatim by the manual-priority lane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant