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:

AspectShort-term (Checkpoints)Long-term (Store)
What it savesThe state of ONE runKnowledge from ALL runs
ScopeA specific thread_idA user, project, or system
Example"At step 3, it searched 2 sources""This user prefers academic sources"
MechanismMemorySaver / PostgresSaverInMemoryStore / PostgresStore
AnalogyA document's draftYour 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 @entrypoint and accessed as *, store: BaseStore
  • For production, PostgresStore replaces InMemoryStore with 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

  1. LangGraph Memory Store — Official documentation on stores and long-term memory
  2. LangGraph Persistence — Checkpointing vs Store
  3. LangGraph Functional API — Store in @entrypoint and @task
  4. InMemoryStore Reference — Complete API reference
  5. LangGraph Cross-thread Memory — Memory across threads
  6. PostgresStore Guide — Migrating to PostgresStore

Module 8 — LangChain & LangGraph: From Chains to Agents