Scope
LLMs are stateless — each call sees only what’s in the prompt. “Memory” is everything you do to make an app act like it remembers: what to keep in the context window now (short-term), what to persist in an external store and retrieve later (long-term), and — the 2025-26 frontier — letting the agent itself decide what to write, update, and forget. Covers conversation-memory strategies, the episodic/semantic/procedural taxonomy, vector + temporal-graph backends, the frameworks (Mem0, Letta/MemGPT, Zep, LangMem), retrieval + forgetting/decay, evaluation (LongMemEval, LoCoMo), and the risk surface (memory bloat, stale facts, PII/GDPR, memory poisoning). For the agent loop that uses memory, see 08 - Agents & Agentic Systems; for the attack surface, see 14 - Safety, Security & Risk.
Related: Gen AI Engineer, 08 - Agents & Agentic Systems, 14 - Safety, Security & Risk, LangGraph Senior Prep
1. The concept: the model has no memory — you build it
A chat model is a pure function: tokens in, tokens out. It has no state between calls. The “memory” a user perceives is an illusion you engineer by deciding what text lands back in the next prompt. Two regimes:
| Short-term memory | Long-term memory | |
|---|---|---|
| Where | inside the context window (the prompt itself) | external store (DB / vector index / graph) |
| Lifetime | one conversation / thread | across sessions, days, users |
| Mechanism | you re-send prior turns | you retrieve relevant past facts and inject them |
| Bounded by | context length + cost/latency | storage + retrieval quality |
| Failure mode | truncation, “lost in the middle” | retrieval miss, stale/contradictory facts |
Mental model
Short-term memory = the model’s working set (RAM). Long-term memory = disk you page in on demand. The whole discipline is deciding what’s worth promoting to RAM right now, and what’s worth writing to disk for later. Memory is not RAG over documents — it’s RAG over the interaction history and learned facts about the user/agent.
2. Conversation-memory strategies (short-term)
The classic four ways to fit a growing conversation into a finite window. (Pattern names from the LangChain era; the patterns outlive the deprecated classes — see §8.)
| Strategy | What it keeps | Pros | Cons |
|---|---|---|---|
| Buffer | every turn, verbatim | lossless, simple | overflows the window; cost grows every turn |
Sliding window (buffer-window, last k) | last k turns | bounded cost, recency-correct | silently forgets anything older than k |
| Summary | a running LLM-written summary of the whole chat | very compact, unbounded history | lossy; summarization drift; extra LLM call per update |
| Summary-buffer (hybrid) | summary of old turns + verbatim recent turns | best of both — detail near, gist far | most moving parts; tune the cutoff |
Default that ages well
Summary-buffer is the pragmatic default for assistants: verbatim last few turns (precision where it matters) plus a rolling summary (cheap long tail). It also sets up the consolidation pattern that defends against memory poisoning (§9).
Summarization is lossy and can hallucinate
A summary can drop the one detail the user later asks about, or invent one. Never summarize a tool-call result, a number, an ID, or a legal/medical fact — keep those verbatim or push them to long-term store. Summaries are for narrative, not for facts you’ll be held to.
3. Vector / semantic memory (long-term retrieval)
The standard long-term backend: embed each memory (a fact, a turn, a summary) into a vector, store it, and at query time embed the current input and pull the top-k most similar memories into the prompt. It’s 07 - RAG machinery pointed at interaction history instead of documents.
- Granularity matters. Storing raw turns gives precision but bloats fast and retrieves noise. Storing extracted facts (“user is vegetarian”, “prefers metric units”) retrieves far cleaner — this extraction step is what Mem0 and LangMem automate.
- Scoping is mandatory. Every memory must carry a
user_id(and oftenagent_id/thread_id) so retrieval is filtered per-user. Without it you leak one user’s memories into another’s prompt — a privacy incident (§9). - Hybrid retrieval (vector + metadata filter + recency) beats pure cosine similarity; relevance alone surfaces old, superseded facts.
4. The memory taxonomy: episodic / semantic / procedural
Borrowed from cognitive science, now the standard vocabulary (LangMem, Letta, and the research literature all use it). Knowing the three types — and that they need different stores and write policies — is a senior signal.
| Type | What it holds | Example | Typical store | Write cadence |
|---|---|---|---|---|
| Episodic | specific past events/experiences (“what happened”) | “Last Tuesday the user asked to cancel order #441” | vector store / event log | append per event |
| Semantic | distilled facts & knowledge (“what is true”) | “User is allergic to penicillin” | vector store / KV / graph | extract + dedupe + update |
| Procedural | how to behave (“how to do it”) | the agent’s own system prompt / few-shot rules it has learned | prompt / rules block | rewrite/optimize, rarely |
Procedural memory = the agent editing its own instructions
The most modern and least obvious type: the agent rewrites its own system prompt or few-shot examples based on feedback (“the user hates verbose replies → tighten the persona”). LangMem supports this explicitly via prompt optimization. It’s how an agent learns a behavior rather than just recalling a fact.
Entity memory is a focused slice of semantic memory: track a structured record per entity (person, project, company) and update its fields as new turns arrive — closer to a CRM row than a free-text fact. Good when the domain has stable entities you keep referring to.
5. The frameworks (2025-26)
You rarely build memory from scratch. The current landscape:
| Framework | Core idea | Backend | Distinctive strength |
|---|---|---|---|
| Mem0 | LLM extracts facts from each turn, then a decision step issues ADD/UPDATE/DELETE/NOOP to keep the store consistent | vector (+ optional graph variant, Mem0^g) | production-oriented; reports large token/latency savings vs stuffing full history |
| Letta (was MemGPT) | LLM-as-OS: agent self-edits in-context memory blocks; pages between tiers via tool calls | core (in-context) / recall / archival tiers | self-editing memory blocks; stateful-agent server |
| Zep (engine: Graphiti) | temporal knowledge graph — facts are edges with validity intervals, so superseded facts are invalidated, not deleted | temporal KG (bi-temporal) | tracks how facts change over time; non-lossy history |
| LangMem | SDK over LangGraph’s store; supports semantic and procedural (prompt-optimizing) memory | LangGraph BaseStore | native to LangGraph; the only one that rewrites the agent’s own prompt |
How to choose
- Need to know when a fact was true / track changes over time (e.g. “what was the user’s plan last month”) → Zep/Graphiti temporal graph.
- Want a drop-in extract-and-consolidate layer with low token cost → Mem0.
- Building stateful agents that manage their own memory tiers → Letta.
- Already on LangGraph and want procedural/semantic memory native → LangMem.
Latency varies wildly
LLM-driven memory (extraction, self-edit, prompt optimization) costs inference on the write path. LangMem’s procedural optimization has been measured in the tens of seconds at p95 — fine for a background/batch job, unusable inline in a chat turn. Push heavy memory writes off the request path.
6. Agentic memory: agents that self-edit (the frontier)
The 2025-26 shift mirrors the reasoning-model shift in 02 - Decoding, Sampling & Reasoning Controls: from you hand-coding “summarize after N turns” to the agent deciding what to remember.
- The agent has memory tools —
add_memory,update_memory,delete_memory,search_memory— and calls them inside its reasoning loop when it judges something worth keeping. - MemGPT/Letta pioneered this: the model itself decides when to write to core memory or page archival memory in/out. Mem0’s full-agentic mode lets the LLM choose what to store and how to phrase the retrieval query. A-Mem (2025) generates linked, self-organizing memory notes.
- Upside: adaptive, no brittle hand-tuned rules. Downside: memory quality = the model’s judgment, every operation costs tokens, and a bad write persists and contaminates future turns.
Self-editing memory is also the attack surface
The same loop that lets an agent write its own memory lets an attacker trick it into writing a poisoned memory (§9). Autonomy on the write path and security on the write path are the same design problem.
7. Retrieval over memory + forgetting / decay
A store that only grows is a store that gets slower, costlier, and noisier. Mature memory systems forget on purpose.
Retrieval scoring — don’t rank by similarity alone. The well-known composite (from generative-agents work) blends three signals:
score = w_sim · relevance(query, mem) # semantic match
+ w_rec · recency(now − last_access) # exponential time decay
+ w_imp · importance(mem) # LLM-rated salience 1–10
Forgetting strategies:
| Strategy | How | When |
|---|---|---|
| TTL / expiry | drop memories older than X | ephemeral context (today’s task) |
| Importance-weighted decay | low-importance + old → evict first | general long-term store |
| Consolidation | every N turns, summarize raw turns into facts, then delete the raw | bound growth + shrink attack window |
| Supersession | mark old fact invalid when a newer one contradicts (temporal graphs do this natively) | facts that change (“moved to Berlin”) |
Forgetting is a feature, not a bug
Importance-weighted decay keeps the store small enough that retrieval stays sharp, and limits how long a poisoned or stale entry can live. “Remember everything” is the wrong default.
8. ⚠️ LangChain’s classic memory classes are deprecated
A common interview trap and a real migration in live codebases.
ConversationXMemoryis dead — use LangGraph persistence + LangMemThe old
ConversationBufferMemory,ConversationBufferWindowMemory,ConversationSummaryMemory,ConversationSummaryBufferMemoryclasses were deprecated in LangChain v0.3.1 and slated for removal at v1.0. They had no real persistence, no per-conversation isolation, no production story.The replacement is LangGraph persistence, with two primitives:
- Checkpointer — in-thread (one
thread_id) conversation history; this is short-term memory done right (durable, resumable).BaseStore— cross-thread, user-scoped long-term facts.Layer LangMem on top for LLM-driven extraction and procedural (prompt-optimizing) memory.
The strategies in §2 (buffer/window/summary/summary-buffer) are still correct — you now implement them over the checkpointer/store instead of the retired classes.
9. Caveats & risk mitigation
- Memory bloat → retrieval noise. An ever-growing store buries the relevant memory under near-duplicates; top-k starts returning junk and the model gets worse. Mitigate: extract facts not raw turns; consolidate every N turns then delete the raw; importance-weighted decay; dedupe on write.
- Stale / contradictory facts. “User lives in Paris” and “User moved to Berlin” both retrieved → the model picks one at random. Mitigate: supersession on write (UPDATE/invalidate, not just ADD — Mem0’s update step, Zep’s temporal invalidation), and store a timestamp so newer wins.
- PII retention + GDPR right-to-deletion. A long-term store quietly accumulates personal data, and Article 17 (right to erasure) means a user can demand it all be deleted — including copies baked into summaries and graph edges. Mitigate: scrub/redact PII before write, strict per-user scoping (
user_idon every record), documented retention/TTL, and a real deletion API that purges the raw store and derived artifacts. Treat “we can’t find all their data” as a compliance failure, not a tech detail. See 14 - Safety, Security & Risk. - Prompt injection writing poisoned memories. This is the sharpest agentic-memory risk. An attacker embeds instructions in ordinary-looking input; the agent’s own self-edit loop stores them, and they fire on future turns — even for other users of a shared store. The MINJA attack (NeurIPS 2025) achieved this via query-only interaction with reported >95% injection success. A poisoned memory in a multi-agent system can propagate downstream fast. Mitigate:
- Validate writes — schema/policy-check and (LLM- or rule-) classify candidate memories before they persist; reject instruction-like or out-of-policy content.
- Separate read and write paths — agent reads from a validated snapshot; writes land in a staging area and only promote after review, so in-session poisoning can’t immediately steer behavior.
- Consolidate-then-delete shrinks the window a raw poisoned turn can survive.
- Per-user scoping stops cross-user contamination; read-only audit snapshots give you a clean baseline to diff against.
- Self-editing = trusting model judgment. Agentic writes are only as good as the model; a confidently-wrong extraction persists. Mitigate: keep a human-/eval-in-the-loop on high-stakes memories; log every write with provenance so you can trace and roll back.
- Cost & latency on the write path. Extraction, consolidation, and prompt optimization are extra LLM calls. Mitigate: do them async/batched off the request, not inline — tie to 15 - Cost, Latency & Performance.
10. In practice
# LangGraph short-term (checkpointer) + cross-thread long-term (store) + LangMem
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.store.postgres import PostgresStore
checkpointer = PostgresSaver(conn) # in-thread conversation history (short-term)
store = PostgresStore(conn) # cross-thread, user-scoped facts (long-term)
# Always namespace long-term memory PER USER (scoping = privacy + isolation)
namespace = ("memories", user_id)
store.put(namespace, key="diet", value={"fact": "vegetarian"})
hits = store.search(namespace, query="what does the user eat", limit=5)# Mem0: extract-and-consolidate layer (ADD/UPDATE/DELETE decided by the LLM)
from mem0 import Memory
m = Memory()
# write path: facts get extracted from the turn; updates supersede, not duplicate
m.add("I switched from Paris to Berlin last month", user_id="u_123")
# read path: scoped, semantic retrieval into the prompt
context = m.search("where does the user live", user_id="u_123")# Defensive write: validate before persisting (anti-poisoning, §9)
def safe_write(candidate: str, user_id: str):
if looks_like_instruction(candidate) or contains_pii(candidate):
return # reject / scrub — don't store attacker-controlled directives
staging.put(("staging", user_id), value=candidate) # promote after review11. Sources
- Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory (arXiv 2504.19413)
- Letta — Memory Blocks: The Key to Agentic Context Management
- MemGPT: Towards LLMs as Operating Systems (arXiv 2310.08560)
- Zep: A Temporal Knowledge Graph Architecture for Agent Memory (arXiv 2501.13956)
- Graphiti — Zep’s open-source temporal knowledge graph engine
- LangMem SDK
- LangChain Docs — Memory overview (LangGraph persistence)
- A-Mem: Agentic Memory for LLM Agents (arXiv 2502.12110)
- LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory
- LoCoMo — Evaluating Very Long-Term Conversational Memory of LLM Agents
- A Practical Memory Injection Attack against LLM Agents (MINJA, NeurIPS 2025) — arXiv 2503.03704
- Memory Poisoning Attack and Defense on Memory-Based LLM Agents (arXiv 2601.05504)