Lists of results, logs and chat messages must grow, and parallel nodes must not clobber each other.
By default, when a node returns {"notes": ["b"]}, the old value of notes is replaced. Often you want to add instead: collect search results, keep a log, build a chat history.
A reducer is a function that decides how an update is combined with the existing value. You attach one with Annotated in the state definition:
from typing import Annotated
import operator
class State(TypedDict):
notes: Annotated[list[str], operator.add]operator.add on two lists joins them, so ["a"] plus an update ["b"] becomes ["a", "b"]. LangGraph calls reducer(old_value, update) each time a node writes that key.
Reducers matter even more with parallel nodes. If two nodes run in the same step and both write a plain key, LangGraph can't know which value wins and raises an InvalidUpdateError. With a reducer, both updates are combined.
You can write your own reducer: any function taking (current, update) and returning the new value, for example keeping a running maximum or merging dicts.
Note the asymmetry: with a reducer, a node returns only the new items, not the full list. Returning the whole list again would duplicate everything.
Example
import operator
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
topic: str
notes: Annotated[list[str], operator.add] # accumulate
def search_web(state: State) -> dict:
return {"notes": [f"web: {state['topic']}"]}
def search_docs(state: State) -> dict:
return {"notes": [f"docs: {state['topic']}"]}
b = StateGraph(State)
b.add_node(search_web)
b.add_node(search_docs)
b.add_edge(START, "search_web") # both start in parallel
b.add_edge(START, "search_docs")
b.add_edge(["search_web", "search_docs"], END)
print(b.compile().invoke({"topic": "bees", "notes": []}))
# {'topic': 'bees', 'notes': ['docs: bees', 'web: bees']} (both kept)Remember
- Annotated[type, reducer] controls how updates merge
- operator.add appends lists instead of replacing them
- Parallel writes to a key without a reducer raise InvalidUpdateError
- With a reducer, return only the new items
Watch out for
- Returning the whole accumulated list from a node whose key uses operator.add, which duplicates every item.