Long-term memory with stores

LangGraph · Advanced · 14 min

Checkpointers remember one conversation; a store lets an agent remember a user across all of them.

A checkpointer gives short-term memory: one thread. To remember facts across threads (a user's preferences, past decisions, learned instructions) you need long-term memory. LangGraph provides stores for this.

A store holds JSON-like documents organized by namespace, a tuple like (user_id, "memories"), which works like a folder path. Each item has a key and a value dict.

  • store.put(namespace, key, value) saves or overwrites,
  • store.get(namespace, key) reads one item,
  • store.search(namespace, query=..., limit=...) lists or searches items.

Compile with builder.compile(checkpointer=..., store=InMemoryStore()). Nodes reach it through runtime.store (the Runtime parameter from the runtime-context lesson), and the user ID usually comes from runtime.context. Async nodes use aput, aget and asearch.

Semantic search. Create the store with an index config (an embedding model and its dimensions) and search(namespace, query="food preferences") returns items by meaning, not exact words.

Design questions you must answer:

  • What to remember: facts about the user (profile), past episodes, or instructions the agent refined.
  • When to write: in the main flow (simple, adds latency) or in a background step after the reply.
  • How to scope namespaces so one user can never read another's memories.

Use InMemoryStore for development; in production use a database-backed store (for example Postgres), or the store LangSmith Deployment provides.

Example

import uuid
from dataclasses import dataclass
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore


@dataclass
class Context:
    user_id: str


def remember_and_reply(state: MessagesState, runtime: Runtime[Context]) -> dict:
    ns = (runtime.context.user_id, "memories")
    text = state["messages"][-1].content
    if text.startswith("remember "):
        runtime.store.put(ns, str(uuid.uuid4()), {"fact": text.removeprefix("remember ")})
    facts = [item.value["fact"] for item in runtime.store.search(ns)]
    return {"messages": [{"role": "assistant", "content": f"I know: {facts}"}]}


b = StateGraph(MessagesState, context_schema=Context)
b.add_node(remember_and_reply)
b.add_edge(START, "remember_and_reply")
b.add_edge("remember_and_reply", END)
graph = b.compile(checkpointer=InMemorySaver(), store=InMemoryStore())

ask = lambda text, thread: graph.invoke({"messages": [{"role": "user", "content": text}]},
                                        {"configurable": {"thread_id": thread}}, context=Context("ana"))
ask("remember I like tea", "t1")
print(ask("what do you know?", "t2")["messages"][-1].content)   # I know: ['I like tea']  (new thread!)

Remember

  • Checkpointer = per-thread memory; store = cross-thread memory
  • Items live in namespaces (tuples) with put/get/search
  • Nodes use runtime.store; scope namespaces by user
  • Configure an index for semantic search

Watch out for

  • Using a namespace that doesn't include the user ID, letting users read each other's memories.

Check yourself

  1. A fact saved in thread A must be available in thread B for the same user. What do you use?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.langchain.com/oss/python/langgraph/stores