← all posts
// agents · langgraph

Long-term memory in LangGraph: the Store, and what I regret storing

A checkpointer gives a LangGraph agent memory the way a bookmark gives a book memory: within one thread, resume works beautifully, and across threads there's nothing. The analyst who tells our report assistant on Tuesday to stop opening with boilerplate starts a fresh thread on Thursday and gets boilerplate. Cross-thread memory is what the Store is for.

The Store is a namespaced key-value layer with optional semantic search. You compile the graph with both a checkpointer and a store, nodes get the store handed to them, namespaces are tuples like ("prefs", analyst_id), puts upsert by key, and with an embedding index configured, search does similarity over what you've stored. Postgres-backed in production, in-memory in tests. The API surface I actually use is two calls:

store.put(("prefs", uid), "report_style", {"fact": "exec summary first, no boilerplate"})
store.search(("facts", uid), query="which sectors does she cover", limit=3)

the part that worked

Preference memory, and it worked embarrassingly fast. A small extraction node watches each exchange; when an analyst states a durable preference (summary first, tables over prose, flag UK entities), it writes a fact keyed by subject into their prefs namespace. Next thread, the assistant loads prefs before the first token. About thirty lines of code. The first time it carried across threads, the analyst asked, a little suspiciously, what else it remembered. Fair question, as it turned out.

The keyed upsert is the quiet win: "report_style" written twice keeps the newer value, so a correction replaces the mistake instead of coexisting with it.

the part I regret

Late May, I got greedy.

If a little memory helped, raw memory should help more, so I started dumping conversation snippets into a memories namespace wholesale and let semantic search sort it out at read time. For about a week it looked brilliant. Recall for free, no modeling work.

Then an analyst rotated coverage (off consumer fintech, onto payments infrastructure), and the assistant kept asserting her old portfolio for two more weeks, because a March snippet said so and its wording matched the queries better than the June correction did. Around the same time, two stored snippets flatly disagreed about a client's reporting cadence, quarterly versus annual, and search returned whichever matched the phrasing of that day's question.

Semantic search ranks by similarity, not by truth, and a stale fact doesn't smell stale to an embedding.

The root cause wasn't retrieval quality. Raw snippets have no identity (nothing to overwrite, no way for a new statement to retire an old one), so contradictions accumulate forever and the ranker picks between them on phrasing. Append-only memory plus similarity search equals confident staleness, delivered fluently.

curation, keys, forgetting

What runs now is stricter and smaller.

Facts, not transcripts. On a small, cheap model, the extraction step distills discrete statements: subject, value, updated_at, a provenance pointer back to the source thread. One subject, one key; a write retires the previous value by construction. When two facts on different keys still conflict, retrieval sorts by updated_at and the newer one wins mechanically, no judgment call at read time.

Forgetting is a feature. Preferences keep a long shelf life, project facts expire after 45 days unless re-asserted, and anything the extractor marked unconfirmed dies within a week: TTL where the store backend supports it, a nightly sweep where it doesn't. Deleting memory felt vaguely wrong for about a day. Then retrieval quality visibly improved and I got over it.

Raw transcripts still exist, in logs, where they belong. Memory is the index card, not the recording.

what the Store is and isn't

I like the Store precisely because it's boring: a KV with search, cheap to reason about. That's also its limitation: no relations, no cross-namespace joins, no contradiction detection. Every bit of discipline above is me compensating in application code for structure the Store doesn't have. If your facts and their relationships are the actual product, you've left Store territory and entered the knowledge graph conversation; for where cross-thread memory sits among the other scopes and stores, agent memory architecture is the map I wish I'd drawn first.

My rule after a spring of this: store less than feels safe, and let most of it expire. Key everything by subject so the newest write wins. The assistant remembers less than it did in May. And it's been wrong about a person exactly once since.

#langgraph#memory#agents