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: 29 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 crash course writes its chatbot's state before any node. It imports Annotated, TypedDict, and add_messages from langgraph.graph.message. add_messages is a reducer: each time the chatbot answers, the new message is appended to the messages list instead of replacing it. The state class is a TypedDict with one key:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class State(TypedDict):
# Messages have the type "list". The `add_messages` function
# in the annotation defines how this state key should be updated
# (in this case, it appends messages to the list, rather than overwriting them)
messages:Annotated[list,add_messages]Annotated attaches extra information to a type: the key is still a list, and add_messages travels with it as the rule for updating it. MessagesState, below, is that same key, ready-made.
The MessagesState shape
from langgraph.graph import MessagesState # the State above, ready-madeImporting 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)2 Hello!
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
- Next: Chat models
- 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.