# Checkpointers and Threads — LangGraph Agents & Multi-Agent Systems

Source: https://www.geekswithgeeks.com/en/langgraph-agents/m-checkpoint

> Persist state between runs so a conversation can continue.

## A thread is a saved conversation

A **checkpointer** saves the graph state after every step. When you compile with a checkpointer and invoke with a **`thread_id`** in the config, LangGraph loads that thread's saved state, merges your new input through the reducers, runs, and saves again. Different thread IDs are fully separate conversations. This is **short-term memory**. `InMemorySaver` is for development and tests; production uses a durable backend (for example the SQLite or Postgres savers). Treat thread IDs as sensitive: derive them from authenticated users and sessions so one user can never load another's thread.

## Save state at every step

Checkpointers save state per thread, enabling conversations, resume after failure and replay.

![Four ideas: thread, checkpoint, store, trim.](assets/figures/langgraph-agents/section-4-map.svg) — Figure 4.1 — Thread, checkpoint, store and trim.

## Two users, two threads, run

I ran this offline with langgraph 1.2.12 and langchain-core 1.6.6 in a Python virtual environment. No API key or model is needed because plain Python functions stand in for the model, so the output is repeatable. Alice's second call sees her first message and the bot's reply (3 messages by then), because the thread is remembered. Bob's thread starts fresh.

```python
import operator
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    messages: Annotated[list, operator.add]     # reducer: new messages are appended to the stored ones

def reply(state):
    return {"messages": [f"bot: I have seen {len(state['messages'])} message(s)"]}

g = StateGraph(State)
g.add_node("reply", reply); g.add_edge(START, "reply"); g.add_edge("reply", END)
app = g.compile(checkpointer=InMemorySaver())

alice = {"configurable": {"thread_id": "alice"}}
bob = {"configurable": {"thread_id": "bob"}}
print(app.invoke({"messages": ["alice: hi"]}, alice)["messages"])
print(app.invoke({"messages": ["alice: again"]}, alice)["messages"])   # same thread: history is remembered
print(app.invoke({"messages": ["bob: hello"]}, bob)["messages"])       # new thread: starts fresh

```

Output:

```
['alice: hi', 'bot: I have seen 1 message(s)']
['alice: hi', 'bot: I have seen 1 message(s)', 'alice: again', 'bot: I have seen 3 message(s)']
['bob: hello', 'bot: I have seen 1 message(s)']
```

**Quiz:** What identifies a conversation to the checkpointer?

- [ ] The GPU id
- [ ] The model name
- [ ] The node name
- [x] The thread_id in the run config

*Answer:* The thread_id in the run config. Each thread_id has its own saved state history.
