fix(diff): stop destroying memories on CONFLICT and cosine-only UPDATE#84
Open
astrologist wants to merge 1 commit into
Open
fix(diff): stop destroying memories on CONFLICT and cosine-only UPDATE#84astrologist wants to merge 1 commit into
astrologist wants to merge 1 commit into
Conversation
The diff engine silently soft-deletes an existing memory whenever the new
content is classified CONFLICT or UPDATE. Both paths misfire on long
technical notes and destroy unrelated memories:
1. classifySuggestion returns CONFLICT when similarity >= 0.7 and EITHER
text contains a negation word ("replaced", "no longer", "never",
"deprecated", ...). Change-log style memories almost always contain
these words, and same-domain notes easily reach 0.7 through shared
vocabulary plus the cosine >= 0.85 hoist. Observed three times in
production: a new memory about topic A silently deleted an unrelated
memory about topic B in the same entity cluster.
2. A CONFLICT is precisely the case where auto-deleting one side is
wrong: the texts disagree, so the caller should decide which one wins.
Changes:
- remember: CONFLICT now keeps both memories. Detection is unchanged and
the response still carries diff_suggestion=CONFLICT, so callers can
merge or forget deliberately.
- remember: UPDATE only auto-replaces when the top match has
TokenSimilarity >= 0.6. Cosine-only similarity (same-domain embeddings
cluster at 0.85+) is not enough evidence to destroy a memory.
- classify: near-verbatim re-statements (token similarity > 0.9) are
classified DUPLICATE before the negation scan — a text can no longer
"conflict" with an identical copy of itself and get re-added forever.
- classify: a new text >25% longer than its match is never DUPLICATE.
Previously a superset extension was cosine-hoisted above 0.9,
classified DUPLICATE, and skipped — silently dropping the new content.
Repro (before): store two same-domain notes describing different facts,
both containing "no longer"; the second remember returns
action=updated and soft-deletes the first. After: both are kept with
diff_suggestion=CONFLICT.
go test ./internal/search passes; existing table-driven cases unchanged
(call sites updated for the new tokenSim parameter).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
mnemon remembersilently soft-deletes an existing memory whenever the built-in diff classifies the new content as CONFLICT or UPDATE. Both paths misfire on long technical notes, destroying unrelated memories:classifySuggestionreturns CONFLICT when similarity >= 0.7 and either text contains a negation word ("replaced","no longer","never","deprecated", …). Change-log style memories almost always contain these words, and same-domain notes easily reach 0.7 similarity through shared vocabulary plus the cosine >= 0.85 hoist.We hit this three times in production use (v0.1.13): storing a new memory about topic A silently deleted an unrelated memory about topic B in the same entity cluster. Each time the only reason no data was lost permanently is that the
replaced_idin the response was noticed in-session.Reproduction
Store two same-domain notes describing different facts, both containing "no longer" (any two realistic incident write-ups about the same system will do). The second
rememberreturnsaction: updated, diff_suggestion: CONFLICTand soft-deletes the first.Fix
diff_suggestion: CONFLICT, so callers can merge orforgetdeliberately.TokenSimilarity >= 0.6. Cosine-only similarity (same-domain embeddings cluster at 0.85+) is not enough evidence to destroy a memory.Testing
go test ./internal/searchpasses (call sites updated for the newtokenSimparameter).MNEMON_DATA_DIR(with Ollama embeddings active):🤖 Generated with Claude Code