THE CONTRADICTION PROBLEM IN AI MEMORY

Here is a thing that happens more often than anyone admits. A user tells your AI agent they live in San Francisco. A few weeks later, they mention relocating to Boston for work. Your memory system now has two facts about where this person lives. The next time the agent needs to answer "where does this user live," it picks whichever fact has the higher cosine similarity to the query embedding. Sometimes that is Boston. Sometimes it is San Francisco. The agent does not know it is wrong. It just answers with full confidence. Confidently wrong is a special kind of wrong.

I have spent a lot of time thinking about this problem while building widemem. The contradiction problem is, in my opinion, the single biggest reliability issue in AI memory today. Not because it is technically exotic. Because it is mundane, frequent, and invisible until it produces a wrong answer that someone notices.


WHY CONTRADICTIONS ACCUMULATE

People's lives change. They move, change jobs, get married, switch doctors, update their preferences. Every change creates a potential contradiction with something already stored in memory. Over months of interaction, an active user might generate dozens of these implicit contradictions.

The standard memory architecture has no mechanism to detect this. Text goes in, facts get extracted, embeddings get stored, retrieval uses cosine similarity. At no point does the system ask: "wait, does this new fact conflict with something I already know?"

JanFebMarJunSepNaive system (no resolution)lives in San Franciscoworks at startuplives in Bostonworks at bank!!With conflict resolutionlives in Bostonworks at bank(SF fact replaced)(startup fact replaced)

Top: contradictions accumulate. Bottom: old facts get replaced at write time.

The top row is what most systems do. Facts pile up. Contradictions sit side by side. The system has no opinion about which is current. The bottom row is what happens with conflict resolution at write time. Old facts get replaced when new contradicting information arrives. The memory state stays clean.


VECTOR SIMILARITY CANNOT SOLVE THIS

The obvious reaction is: "just check if the new fact is similar to existing facts and flag conflicts." This works for some cases and fails badly for others.

"Lives in San Francisco" and "just moved to Boston" have reasonable embedding similarity because they are both about location. A similarity threshold of 0.6 catches this. But "lives on the West Coast" and "relocated to Boston" might score 0.41. Same contradiction, different phrasing, missed by the threshold.

threshold (0.6)0.72 detected"lives in San Francisco"vs "just moved to Boston"0.68 detected"works as a data engineer"vs "got promoted to senior"0.41 MISSED"lives on the West Coast"vs "relocated to Boston"0.33 MISSED"enjoys the mild weather"vs "hates the cold winters"0.29 MISSED"single, no kids"vs "just got married"

Contradictions below the similarity threshold slip through undetected. The last three are real contradictions that embeddings miss.

The problem gets worse with indirect contradictions. "Single, no kids" and "just got married" are clearly contradictory, but the embeddings do not overlap much. The topics are related (personal life) but the words are different. Cosine similarity between these two facts might be 0.29. Well below any reasonable threshold.

Lowering the threshold does not fix it. Set it to 0.3 and you catch more contradictions, but you also pull in a flood of unrelated facts for every write operation. The LLM resolver then has to sort through dozens of irrelevant matches, which wastes tokens and increases the chance of bad decisions.


WHAT ACTUALLY WORKS: BATCH CONFLICT RESOLUTION

The approach that works in widemem is to send all new facts plus their candidate matches to an LLM in a single call and let it decide what to do with each one. The LLM understands that "lives in San Francisco" and "just moved to Boston" are contradictions, even when the embeddings are ambiguous. It also understands that "works as a data engineer" and "got promoted to senior data engineer" is a refinement, not a contradiction.

The prompt gives the LLM four options per fact:

For each new fact, decide the action:
  ADD    - genuinely new, not captured by existing memories
  UPDATE - supersedes or refines an existing memory (specify which one)
  DELETE - makes an existing memory invalid (specify which one)
  NONE   - already captured, skip it

Rules:
- Be conservative: prefer NONE over ADD if already well-captured
- Detect contradictions: "moved to Boston" should UPDATE "lives in SF"
- Detect refinements: "senior engineer at Google" should UPDATE "works at Google"
- One action per new fact

The key word in that prompt is "conservative." Left to its own devices, an LLM will ADD almost everything. LLMs are hoarders by nature. It needs explicit instruction to check for duplicates and contradictions first. Without the conservative bias, you end up with near-duplicates cluttering the memory store.

Why batch matters

The first version of widemem checked each new fact individually. One fact, one LLM call. This was correct but had two problems. First, ten facts meant ten API calls, which is expensive. Second, the LLM could not see relationships between the incoming facts.

Consider this scenario: a user says "I just moved to Boston for a new job at a hospital." The extractor produces three facts: "lives in Boston," "works at a hospital," and "recently relocated." If you check each one individually, the LLM resolving "lives in Boston" does not know that "recently relocated" is also in the batch. It might decide to ADD "lives in Boston" alongside "lives in San Francisco" instead of updating, because it lacks the context that this is clearly a relocation.

In the batch approach, the LLM sees all three facts together plus all related existing memories. It can reason: "fact 0 (lives in Boston) contradicts existing memory 2 (lives in San Francisco), and fact 2 (recently relocated) confirms this is a move, so UPDATE existing memory 2." Better decisions, fewer API calls.


THE TYPES OF CONTRADICTION NOBODY TALKS ABOUT

Partial contradictions

"I work at Google" followed by "my team at Google just moved to Alphabet." Is the second fact a contradiction? An update? Both are technically true. The user still works at Google (which is part of Alphabet). But the organizational context has shifted. The resolver needs to understand corporate structure to handle this correctly. It usually does, because LLMs have that context. But "usually" is not "always."

