TechByteByByte

Memory in LangGraph: Checkpoint, Thread, and the Long-Term Store

A precise, honest comparison of six terms that get blurred together constantly, plus the real mechanism for memory that genuinely survives across separate threads, not just within one.

#LangGraph#Memory#Store#Checkpointing

Recall every memory-adjacent term this course has used so far — state, checkpoint, thread, and now, genuinely for the first time, something that survives even across threads. It’s worth stopping here and getting all of this permanently, precisely straight, because these terms genuinely get blurred together constantly, including in real, published material.

The precise comparison, stated once, clearly

TermWhat it actually is
StateThe real, current data for one graph execution (Module 3)
CheckpointOne saved snapshot of state, after one node finishes (Module 16)
ThreadA continuous, isolated stream of checkpoints, identified by thread_id (Module 17)
Short-term memoryEverything above — genuinely scoped to one thread
Long-term memoryInformation that survives across different threads entirely — this module
StoreThe real, concrete mechanism long-term memory is actually built on

Why short-term memory alone genuinely isn’t enough

Recall Module 17’s real, correct isolation — two different customers, two different threads, no bleed-over. That’s exactly right for conversation history. But consider a genuinely different, real need: “this customer always prefers email over phone contact” — a fact that should persist even when this same customer starts a brand-new support conversation next month, in a genuinely new thread. Checkpoints and threads, by design, can’t do this — they’re deliberately scoped to one continuous conversation.

Example 1: the Store, doing exactly this

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

# save a fact, in a namespace scoped to this specific customer, NOT to any one thread
store.put(("customer_preferences", "c_1"), "contact_method", {"value": "email"})

# retrieve it — genuinely independent of which thread you're currently in
item = store.get(("customer_preferences", "c_1"), "contact_method")
print(item.value)

Notice store.put and store.get take a namespace (here, ("customer_preferences", "c_1")) and a key, completely independent of any thread_id. This is the real, structural difference from Module 16’s checkpointer: a checkpoint is tied to one thread; a Store entry is tied to whatever real, meaningful identity you choose — here, the customer themselves, genuinely surviving across however many separate conversations they ever have.

Example 2: using the Store inside a real graph

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.store.memory import InMemoryStore
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.base import BaseStore
from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-4o-mini")

def call_model(state: MessagesState, config: dict, *, store: BaseStore) -> dict:
    customer_id = config["configurable"]["customer_id"]
    preference = store.get(("customer_preferences", customer_id), "contact_method")
    pref_text = preference.value["value"] if preference else "unknown"
    system_note = f"This customer's preferred contact method is: {pref_text}."
    return {"messages": [model.invoke([{"role": "system", "content": system_note}] + state["messages"])]}

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

store = InMemoryStore()
store.put(("customer_preferences", "c_1"), "contact_method", {"value": "email"})

graph = builder.compile(checkpointer=InMemorySaver(), store=store)

config = {"configurable": {"thread_id": "new-conversation-today", "customer_id": "c_1"}}
result = graph.invoke({"messages": [{"role": "user", "content": "How should I contact you about my order?"}]}, config=config)
print(result["messages"][-1].content)

Notice this graph genuinely uses both real memory systems at once: checkpointer for this specific conversation’s short-term, thread-scoped history, and store for the customer’s long-term preference — set once, and available in this brand-new thread, "new-conversation-today", without ever having been mentioned in this specific conversation at all.

flowchart TD
    A[Checkpointer] --> B["Short-term memory:\nscoped to ONE thread"]
    C[Store] --> D["Long-term memory:\nsurvives ACROSS threads,\nscoped to whatever real identity\nyou choose — customer, user, account"]

Common mistakes worth avoiding

Using the checkpointer for information that genuinely needs to survive across threads. Recall this module’s own core example — a customer preference stored only in one thread’s checkpoint disappears the moment that specific conversation ends. Long-term facts belong in the Store, deliberately, not the checkpointer.

Using the Store for information that’s genuinely just this one conversation’s business. The reverse mistake is just as real — cramming ordinary conversation history into the Store, rather than letting the checkpointer handle it, adds real, unnecessary complexity for no genuine benefit.

Choosing a Store namespace that isn’t actually meaningful. Recall Example 1’s ("customer_preferences", "c_1") — a real, deliberate namespace, scoped to genuine, lasting identity. A vague or inconsistent namespace makes retrieving the right fact later genuinely unreliable.

What you should take away from this module

  • State, checkpoint, and thread are all genuinely short-term — scoped to one, single conversation.
  • Long-term memory, built on the Store, genuinely survives across separate threads, scoped to whatever real identity — a customer, a user — actually matters for the fact being remembered.
  • A real, complete agent typically uses both systems together: the checkpointer for this conversation’s flow, the Store for facts that should outlive it.

Where this goes next

The next module finally, properly resolves Module 1’s original refund-approval scenario: Interrupts — genuinely pausing a graph’s execution mid-run, and waiting for a real human, using the persistence mechanisms you now completely understand.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed