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
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.
from langgraph.graph import MessagesState, StateGraph, START, END
from langchain.messages import HumanMessage, AIMessageA 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.
def reply(state):
return {"messages": [AIMessage("Hello!")]} # return one new messageBuilding the graph
Build the graph on MessagesState with that one node between START and END.
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.
out = builder.compile().invoke({"messages": [HumanMessage("hi")]})
print(len(out["messages"]), out["messages"][-1].content) # count, then last textAppending a message end to end
The same pieces in one file, ready to run.
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
replynode returned oneAIMessage, andadd_messagesappended 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 list | add_messages | |
|---|---|---|
| A returned message list | Replaces the whole list | Is appended to it |
| The conversation | Lost on each step | Grows across steps |
| Also does | Nothing | Updates a message matched by id, and reads dict form |
Where MessagesState fits
- Any graph that talks to a model uses
MessagesStateor a state with theadd_messagesreducer. - Subclass
MessagesStatewhen you need extra keys alongside the conversation.
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.Related
- Previous: Messages: how a model reads and writes
- Next: Chat models: init_chat_model
- Reference: Graph API: MessagesState
- Add a second node that appends another
AIMessage. How many messages now? - Subclass
MessagesStatewith acategorykey and set it in a node.
Every expert started right here.