LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 5 · Long-term Memory
1 5-1 Memory Store
2 5-2 Schema Profile
3 5-3 Schema Coll.
4 5-4 Memory Agent
SUM Module Summary
HomeLangGraphModule 5 · Long-term Memory5-1 Memory Store
📓 Notebook 📖 Explained
LANGGRAPH MODULE 5 · LESSON 1

Memory Store: Cross-thread long-term memory system

Use LangGraph Store to build a personalized chatbot that remembers user information across conversations - in-depth explanation of InMemoryStore, namespace design, memory read and write nodes and dual storage compilation mode.

1Review of memory types: short-term vs. long-term memory

In cognitive science, there are many types of memory. In AI applications, the most important distinction isShort-term memory (within-thread)andLong-term memory (cross-thread)

DimensionsShort-term memory (within-thread)Long-term memory (cross-thread)
ScopeWithin a single conversation session (thread)Across all conversation sessions (across threads)
storage mechanismCheckpointer(MemorySaver)LangGraph Store (InMemoryStore/Database)
Store contentA complete state snapshot of the graph (message history, intermediate results)User portraits, preferences, facts (key-value pairs)
identification keythread_iduser_id + namespace + key
analogyWorking memory (current task context)Semantic memory (persistent facts about the user)
core insights

checkpointer solves "what was said in this conversation", and Store solves "who is this user". The combination of the two allows AI to have both continuous conversation context and cross-session user cognition.

2LangGraph Store core API: put/search/get

LangGraph Store is an open sourcePersistent key-value storage base classBaseStore), providing three core methods to manage cross-thread memory.

Initialize InMemoryStore

import uuid
from langgraph.store.memory import InMemoryStore

# 创建内存中的 Store 实例(生产环境可替换为数据库后端)
in_memory_store = InMemoryStore()

put(): write to memory

# 定义命名空间:(user_id, 分类名)
user_id = "1"
namespace_for_memory = (user_id, "memories")

# 生成唯一 key(每条记忆一个 UUID)
key = str(uuid.uuid4())

# value 必须是字典
value = {"food_preference": "I like pizza"}

# 写入 Store
in_memory_store.put(namespace_for_memory, key, value)

search(): Batch search by namespace

# 检索命名空间下的所有记忆
memories = in_memory_store.search(namespace_for_memory)

# 每条记忆的完整结构示例:
# {
#   'value': {'food_preference': 'I like pizza'},
#   'key': 'a754b8c5-...',
#   'namespace': ['1', 'memories'],
#   'created_at': '2024-11-04T22:48:16.727572+00:00',
#   'updated_at': '2024-11-04T22:48:16.727574+00:00'
# }

get(): precise retrieval by namespace + key

# 精确获取单条记忆
memory = in_memory_store.get(namespace_for_memory, key)
memory.dict()  # 返回与 search()[0] 相同的结构
Usage scenarios of put vs get vs search

put: Write/update a single memory (overwrite if the same key is passed in).get: Accurately read the memory of a known key, suitable for fixed key scenarios such as "getting user portraits".search: List all memories under a certain namespace, suitable for batch scenarios such as "obtaining all user preferences".

3Namespace design: organizing memory like a file system

Store'snamespaceIt is a tuple that works like a file system directory path, allowing you to hierarchically isolate different users and different types of memories.

Store namespace structure diagram

Store (InMemoryStore)
├── namespace: ("memory", "user_1")
└── key: "user_memory"{"memory": "The user is Lance and likes riding..."}
├── namespace: ("memory", "user_2")
└── key: "user_memory"{"memory": "The user is Alice and likes to read..."}
# 命名空间:按 user_id 隔离,"memory" 是分类前缀
namespace = ("memory", user_id)

# 固定 key:每个用户只保存一条"汇总画像"记忆
key = "user_memory"

# 读取:先检查记忆是否存在
existing_memory = store.get(namespace, key)

if existing_memory:
    content = existing_memory.value.get('memory')
else:
    content = "No existing memory found."
Design decision: single summary vs. multiple independent memories

This section adopts the "single summary memory" mode: after each conversation, LLM willMerge updatesto the same memory (with the same keyputcoverage). Another mode is to store a UUID key for each fact to form a memory collection (see Lesson 4).

4Chatbot architecture: responsibilities of two memory nodes

The whole picture is very simple:START → call_model → write_memory → END. Every time a user sends a message, it will go through two stages: "response" and "memory update".

Memory Store chatbot graph structure

START
call_model
Read long-term memory → generate personalized responses
write_memory
Reflective Conversation → Update long-term memory
END
nodeResponsibilitiesmemory used
call_modelFrom StorereadUser portrait, inject system prompt words, call LLM to generate personalized responsesRead long-term memory (Store.get)
write_memoryReflect on the new information in this conversation, call LLM to merge and update the user portrait,write StoreRead + write to long-term memory (Store.get / Store.put)
Key design of function signature

Both node functions accept three parameters:state(Picture status),config(Runtime configuration, including user_id),store(BaseStore instance, automatically injected by LangGraph).

5call_model node: reads memory from Store and personalizes response

MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
If you have memory for this user, use it to personalize your responses.
Here is the memory (it may be empty): {memory}"""
from langgraph.store.base import BaseStore
from langchain_core.runnables.config import RunnableConfig

def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
    # Step 1:从 config 中取出 user_id
    user_id = config["configurable"]["user_id"]

    # Step 2:构造命名空间,从 Store 读取记忆
    namespace = ("memory", user_id)
    existing_memory = store.get(namespace, "user_memory")

    # Step 3:提取记忆内容(首次使用时为 None)
    if existing_memory:
        existing_memory_content = existing_memory.value.get('memory')
    else:
        existing_memory_content = "No existing memory found."

    # Step 4:将记忆注入系统提示词,调用 LLM
    system_msg = MODEL_SYSTEM_MESSAGE.format(memory=existing_memory_content)
    response = model.invoke(
        [SystemMessage(content=system_msg)] + state["messages"]
    )
    return {"messages": response}
MessagesState automatically manages short-term memory

state["messages"]The message history in is given byMemorySavercheckpointer is managed automatically - just use the samethread_id, LangGraph will automatically persist historical messages and restore them on the next request.

6write_memory node: reflect on the conversation and update long-term memory

CREATE_MEMORY_INSTRUCTION = """You are collecting information about the user to personalize your responses.

CURRENT USER INFORMATION:
{memory}

INSTRUCTIONS:
1. Review the chat history below carefully
2. Identify new information about the user, such as:
   - Personal details (name, location)
   - Preferences (likes, dislikes)
   - Interests and hobbies
3. Merge any new information with existing memory
4. Format the memory as a clear, bulleted list
5. If new information conflicts with existing memory, keep the most recent version

Based on the chat history below, please update the user information:"""
def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    namespace = ("memory", user_id)
    existing_memory = store.get(namespace, "user_memory")

    if existing_memory:
        existing_memory_content = existing_memory.value.get('memory')
    else:
        existing_memory_content = "No existing memory found."

    # LLM 反思对话,提取并合并用户信息
    system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=existing_memory_content)
    new_memory = model.invoke(
        [SystemMessage(content=system_msg)] + state['messages']
    )

    # 用相同 key 覆盖写入(合并更新)
    store.put(namespace, "user_memory", {"memory": new_memory.content})
Memory is written in-the-hot-path

At the end of every conversation,write_memoryNodes will call LLM synchronously to update the memory, which will increase latency and token consumption. The other is the "offline" update strategy, which writes asynchronously in the background. The trade-offs between the two strategies are fully discussed in Module 5.

7Dual storage compilation mode and configuration injection

Pictures need to be accepted at the same timecheckpointer(short term memory) andstore(Long Term Memory) compiled with two parameters.

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.store.memory import InMemoryStore

builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_node("write_memory", write_memory)
builder.add_edge(START, "call_model")
builder.add_edge("call_model", "write_memory")
builder.add_edge("write_memory", END)

# 长期记忆:跨线程 Store
across_thread_memory = InMemoryStore()
# 短期记忆:线程内 Checkpointer
within_thread_memory = MemorySaver()

# 关键:同时传入 checkpointer(短期)和 store(长期)
graph = builder.compile(
    checkpointer=within_thread_memory,
    store=across_thread_memory
)
# thread_id → MemorySaver;user_id → Store
config = {
    "configurable": {
        "thread_id": "1",
        "user_id": "1"
    }
}

for chunk in graph.stream(
    {"messages": [HumanMessage(content="Hi, my name is Lance")]},
    config,
    stream_mode="values"
):
    chunk["messages"][-1].pretty_print()
How LangGraph automatically injects store parameters

When the node function signature containsstore: BaseStoreparameters, LangGraph will automatically transfer the parameters passed in during compilation when calling the node.storeObject injection. This is related toconfigThe parameter injection mechanism is the same - the framework determines whether to pass in these special parameters through function signature detection.

8Full running demo: Cross-thread memory persistence

Thread 1: Establishing a user profile

Thread ID: "1" | User ID: "1"

Human: Hi, my name is Lance
AI: Hello, Lance! It's nice to meet you. How can I assist you today?

Human: I like to bike around San Francisco
AI: That sounds like a great way to explore the city, Lance! San Francisco has some beautiful routes. Do you have a favorite trail?

Second conversation (Thread 2): Cross-thread memory takes effect

Open a newthread_id, but using the sameuser_id——The robot should be able to remember Lance’s information:

Thread ID: "2" | User ID: "1" — New conversations, old memories

Human: Hi! Where would you recommend that I go biking?
AI: Hi Lance! Since you enjoy biking around San Francisco, here are some fantastic routes:

1. Golden Gate Park — trails and beautiful scenery
2. The Embarcadero — stunning views of the Bay Bridge
3. Marin Headlands — across the Golden Gate Bridge
4. Presidio — network of trails with views of the bay
key verification points

In Thread 2, the robot never sees Lance's name (Thread 2 is completely new dialogue), nor is he told that he likes riding. But becauseuser_id: "1"This information has been recorded in long-term memory,call_modelThe node injects the prompt word after reading it from the Store, so that the AI canAddress users directly by name and give targeted riding suggestions

Design summary

componentstypeidentifiereffect
MemorySaverCheckpointerthread_idPersistent message history to support conversation interruption and recovery
InMemoryStoreStoreuser_id + namespace + keySave user personas across threads to personalize all future conversations
call_modelnode(read)Store.get()Read and inject user personas at the beginning of each conversation
write_memoryNode (write)Store.put()Update user portrait after each conversation
Production environment migration

This section usesInMemoryStore, the data is lost after the process is restarted. In the production environment, it can be replaced by a database-based Store implementation (such as PostgreSQL, Redis). Only the initialization method needs to be changed, and the node code and graph structure remain completely unchanged.