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

MessagesState and add_messages

MessagesState is a prebuilt state with one messages key that uses the add_messages reducer, so a returned message is appended to the conversation instead of replacing it.

Last updated: 27 Sep, 2026 · LangGraph 1.2

A conversation must grow one message at a time. That is exactly what a reducer is for, so LangGraph ships one ready-made for messages.

The MessagesState shape

python
from langgraph.graph import MessagesState        # state with a messages key

# equivalent by hand:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
from langchain.messages import AnyMessage

class State(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

Importing MessagesState

Import MessagesState, the prebuilt state whose messages key already uses the add_messages reducer.

python
from langgraph.graph import MessagesState, StateGraph, START, END
from langchain.messages import HumanMessage, AIMessage

A node that adds a message

Write one node that returns a single AIMessage. Because of the reducer, this message is added to the conversation, not swapped in for it.

python
def reply(state):
    return {"messages": [AIMessage("Hello!")]}   # return one new message

Building the graph

Build the graph on MessagesState with that one node between START and END.

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

Running and counting messages

Start the run with one HumanMessage, then print how many messages the state holds and the text of the last one.

python
out = builder.compile().invoke({"messages": [HumanMessage("hi")]})
print(len(out["messages"]), out["messages"][-1].content)   # count, then last text

Appending a message end to end

The same pieces in one file, ready to run.

Example
from langgraph.graph import MessagesState, StateGraph, START, END
from langchain.messages import HumanMessage, AIMessage

def reply(state):
    return {"messages": [AIMessage("Hello!")]}

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

out = builder.compile().invoke({"messages": [HumanMessage("hi")]})
print(len(out["messages"]), out["messages"][-1].content)

Why the state holds two messages

  • The run started with one HumanMessage.
  • The reply node returned one AIMessage, and add_messages appended it, so the state ends with two messages.
  • Without the reducer, the returned list would replace the conversation and the human message would be lost.

Plain list vs add_messages

Plain listadd_messages
A returned message listReplaces the whole listIs appended to it
The conversationLost on each stepGrows across steps
Also doesNothingUpdates a message matched by id, and reads dict form

Where MessagesState fits

  • Any graph that talks to a model uses MessagesState or a state with the add_messages reducer.
  • Subclass MessagesState when you need extra keys alongside the conversation.
Watch out. A messages key with a plain list and no add_messages replaces the conversation on every write. That is the most common memory bug in a chat graph.
Try it yourself
  • Add a second node that appends another AIMessage. How many messages now?
  • Subclass MessagesState with a category key and set it in a node.

Every expert started right here.