Chat agents need a message list that appends, updates by ID and accepts plain dicts.
Chat applications keep a list of messages (system, user, assistant, tool). LangGraph ships a reducer made for that list: add_messages.
class State(TypedDict):
messages: Annotated[list, add_messages]Compared with operator.add, add_messages:
- appends new messages,
- replaces a message when the update has the same message ID, so you can edit one in place,
- converts plain dicts like
{"role": "user", "content": "hi"}into LangChain message objects (HumanMessage,AIMessage, ...).
Because this is so common there is a ready-made state class, MessagesState, with exactly one key, messages, using add_messages. Use it directly, or subclass it to add your own keys:
class State(MessagesState):
user_name: strA node that talks to a model typically returns {"messages": [reply]}: a list with only the new message, which gets appended.
To delete old messages (for example to keep the context window small), return RemoveMessage(id=...) objects for the messages to drop; add_messages removes them.
Example
from langchain_core.messages import AIMessage
from langgraph.graph import StateGraph, START, END, MessagesState
class State(MessagesState): # messages: Annotated[list, add_messages]
turns: int
def echo(state: State) -> dict:
last = state["messages"][-1].content
return {"messages": [AIMessage(f"You said: {last}")], "turns": state.get("turns", 0) + 1}
b = StateGraph(State)
b.add_node(echo)
b.add_edge(START, "echo")
b.add_edge("echo", END)
out = b.compile().invoke({"messages": [{"role": "user", "content": "hi"}]})
for m in out["messages"]:
print(type(m).__name__, m.content)
# HumanMessage hi
# AIMessage You said: hiRemember
- add_messages appends, updates by ID, and converts dicts to message objects
- MessagesState = a state with a messages key using add_messages
- Subclass MessagesState to add your own keys
- Return only new messages; RemoveMessage deletes old ones