LangGraphLangGraph 1.2 · Python 3.10+
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
37 small wins to finish your pathNext lesson

Remembering the conversation

Run your agent twice and the second run knows nothing about the first. Everything it learned went in the bin the moment invoke returned.

That is not a bug. Every graph so far starts from the dictionary you hand it and forgets when it finishes. For a conversation that is useless, so LangGraph can save the state as it goes.

The graph, exactly as in lesson 15

python
from pretend_model import PretendModel
from langgraph.graph import StateGraph, START, MessagesState

model = PretendModel()

def call_model(state):
    return {"messages": [model.invoke(state["messages"])]}

One new argument

Everything in this lesson comes from this single word.

python
from langgraph.checkpoint.memory import InMemorySaver

builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_edge(START, "call_model")
graph = builder.compile(checkpointer=InMemorySaver())

Give compile a checkpointer and LangGraph writes the state down after every step instead of letting it go. InMemorySaver is the simplest one, keeping everything in a Python dictionary for as long as your program runs.

And one new setting when you run it

A saved state has to be saved under a name, or a second user would walk into the first one's conversation.

python
config = {"configurable": {"thread_id": "asha"}}

The thread id is the name of this conversation. The same id means carry on, and a different id starts a fresh one with nothing remembered.

You met that second argument to invoke in lesson 8, where it carried the recursion limit. It is the same argument, for anything belonging to this run rather than to the graph.

Two calls, one conversation

Example
from langchain_core.messages import HumanMessage

graph.invoke({"messages": [HumanMessage("Hi, I am Asha")]}, config)
result = graph.invoke({"messages": [HumanMessage("Where is order A17?")]}, config)

for msg in result["messages"]:
    print(f"{msg.type:<6} {msg.content}")

Four messages, from two separate calls. The second invoke was handed one message and got back a conversation with all four in it, because the first two were waiting where the last run left them.

Looking at what was saved

The saved state is not hidden away. get_state hands it to you whenever you ask.

Example
saved = graph.get_state(config)

print("messages saved:", len(saved.values["messages"]))
print("next to run:   ", saved.next)

values is the state itself, exactly as the last run left it. next is what was about to happen, and it is empty because the run finished. In lesson 23 you stop a run halfway through and that field stops being a curiosity.

Why a thread and not a variable

A real application has many people talking at once. If the conversation lived in a Python variable, everybody would share one.

The thread id keeps them apart. The same compiled graph serves one user or a million, and the only difference between them is a string.

This one does not survive a restart
InMemorySaver forgets when your program exits. It is for learning and for tests. Real use wants SqliteSaver on one machine or PostgresSaver on a server, and swapping to either is one line with nothing else touched.
Try it yourself
  • Change the thread id on the second call and watch the memory disappear.
  • Add a third turn and count the messages.
  • Print saved.values in full and see the whole state, not only the messages.

Every expert started right here.