Memory

Memory

Think about how important memory is in your daily life. Where did you put your keys? What’s your email password? What’s the name of that friend you haven’t seen in ten years?

Being able to remember past events is also important for an AI agent. Memory, or persistence, gives your agent context for the output that it produces. It lets the agent return to previous states in the graph to correct errors or take a different path. Or it lets the agent pause execution for human feedback before resuming where it left off.

You’ve already worked with a couple of forms of memory in LangGraph. The next two segments will be a review, but the third segment on checkpointing will be new.

Input/Output-Based Memory

The most basic form of preserving state is simply passing output from one node as input to another. While this might not be considered “memory” by most people, in a sense, you’re persisting the state of the previous node in the graph, if not any of the nodes before it. This is the style of persistence you use when you have a plain Graph object:

def function_1(input):
  return "output"

def function_2(input):
  return "output"

def function_3(input):
  return "output"

graph = Graph()
graph.add_node("node_1", function_1)
graph.add_node("node_2", function_2)
graph.add_node("node_3", function_3)
graph.add_edge("node_1", "node_2")
graph.add_edge("node_2", "node_3")

node_2 has access to the output of node_1. But if there were another node before node_1 in the graph, node_2 wouldn’t have access to that unless, of course, node_1 explicitly forwarded that state on.

State-Based Memory

A more powerful version of memory is to define a State object and provide it to a StateGraph. This allows keeping a record of entire AI-human conversations through a message list. If that’s not enough, you can add as many properties to the State object as needed. This object is available to every node in the graph. The following is an abbreviated example:

class MyState(TypedDict):
  messages: Annotated[list, add]
  count: int

def function_1(state):
  return state

def function_2(state):
  return state

def function_3(state):
  return state

graph = StateGraph(MyState)
graph.add_node("node_1", function_1)
graph.add_node("node_2", function_2)
graph.add_node("node_3", function_3)
graph.add_edge("node_1", "node_2")
graph.add_edge("node_2", "node_3")

Both input/output and state-based memory are helpful in their own right, and you’ve used them well up to this point. But LangGraph has even more to offer.

Checkpoints

The real power of LangGraph comes with checkpoints. Checkpoints remember the state at every step of the graph execution. This is like giving a human the superpower of a photographic memory. Plus, a time machine. Plus, the ability to change the past.

You’ll never be able to do all that as a human, but would you like to learn how to give your LangGraph agent those abilities? It’s surprisingly easy.

There are several ways you can create a checkpointer, which is what LangGraph calls the object that saves the state history. The easiest is probably MemorySaver. This maintains the checkpoint history in memory rather than persisting it to a database. You’d use it like so:

from langgraph.checkpoint.memory import MemorySaver

graph = StateGraph(State)
memory = MemorySaver()
app = graph.compile(checkpointer=memory)

That’s it. Now the checkpointer will record everything that happens during execution.

As if that weren’t enough, the checkpointer can also handle different execution history threads. All you need to do is provide a different thread ID:

memory = MemorySaver()
app = graph.compile(checkpointer=memory)

initial_input = {"messages": ["hello"]}
thread = {"configurable": {"thread_id": "1"}}
app.invoke(initial_input, thread)

There, the thread ID is “1”. Change it to “2,” and you have a whole new history with its own checkpoints. It’s like a parallel universe.

thread = {"configurable": {"thread_id": "2"}}
app.invoke(initial_input, thread)

Maintaining multiple threads would be helpful for multi-agent systems where you want to keep the various agents and their state histories separate.

Although the code here used MemorySaver, there are more permanent checkpointers available including SqliteSaver, Postgres, MongoDB, and Redis.

In the following demo, you’ll create a MemorySaver checkpointer and observe how it records the history of a graph execution.

See forum comments
Download course materials from Github
Previous: Introduction Next: Memory Demo