Temporal markers vs. contradictions

"I used to live in San Francisco" is not a contradiction with "lives in San Francisco." It is a temporal marker indicating the fact is now outdated. But the phrasing is tricky. "Used to" implies a past state. The resolver should UPDATE the existing fact and probably ask where the user lives now. In practice, the LLM handles "used to" correctly about 80% of the time. The other 20%, it treats it as a confirmation rather than an invalidation.

Preference drift

"I love spicy food" followed by "I have been avoiding spicy food lately." This is not a binary contradiction. Preferences exist on a spectrum and change over time. The second statement does not fully invalidate the first. Maybe the user still loves spicy food in general but is avoiding it temporarily for health reasons.

The LLM resolver tends to UPDATE in these cases, replacing "loves spicy food" with "has been avoiding spicy food lately." This loses the original preference. A smarter approach might keep both facts with temporal context: "generally likes spicy food, currently avoiding it." Widemem does not do this yet. It is a hard problem because it requires the resolver to synthesize rather than just pick one.

Implicit contradictions

"I am vegetarian" followed later by a casual "had a great steak dinner." These facts do not share keywords. Their embeddings might not be close. But they are clearly contradictory. You know it. I know it. The embedding model does not. Catching this requires world knowledge (vegetarians do not eat steak), not just text similarity.

LLMs have this world knowledge, so the batch resolver does catch "vegetarian" vs. "steak dinner" when both facts are in the context window. The bottleneck is the similarity threshold: if "I am vegetarian" and "had a great steak dinner" are not similar enough to be candidates, the resolver never sees them together.


APPROACHES I CONSIDERED AND REJECTED

Graph-based contradiction detection

Knowledge graphs can model relationships between entities and flag contradictions through constraint violations. If "User : livesIn : San Francisco" and a new triple says "User : livesIn : Boston," the graph can detect that livesIn should be single-valued and flag the conflict.

This is elegant and works well for structured data. The problem is getting from conversational text to clean graph triples. That extraction step is itself LLM-dependent and error-prone. You trade one problem (contradiction detection) for another (reliable entity-relationship extraction). Mem0 is doing good work in this space, combining graph memory with vector search. For my use case, the flat-storage-plus-LLM-resolver approach was simpler and good enough.

Embedding contradictions directly

Some researchers have proposed training embeddings that place contradictory statements close together rather than just semantically similar ones. "Loves spicy food" and "avoids spicy food" would have high similarity in this embedding space, making threshold-based detection work.

This is promising in theory. In practice, contradiction-aware embeddings do not exist as production-ready models yet. Training them requires large-scale contradiction datasets, which are scarce. And you would need to re-embed your entire memory store if you switch models. The overhead is significant for a gain that batch LLM resolution already captures.

Per-field conflict detection

Instead of storing free-text facts, structure them as key-value pairs: location=San Francisco, job=data engineer, allergy=penicillin. Then conflict detection is trivial: two values for the same key is a conflict.

This works until you realize that conversational text does not map cleanly to fixed schemas. "I split my time between Boston and New York" breaks the single-value assumption. "I do some freelance work on the side" does not contradict "works at a bank" but would if you force both into a "job" field. The rigidity of structured schemas does not match the flexibility of human conversation. People are messy. Their data should be allowed to be messy too.


WHAT IS STILL BROKEN

The similarity threshold is a blunt instrument. At 0.6, it catches obvious contradictions and misses subtle ones. At 0.4, it catches more but floods the resolver with noise. There is no single threshold that works for all types of contradictions. A per-topic threshold (lower for personal facts, higher for technical preferences) might help, but adds configuration complexity.

Synthesis is harder than replacement. "Loves spicy food" + "avoiding spicy food lately" should ideally become "generally enjoys spicy food, currently avoiding it." The resolver can do UPDATE or DELETE, but it cannot synthesize a nuanced merged fact. This requires a different kind of prompt engineering, and the merged result needs to be verified for accuracy.

Scale creates context pressure. A user with 50 memories generates manageable candidate lists. A user with 5,000 memories means the similarity search returns dozens of candidates per new fact. The resolver context window fills up. You need a selection strategy (top-K most similar? most recent? highest importance?) and each strategy has blind spots.

The resolver can be wrong. LLMs make mistakes. The resolver might UPDATE a fact that should have been kept, or ADD a duplicate it should have caught. The history log captures what happened, so mistakes can be undone. But they have to be noticed first, and silent misresolutions are hard to spot.


WHERE THIS GOES NEXT

The contradiction problem is not going to be solved by a single technique. It needs layers. Fast vector similarity for the obvious cases. LLM reasoning for the subtle ones. Possibly graph-based constraints for structured relationships. And definitely better embedding models that understand semantic opposition, not just semantic similarity.

Google's Titans paper (arXiv:2501.00663) introduced a "surprise" mechanism that measures how unexpected new input is relative to existing memory. High surprise triggers priority storage. This could work for contradiction detection too: a fact that contradicts existing memory should score high surprise, triggering deeper conflict analysis. I have not implemented this yet, but the idea fits naturally into the existing architecture.

For now, batch conflict resolution with LLM reasoning catches the majority of real-world contradictions. It is not perfect. The threshold creates blind spots. Subtle contradictions slip through. Preference drift gets oversimplified. But it is a substantial improvement over the default approach, which is to do nothing and hope that similarity search picks the right fact.

Hope is not a conflict resolution strategy.


widemem is open source (Apache 2.0) on GitHub and PyPI. Batch conflict resolution is the default behavior. No configuration needed.