Module 8: Memory and Persistence
Long-term Memory: Store and Cross-Session
Capsule overview
Long-term memory stores information that persists across different conversations. Unlike checkpoints (which track a single run), long-term memory accumulates knowledge over time — user preferences, topics from past research, learned patterns.
In capsules 03-06 you learned to save and restore the state of ONE run with checkpointing. That solves "the process crashed, I lost everything." But it doesn't solve "the user comes back tomorrow and the agent remembers nothing." Long-term memory solves that.
Short-term vs long-term: the fundamental distinction
They're two completely separate mechanisms:
| Aspect | Short-term (Checkpoints) | Long-term (Store) |
|---|---|---|
| What it saves | The state of ONE run | Knowledge from ALL runs |
| Scope | A specific thread_id | A user, project, or system |
| Example | "At step 3, it searched 2 sources" | "This user prefers academic sources" |
| Mechanism | MemorySaver / PostgresSaver | InMemoryStore / PostgresStore |
| Analogy | A document's draft | Your personal notebook |
The checkpointer answers: "What happened in THIS conversation?" The store answers: "What do I know about this user from ALL their conversations?"
Session 1 (thread_001): Session 2 (thread_002):
┌─────────────────────┐ ┌─────────────────────┐
│ Checkpoint 1 │ │ Checkpoint 1 │
│ Checkpoint 2 │ │ Checkpoint 2 │
└─────────────────────┘ └─────────────────────┘
│ │
└──────────┬─────────────────────┘
▼
┌─────────────────────┐
│ Long-term Store │
│ (preferences, │
│ history, patterns)│
└─────────────────────┘
Checkpoints are vertical — inside one session. The store is horizontal — it cuts across every session.
InMemoryStore: the store for development
LangGraph ships InMemoryStore as an in-memory implementation. Perfect for development and testing — fast, no dependencies, but it's lost if the process dies.
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
print(type(store))
# Expected output:
# <class 'langgraph.store.memory.InMemoryStore'>
Organizing with namespaces
Data is organized with namespaces — tuples of strings that work like directories:
("users", "mike", "preferences") → mike's preferences
("users", "mike", "history") → mike's history
("users", "ana", "preferences") → ana's preferences (isolated from mike's)
("projects", "research-ai", "notes") → the project's notes
The Store's core operations
put and get
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
namespace = ("users", "mike", "preferences")
store.put(namespace, "sources", {"value": ["academic", "arxiv"], "reason": "Prefers peer-reviewed"})
store.put(namespace, "format", {"value": "bullet_points"})
item = store.get(namespace, "sources")
print(f"Key: {item.key}")
print(f"Value: {item.value}")
print(f"Namespace: {item.namespace}")
missing = store.get(namespace, "nonexistent")
print(f"Nonexistent: {missing}")
# Expected output:
# Key: sources
# Value: {'value': ['academic', 'arxiv'], 'reason': 'Prefers peer-reviewed'}
# Namespace: ('users', 'mike', 'preferences')
# Nonexistent: None
put takes a namespace (tuple), a key (string), and a value (dict). If the key already exists, it overwrites. get returns an Item with .key, .value, .namespace, .created_at, .updated_at, or None if it doesn't exist.
search and delete
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
namespace = ("users", "mike", "preferences")
store.put(namespace, "sources", {"value": ["academic"]})
store.put(namespace, "format", {"value": "bullet_points"})
store.put(namespace, "language", {"value": "es"})
items = store.search(namespace)
print(f"Items: {len(items)}")
for item in items:
print(f" {item.key}: {item.value}")
store.delete(namespace, "language")
print(f"\nAfter delete: {len(store.search(namespace))} items")
# Expected output:
# Items: 3
# sources: {'value': ['academic']}
# format: {'value': 'bullet_points'}
# language: {'value': 'es'}
#
# After delete: 2 items
search returns every item in a namespace. It accepts limit and offset for pagination.
3 concrete use cases
Case 1: Remember the user's favorite sources
After several research sessions, the agent notices you always ask for more detail from academic sources. It prioritizes arXiv automatically.
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
def record_source_preference(store, user_id: str, source_type: str):
namespace = ("users", user_id, "source_preferences")
existing = store.get(namespace, source_type)
count = (existing.value.get("count", 0) + 1) if existing else 1
store.put(namespace, source_type, {"count": count, "source": source_type})
def get_preferred_sources(store, user_id: str) -> list[str]:
namespace = ("users", user_id, "source_preferences")
items = store.search(namespace)
if not items:
return ["web", "academic", "news"]
sorted_sources = sorted(items, key=lambda x: x.value["count"], reverse=True)
return [item.value["source"] for item in sorted_sources]
for _ in range(5):
record_source_preference(store, "mike", "academic")
for _ in range(2):
record_source_preference(store, "mike", "web")
record_source_preference(store, "mike", "news")
preferred = get_preferred_sources(store, "mike")
top = store.get(("users", "mike", "source_preferences"), preferred[0])
print(f"Preferred sources: {preferred}")
print(f"Agent: 'I'll prioritize {preferred[0]} ({top.value['count']} selections).'")
# Expected output:
# Preferred sources: ['academic', 'web', 'news']
# Agent: 'I'll prioritize academic (5 selections).'
Case 2: Build up a research profile
After several sessions, the agent knows your areas of interest and can suggest related topics.
from langgraph.store.memory import InMemoryStore
from datetime import datetime
store = InMemoryStore()
def record_research(store, user_id: str, topic: str):
topics_ns = ("users", user_id, "topics")
existing = store.get(topics_ns, topic)
if existing:
store.put(topics_ns, topic, {"count": existing.value["count"] + 1, "last": datetime.now().isoformat()})
else:
store.put(topics_ns, topic, {"count": 1, "last": datetime.now().isoformat()})
def get_profile(store, user_id: str) -> list[tuple[str, int]]:
topics = store.search(("users", user_id, "topics"))
return sorted([(t.key, t.value["count"]) for t in topics], key=lambda x: x[1], reverse=True)
for topic in ["RAG techniques", "prompt engineering", "RAG techniques", "AI agents", "RAG techniques", "AI agents"]:
record_research(store, "mike", topic)
profile = get_profile(store, "mike")
print("Research profile:")
for topic, count in profile:
print(f" {topic}: {count} sessions")
print(f"\nAgent: 'Your areas of interest are {profile[0][0]} and {profile[1][0]}.'")
# Expected output:
# Research profile:
# RAG techniques: 3 sessions
# AI agents: 2 sessions
# prompt engineering: 1 sessions
#
# Agent: 'Your areas of interest are RAG techniques and AI agents.'
Case 3: Store report format preferences
The user says "I prefer bullet points." The agent remembers it forever.
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
def save_format_pref(store, user_id: str, key: str, value: str):
store.put(("users", user_id, "format_prefs"), key, {"value": value})
def get_format_config(store, user_id: str) -> dict:
defaults = {"structure": "paragraphs", "detail_level": "standard", "max_findings": 5}
items = store.search(("users", user_id, "format_prefs"))
user_prefs = {item.key: item.value["value"] for item in items}
return {**defaults, **user_prefs}
save_format_pref(store, "mike", "structure", "bullet_points")
save_format_pref(store, "mike", "detail_level", "detailed")
save_format_pref(store, "mike", "max_findings", 8)
config = get_format_config(store, "mike")
print(f"Format config: {config}")
print(f"Agent: 'Formatting with {config['structure']}, level {config['detail_level']}.'")
# Expected output:
# Format config: {'structure': 'bullet_points', 'detail_level': 'detailed', 'max_findings': 8}
# Agent: 'Formatting with bullet_points, level detailed.'
Accessing the Store from a graph (StateGraph)
In a LangGraph graph, the store is injected into the nodes automatically when you compile with store=:
from typing import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, START, END
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
class State(TypedDict):
user_input: str
response: str
def greet_user(state: State, config: RunnableConfig, *, store: BaseStore) -> dict:
user_id = config["configurable"].get("user_id", "anonymous")
items = store.search(("users", user_id, "preferences"))
prefs = {item.key: item.value for item in items}
if prefs:
pref_summary = ", ".join(f"{k}={v.get('value', v)}" for k, v in prefs.items())
return {"response": f"Welcome back! Preferences: {pref_summary}"}
return {"response": "Hi! First time here."}
def save_preference(state: State, config: RunnableConfig, *, store: BaseStore) -> dict:
user_id = config["configurable"].get("user_id", "anonymous")
if "bullet" in state["user_input"].lower():
store.put(("users", user_id, "preferences"), "format", {"value": "bullet_points"})
return {}
graph_builder = StateGraph(State)
graph_builder.add_node("greet", greet_user)
graph_builder.add_node("save_pref", save_preference)
graph_builder.add_edge(START, "greet")
graph_builder.add_edge("greet", "save_pref")
graph_builder.add_edge("save_pref", END)
my_store = InMemoryStore()
graph = graph_builder.compile(store=my_store)
r1 = graph.invoke(
{"user_input": "I want bullet points", "response": ""},
config={"configurable": {"user_id": "mike"}},
)
print(f"Session 1: {r1['response']}")
r2 = graph.invoke(
{"user_input": "Research AI", "response": ""},
config={"configurable": {"user_id": "mike"}},
)
print(f"Session 2: {r2['response']}")
# Expected output:
# Session 1: Hi! First time here.
# Session 2: Welcome back! Preferences: format={'value': 'bullet_points'}
Nodes declare *, store: BaseStore as a keyword argument. LangGraph injects the store automatically. The user_id comes from config["configurable"].
Combining Store + Checkpointer
The store and the checkpointer are complementary:
from typing import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, START, END
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
from langgraph.checkpoint.memory import MemorySaver
import datetime
class State(TypedDict):
topic: str
result: str
def research_with_memory(state: State, config: RunnableConfig, *, store: BaseStore) -> dict:
user_id = config["configurable"].get("user_id", "anonymous")
prefs_ns = ("users", user_id, "preferences")
history_ns = ("users", user_id, "history")
prefs = {item.key: item.value for item in store.search(prefs_ns)}
sessions = store.search(history_ns)
session_num = len(sessions) + 1
fmt = prefs.get("format", {}).get("value", "paragraphs")
result = f"Report #{session_num} on '{state['topic']}' (format: {fmt})"
store.put(history_ns, f"session_{session_num}", {
"topic": state["topic"],
"timestamp": datetime.datetime.now().isoformat(),
})
return {"result": result}
graph_builder = StateGraph(State)
graph_builder.add_node("research", research_with_memory)
graph_builder.add_edge(START, "research")
graph_builder.add_edge("research", END)
store = InMemoryStore()
checkpointer = MemorySaver()
store.put(("users", "mike", "preferences"), "format", {"value": "bullet_points"})
graph = graph_builder.compile(checkpointer=checkpointer, store=store)
for topic in ["RAG techniques", "Prompt engineering", "AI agents"]:
result = graph.invoke(
{"topic": topic, "result": ""},
config={"configurable": {"thread_id": f"session-{topic}", "user_id": "mike"}},
)
print(f" {result['result']}")
print(f"\nHistory in the store:")
for item in store.search(("users", "mike", "history")):
print(f" {item.key}: {item.value['topic']}")
# Expected output:
# Report #1 on 'RAG techniques' (format: bullet_points)
# Report #2 on 'Prompt engineering' (format: bullet_points)
# Report #3 on 'AI agents' (format: bullet_points)
#
# History in the store:
# session_1: RAG techniques
# session_2: Prompt engineering
# session_3: AI agents
The checkpointer saves the state of each individual run (each thread_id). The store accumulates the user's history across every run.
Store in the Functional API
With @entrypoint and @task, the store is passed as a parameter to the entrypoint:
from langgraph.func import entrypoint, task
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
from langgraph.checkpoint.memory import MemorySaver
store = InMemoryStore()
checkpointer = MemorySaver()
@task
def generate_greeting(session_count: int, last_topic: str) -> str:
if session_count == 0:
return "Hi! I'm your research assistant."
return f"Welcome back! You've had {session_count} sessions. Last one: '{last_topic}'."
@entrypoint(checkpointer=checkpointer, store=store)
def research_agent(topic: str, *, store: BaseStore) -> dict:
config = entrypoint.get_config()
user_id = config["configurable"].get("user_id", "anonymous")
history_ns = ("users", user_id, "history")
prefs_ns = ("users", user_id, "preferences")
sessions = store.search(history_ns)
last_topic_item = store.get(prefs_ns, "last_topic")
last_topic = last_topic_item.value["value"] if last_topic_item else ""
greeting = generate_greeting(len(sessions), last_topic).result()
import datetime
store.put(history_ns, f"session_{len(sessions) + 1}", {
"topic": topic, "timestamp": datetime.datetime.now().isoformat(),
})
store.put(prefs_ns, "last_topic", {"value": topic})
return {"greeting": greeting, "topic": topic, "session_number": len(sessions) + 1}
for i, topic in enumerate(["RAG", "Prompt engineering", "AI agents"]):
r = research_agent.invoke(
topic, config={"configurable": {"thread_id": f"s{i+1}", "user_id": "mike"}},
)
print(f"Session {r['session_number']}: {r['greeting']}")
# Expected output:
# Session 1: Hi! I'm your research assistant.
# Session 2: Welcome back! You've had 1 sessions. Last one: 'RAG'.
# Session 3: Welcome back! You've had 2 sessions. Last one: 'Prompt engineering'.
The store is declared as *, store: BaseStore in the @entrypoint. The @task functions don't receive the store directly — pass them the data they need as arguments.
Production stores
InMemoryStore is for development. For production, you need real persistence.
PostgresStore
from langgraph.store.postgres import PostgresStore
DB_URI = "postgresql://user:password@localhost:5432/mydb"
with PostgresStore.from_conn_string(DB_URI) as store:
store.put(("users", "mike", "preferences"), "format", {"value": "bullet_points"})
item = store.get(("users", "mike", "preferences"), "format")
The API is identical to InMemoryStore. The change is one line. The recommended strategy:
import os
if os.getenv("ENVIRONMENT") == "production":
from langgraph.store.postgres import PostgresStore
store = PostgresStore.from_conn_string(os.getenv("DATABASE_URL"))
else:
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
Namespace design
# User level
("users", user_id, "preferences") → format, sources, language
("users", user_id, "history") → session history
("users", user_id, "topics") → topics with frequency
# Project level
("projects", project_id, "notes") → shared notes
# Global level
("system", "templates") → report templates
Rules:
- ✅ The most specific level possible —
("users", "mike", "preferences"), not("preferences", "mike") - ✅ Hierarchical consistency — entity → instance → category
- ✅ Stable IDs, not names that change
- ❌ Don't mix different entities in the same namespace
- ❌ No more than 4 levels — it becomes hard to navigate
Troubleshooting
Problem 1: "store.get returns None even though I did a put"
Cause: A different namespace. ("users", "mike", "prefs") and ("users", "mike", "preferences") are not the same.
Solution: Define namespaces as constants:
USER_PREFS = lambda uid: ("users", uid, "preferences")
store.put(USER_PREFS("mike"), "format", {"value": "bullets"})
item = store.get(USER_PREFS("mike"), "format") # Always matches
Problem 2: "The data disappears on restart"
Cause: InMemoryStore lives in RAM.
Solution: Serialize to JSON on shutdown, load on startup. For production, use PostgresStore. In capsule 08 (the project) you'll implement file persistence.
Problem 3: "The store isn't available in my nodes"
Cause: You didn't pass store= when compiling.
Solution:
graph = graph_builder.compile(store=my_store) # ← don't forget this
Problem 4: "Data conflicts between users"
Cause: You're not including user_id in the namespace.
Solution: Always use the user_id:
user_id = config["configurable"]["user_id"]
namespace = ("users", user_id, "preferences") # Per-user isolation
Exercises
Exercise 1: Full CRUD with InMemoryStore (Easy)
Create an InMemoryStore, save 3 items under ("projects", "alpha", "notes"), read a specific one, list them all, delete one, and verify that 2 remain.
See solution
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
ns = ("projects", "alpha", "notes")
store.put(ns, "idea_1", {"text": "Use RAG for internal search", "priority": "high"})
store.put(ns, "idea_2", {"text": "Add an embeddings cache", "priority": "medium"})
store.put(ns, "idea_3", {"text": "Metrics dashboard", "priority": "low"})
item = store.get(ns, "idea_2")
print(f"idea_2: {item.value['text']}")
print(f"\nAll ({len(store.search(ns))}):")
for item in store.search(ns):
print(f" {item.key}: {item.value['text']}")
store.delete(ns, "idea_1")
print(f"\nAfter delete ({len(store.search(ns))}):")
for item in store.search(ns):
print(f" {item.key}: {item.value['text']}")
# Expected output:
# idea_2: Add an embeddings cache
#
# All (3):
# idea_1: Use RAG for internal search
# idea_2: Add an embeddings cache
# idea_3: Metrics dashboard
#
# After delete (2):
# idea_2: Add an embeddings cache
# idea_3: Metrics dashboard
Exercise 2: Automatic preference detection (Easy)
Implement detect_and_save_preferences(store, user_id, text) that detects preferences in the user's text: "short summary" → detail_level: brief, "academic sources" → preferred_sources: academic, "bullet" → format: bullet_points. Test it with 3 inputs and show the accumulated preferences.
See solution
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
def detect_and_save_preferences(store, user_id: str, text: str):
ns = ("users", user_id, "auto_prefs")
t = text.lower()
if "short summary" in t or "brief" in t:
store.put(ns, "detail_level", {"value": "brief", "from": text[:50]})
if "academic" in t or "arxiv" in t or "paper" in t:
store.put(ns, "preferred_sources", {"value": "academic", "from": text[:50]})
if "bullet" in t or "list" in t:
store.put(ns, "format", {"value": "bullet_points", "from": text[:50]})
inputs = [
"Give me a short summary of RAG with papers from arxiv",
"I want a list with bullet points",
"Research the advances in AI agents in depth",
]
for text in inputs:
detect_and_save_preferences(store, "mike", text)
print("mike's preferences:")
for item in store.search(("users", "mike", "auto_prefs")):
print(f" {item.key}: {item.value['value']}")
# Expected output:
# mike's preferences:
# detail_level: brief
# preferred_sources: academic
# format: bullet_points
Exercise 3: Store integrated into a StateGraph (Medium)
Create a graph with 3 nodes: load_profile (reads the profile from the store), process (simulates research), update_profile (updates the profile). Compile it with an InMemoryStore. Run it 3 times with the same user_id and show how the profile gets richer.
See solution
from typing import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, START, END
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
class State(TypedDict):
topic: str
result: str
session_count: int
def load_profile(state: State, config: RunnableConfig, *, store: BaseStore) -> dict:
uid = config["configurable"]["user_id"]
items = store.search(("users", uid, "profile"))
profile = {item.key: item.value for item in items}
return {"session_count": profile.get("count", {}).get("value", 0)}
def process(state: State, config: RunnableConfig) -> dict:
return {"result": f"Research #{state['session_count'] + 1} on '{state['topic']}' complete."}
def update_profile(state: State, config: RunnableConfig, *, store: BaseStore) -> dict:
uid = config["configurable"]["user_id"]
ns = ("users", uid, "profile")
store.put(ns, "count", {"value": state["session_count"] + 1})
store.put(ns, "last_topic", {"value": state["topic"]})
existing = store.get(ns, "all_topics")
topics = existing.value["value"] if existing else []
if state["topic"] not in topics:
topics.append(state["topic"])
store.put(ns, "all_topics", {"value": topics})
return {}
builder = StateGraph(State)
builder.add_node("load", load_profile)
builder.add_node("process", process)
builder.add_node("update", update_profile)
builder.add_edge(START, "load")
builder.add_edge("load", "process")
builder.add_edge("process", "update")
builder.add_edge("update", END)
my_store = InMemoryStore()
graph = builder.compile(store=my_store)
for topic in ["RAG", "Prompt Engineering", "AI Agents"]:
r = graph.invoke(
{"topic": topic, "result": "", "session_count": 0},
config={"configurable": {"user_id": "mike"}},
)
print(r["result"])
print(f"\nFinal profile:")
for item in my_store.search(("users", "mike", "profile")):
print(f" {item.key}: {item.value}")
# Expected output:
# Research #1 on 'RAG' complete.
# Research #2 on 'Prompt Engineering' complete.
# Research #3 on 'AI Agents' complete.
#
# Final profile:
# count: {'value': 3}
# last_topic: {'value': 'AI Agents'}
# all_topics: {'value': ['RAG', 'Prompt Engineering', 'AI Agents']}
Exercise 4: Multi-user with isolated namespaces (Medium)
Create a system where 3 users save preferences in the same store. Verify that the data is isolated — one user doesn't see another's preferences.
See solution
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
users = {
"alice": {"format": "bullet_points", "language": "en"},
"bob": {"format": "paragraphs", "language": "es"},
"carol": {"format": "detailed", "language": "fr"},
}
for uid, prefs in users.items():
for key, value in prefs.items():
store.put(("users", uid, "preferences"), key, {"value": value})
for uid in users:
items = store.search(("users", uid, "preferences"))
print(f"{uid}: {', '.join(f'{i.key}={i.value[\"value\"]}' for i in items)}")
alice_lang = store.get(("users", "alice", "preferences"), "language").value["value"]
bob_lang = store.get(("users", "bob", "preferences"), "language").value["value"]
print(f"\nAlice lang={alice_lang}, Bob lang={bob_lang}, different={alice_lang != bob_lang}")
# Expected output:
# alice: format=bullet_points, language=en
# bob: format=paragraphs, language=es
# carol: format=detailed, language=fr
#
# Alice lang=en, Bob lang=es, different=True
Exercise 5: Multi-tenant namespace design (Advanced)
Design a schema for organizations with multiple users. Each org has shared config, each user has preferences. Implement get_effective_config(store, org_id, user_id) that merges org defaults + user overrides (the user wins). Test it with 2 orgs, 2 users each.
See solution
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
for key, val in [("model", "gpt-4.1"), ("max_sources", 5), ("language", "en")]:
store.put(("orgs", "acme", "config"), key, {"value": val})
store.put(("orgs", "acme", "users", "alice", "prefs"), "language", {"value": "es"})
for key, val in [("model", "gpt-4.1-mini"), ("max_sources", 3), ("language", "fr")]:
store.put(("orgs", "globex", "config"), key, {"value": val})
store.put(("orgs", "globex", "users", "carol", "prefs"), "language", {"value": "en"})
def get_effective_config(store, org_id: str, user_id: str) -> dict:
org_items = store.search(("orgs", org_id, "config"))
org_config = {item.key: item.value["value"] for item in org_items}
user_items = store.search(("orgs", org_id, "users", user_id, "prefs"))
user_prefs = {item.key: item.value["value"] for item in user_items}
return {**org_config, **user_prefs}
for org, user, desc in [("acme", "alice", "org en, user es"), ("acme", "bob", "no user prefs"), ("globex", "carol", "org fr, user en")]:
config = get_effective_config(store, org, user)
print(f"{org}/{user} ({desc}): {config}")
# Expected output:
# acme/alice (org en, user es): {'model': 'gpt-4.1', 'max_sources': 5, 'language': 'es'}
# acme/bob (no user prefs): {'model': 'gpt-4.1', 'max_sources': 5, 'language': 'en'}
# globex/carol (org fr, user en): {'model': 'gpt-4.1-mini', 'max_sources': 3, 'language': 'en'}
The {**org_config, **user_prefs} merge applies inheritance: the org defines defaults, the user overrides them.
Summary
In this capsule you learned:
- Short-term (checkpoints) and long-term (store) are different mechanisms — checkpoints track one run, the store accumulates knowledge across runs
- InMemoryStore has 4 operations (
put,get,search,delete) organized by hierarchical namespaces - 3 concrete use cases: favorite sources, research profile, format preferences — each one transforms the user's experience
- The store is injected into nodes when you compile with
store=— nodes declare*, store: BaseStore - Store + Checkpointer work together: the checkpointer for resilience, the store for intelligence across sessions
- In the Functional API, the store is passed to the
@entrypointand accessed as*, store: BaseStore - For production,
PostgresStorereplacesInMemoryStorewith the same API - Namespace design determines scalability — use clear hierarchies and isolate data between users
Next capsule: Evolving Project v3 — your Research Agent gains memory. Close the terminal, open it again, and the agent remembers who you are.
Additional resources
- LangGraph Memory Store — Official documentation on stores and long-term memory
- LangGraph Persistence — Checkpointing vs Store
- LangGraph Functional API — Store in @entrypoint and @task
- InMemoryStore Reference — Complete API reference
- LangGraph Cross-thread Memory — Memory across threads
- PostgresStore Guide — Migrating to PostgresStore
Module 8 — LangChain & LangGraph: From Chains to Agents