Skip to content

fix(dedupe): model returns bullets-to-remove, Python applies (no file regen) - #11

Merged
EdgeCaser merged 1 commit into
mainfrom
fix/semantic-dedupe-prompt-hardening
Jul 8, 2026
Merged

EdgeCaser merged 1 commit into
mainfrom
fix/semantic-dedupe-prompt-hardening

Conversation

@EdgeCaser

Copy link
Copy Markdown
Owner

What & why

Follow-up to #10. When I validated memsync dedup --semantic live against the real GLOBAL_MEMORY.md, the model (Sonnet, low effort) kept prepending a "No duplicates found" announcement, and — because the memory file contains instruction-like text (persona rules, security notes) — it reacted to that text and injected a flag about it. #10's guard correctly rejected all of this, but that left the command erroring on every run, so the preview was never useful.

Wording alone can't fix it, and the claude_code backend only injects prefill as a soft system hint (not a real assistant-turn prefill — see _call_claude_code), so that lever is out too.

The fix: change the contract

The model no longer regenerates the file. It returns only the verbatim text of the bullets to remove (one per line), or NONE. Python does the removal. Consequences:

  • Stray chatter (announcements, flags, explanations) matches no real bullet → ignored, never written.
  • A hallucinated or paraphrased "removal" matches nothing → removes nothing.
  • The file is constructed in Python from the original minus matched lines → the model can't reformat or pollute it.

This supersedes #10's introduced-lines guard (the model can no longer introduce anything) with:

  • verbatim-match removal (only real bullets can be removed),
  • a sanity cap (never remove >half the bullets),
  • byte-identical return on NONE (no spurious diff), preserving trailing newline on removal.

Also fixes a subtle ""[:1] in "-*+" truthiness bug that would have treated blank lines as removable bullets.

Validation

Live: memsync dedup --semantic on the real file now prints "No semantic duplicates found." (exit 0) instead of erroring.

Tests (7): mark-and-remove; NONE byte-identical; non-matching commentary is a no-op; hallucinated removal is a no-op; bulk-removal cap; trailing-newline preserved; blank-line spacing preserved with internal blanks in the response.

Full suite: 460 passed, 1 skipped, coverage 82.60%. ruff check memsync/ clean.

🤖 Generated with Claude Code

… regen)

Follow-up to #10. Validating semantic dedupe live surfaced that Sonnet at low
effort keeps prepending a "No duplicates found" announcement and, worse, reads
instruction-like text inside the memory (persona rules, security notes) and
reacts to it — all of which #10's guard correctly rejected but which left the
command erroring on every run, so the preview was never useful.

Prompt wording alone can't stop this, and the claude_code backend only injects
prefill as a soft system hint (not a real assistant-turn prefill), so that lever
is out too. The durable fix is to change the contract: the model no longer
regenerates the file. It returns only the verbatim text of the bullets to REMOVE
(or NONE), and Python performs the removal. Stray chatter that matches no real
bullet is ignored; the file's integrity no longer depends on model formatting.

This supersedes #10's introduced-lines guard (the model can no longer introduce
anything) with a sanity cap (never remove >half the bullets) and verbatim-match
removal (a hallucinated/paraphrased line removes nothing). Also fixes a
trailing-newline mismatch that produced a spurious 1-line diff on clean files,
and a blank-line edge case ("" is spuriously `in` any string).

Live-validated: `memsync dedup --semantic` on the real GLOBAL_MEMORY.md now
returns "No semantic duplicates found." (exit 0), instead of erroring.

Tests: mark-and-remove; NONE is byte-identical; non-matching commentary is a
no-op; hallucinated removal is a no-op; bulk-removal cap; trailing-newline and
blank-line spacing preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@EdgeCaser
EdgeCaser merged commit dc5ccd0 into main Jul 8, 2026
7 checks passed
@EdgeCaser
EdgeCaser deleted the fix/semantic-dedupe-prompt-hardening branch July 8, 2026 18:12
EdgeCaser added a commit that referenced this pull request Jul 27, 2026
…ening

fix(dedupe): model returns bullets-to-remove, Python applies (no file regen)
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