Four Memory Types, One Trust Problem

Your agent greets a returning user with, “Welcome back — still looking for gardening gifts for your mom?” The user replies, “Actually, she’s more into cooking now.”

Your agent now has one well-established memory (“mom likes gardening”) and one fresh claim (“mom likes cooking”), and about half a second to decide which one to keep, which to archive, and whether to trust the new one at all.

That half-second is what “memory-aware” actually means. Not the storage — the judgment.

Are all your memories really the same shape?

Think about four things you remember right now. The open browser tab you were about to close. Last Tuesday’s dentist appointment. The atomic number of oxygen. The way your fingers reach for ⌘-T without you looking.

Are those four the same kind of memory?

Almost certainly not. They differ in how long they last, how you retrieve them, whether they’re about a specific event or a timeless fact, and — crucially — how you know they’re true. You don’t verify muscle memory the way you verify a schedule.

Memory-aware agents, when they’re honest about what they’re doing, split the same four ways.

  • Short-term — the current session. Unfiltered, but with no persistence.
  • Episodic — records of past sessions. A court transcript: verbatim and high-fidelity, but only useful if you know which one to pull.
  • Semantic — extracted facts about the user. A dossier assembled from many past interviews: structured and searchable, but only as trustworthy as the interviewer who filled it in.
  • Procedural — behavioral patterns. A track record: statistically informative, individually meaningless. Useful for saying “this user usually compares products before buying,” not for saying anything about this moment.

Each of these has its own trust semantics — its own answer to how much should the agent believe what it just retrieved? Short-term is trusted absolutely (the user just said it). Episodic is trusted verbatim (it’s a recording). Procedural is trusted statistically (it describes patterns, not the present). Semantic is where things get interesting, because facts extracted from conversation can be wrong at extraction, go stale, or contradict each other.

This post is about the dossier. That’s where trust gets fragile fastest, and where the engineering lives.

What would a trustworthy fact even look like?

Suppose you had to write down one fact about a user in a way another program could reason with. Not a sentence — sentences don’t compose. What structure would you use?

The standard answer is the subject-predicate-object triple: (user.mom, likes, gardening). Three fields, machine-comparable, and easy to check for contradiction (two triples with the same subject and predicate but different objects are a candidate contradiction). Every semantic memory system worth taking seriously stores something like this, whatever it calls the fields.

But an SPO triple alone doesn’t tell you whether to believe the fact. That takes metadata:

CREATE TABLE semantic_memory (
    fact_id            TEXT PRIMARY KEY,
    user_id            TEXT NOT NULL,
    subject            TEXT NOT NULL,          -- "user.mom"
    predicate          TEXT NOT NULL,          -- "likes"
    object             TEXT NOT NULL,          -- "gardening"
    confidence         REAL DEFAULT 1.0,       -- entailment score at extraction
    frequency          INTEGER DEFAULT 1,      -- times reinforced
    source_episode_id  TEXT,                   -- pointer to the conversation it came from
    first_seen         DATETIME,
    last_seen          DATETIME,
    status             TEXT DEFAULT 'active'   -- active | contradicted | user_confirmed | archived
);

Notice what those fields, together, actually answer. Not “does the agent know this?” — but “how much should the agent believe it, right now, if pushed?” Confidence measures how sure extraction was. Frequency measures how many independent conversations reinforced it. source_episode_id gives a traceable path back to the exact exchange it came from — without which none of the others are auditable.

Trust, in this schema, is a first-class field. Not a vibe.

How do you know a fact you just extracted is actually true?

Give an LLM a conversation and ask it to extract facts, and you’ll get facts. Some of them will be right. Some will be plausible but not actually supported by the conversation. Some will be creative inferences the model made because it was asked to be helpful.

If you trust the LLM’s extraction blindly, your dossier ends up half-fabricated within a hundred sessions.

The fix is a separation of concerns older than LLMs: generate then verify. Let one model be creative — extract candidate facts. Let a different, more skeptical model check whether the source actually supports each candidate. For facts extracted from conversation, the second model is an NLI (natural language inference) classifier — a small, cheap model whose one job is to score whether text A entails text B.

# Step 1: LLM generates candidate facts
candidates = llm.extract_facts(
    conversation,
    instruction="Extract explicit facts as (subject, predicate, object) triples."
)
# → [(user.mom, likes, gardening),
#    (user, budget, ~2000),
#    (user.mom, has_birthday_in, March)]

# Step 2: NLI verifies each candidate against the source
verified = []
for fact in candidates:
    hypothesis = f"{fact.subject} {fact.predicate} {fact.object}"
    score = nli_model.entailment_score(
        premise=conversation.text,
        hypothesis=hypothesis
    )
    if score >= 0.7:
        verified.append((fact, score))
# → [(mom likes gardening,      0.92),
#    (user budget ~2000,         0.85)]
#    # mom's birthday in March:  0.61 — dropped

# Step 3: store the survivors, tagging confidence with the NLI score
for fact, score in verified:
    semantic_store.write(fact, confidence=score, source_episode_id=ep_id)

Two things worth flagging. First, the NLI score becomes the stored confidence — the model that judged the fact is the model whose opinion becomes the “how sure are we?” metadata. That’s honest bookkeeping. Second, an LLM was asked to be creative, a different model was asked to be skeptical, and only their intersection made it into the dossier. Never trust extraction alone.

What should the agent do when a new fact contradicts an old one?

Back to the opening. The dossier says (user.mom, likes, gardening). The user just said mom likes cooking. What should happen?

The intuitive answers are all wrong in interesting ways. “Always believe the user” — then a single ambiguous utterance can overwrite a fact reinforced across five conversations. “Always defer to the older fact” — then the user is trapped in a stale identity the agent won’t let them evolve out of. “Ask every time” — annoying, and defeats the point of having memory. None of these is a real answer, because a real answer requires weighing the evidence, not choosing a winner in advance.

The pipeline that does the weighing has three stages.

Stage 1 — narrow the search. The dossier might have hundreds of facts about this user. You don’t want to run an expensive contradiction check against all of them. A bi-encoder embeds the new fact and retrieves the top-5 most semantically similar existing facts. Cheap, high-recall filter.

Stage 2 — score for contradiction. For each of those 5 candidates, an NLI model — the same kind used for extraction, but now scoring the contradiction label — checks whether the new fact actually contradicts the old one. “Mom likes cooking” and “mom likes gardening” might not contradict at all (she can like both). “Mom lost interest in gardening” and “mom likes gardening” do.

Stage 3 — resolve using metadata. For any pair the NLI flags as a real contradiction, compare the trust metadata the schema was designed to carry:

def resolve(old_fact, new_fact):
    # Case 1: old fact weakly supported, new fact confidently extracted.
    if old_fact.frequency == 1 and new_fact.confidence > old_fact.confidence:
        archive(old_fact, status="contradicted")
        write(new_fact)
        return "auto_replaced"

    # Case 2: old fact well-established, new fact a single mention.
    if old_fact.frequency >= 3 and new_fact.frequency == 1:
        write(new_fact, status="pending_confirmation")
        flag_for_user_confirmation(old_fact, new_fact)
        return "user_asked"

    # Case 3: default — recency wins, but nothing is deleted.
    archive(old_fact, status="superseded")
    write(new_fact)
    return "recency_default"

Case 2 is the interesting one. When the evidence is genuinely mixed — an old, well-reinforced belief versus a fresh, confident-but-single claim — the honest move is neither to auto-replace nor to auto-ignore. It’s to defer to the source: ask the user, in the next session, whether the change is real. “Last time you mentioned your mom is more into cooking now. Should I update my recommendations?” That’s why status = 'user_confirmed' exists as a state at all — it’s how the system records that a human, not a heuristic, closed the loop.

One important call-out: this resolution strategy is one design choice, not the design choice. The pipeline — bi-encoder narrows, NLI verifies, metadata resolves — is broadly reusable. The policy (frequency thresholds, whether to auto-replace, when to ask) is domain-specific and belongs in config, not code. A medical or legal agent should probably never auto-replace a fact without user confirmation, no matter how confident the new extraction. A marketing personalization agent might prefer the opposite — freshness wins fast, staleness is the failure mode to avoid. Same mechanism, opposite defaults. What the resolution logic really encodes is whose priorities the agent optimizes for when its beliefs collide — and that’s a decision for the domain, not the pipeline.

So — whose version of the user is real?

There isn’t one. Semantic memory is a moving snapshot of who the agent currently believes the user to be, and the honest thing a memory-aware agent can do is track which version of the user it’s using and why. That’s what confidence, frequency, and source_episode_id together are for. They aren’t decoration. They’re the evidence trail that lets the agent — and, when it matters, the developer debugging a bad interaction — reconstruct where a belief came from.

The reframe that falls out of all of this: memory-aware isn’t storage. It’s epistemics. Each of the four systems answers “how much should I trust this?” differently, and the moment where they disagree — the moment “mom likes gardening” and “mom likes cooking” collide — isn’t a bug to suppress. It’s the part of the system that decides whether the user ends up feeling remembered or feeling stereotyped.

The next time an agent gets your context wrong, the tempting fix is a better model. The more useful question is: was the fact wrong at extraction, wrong at retrieval, or wrong at resolution? Those are three different problems, and they don’t share a fix.