The Architecture of Agent Memory: How LangGraph Really Works

Published: (December 13, 2025 at 07:33 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Understanding LangGraph’s State

State is the execution memory of a LangGraph agent. It records every input, intermediate thought, tool output, and decision as the graph is traversed. Each node receives the current state, performs its logic, and returns only the part of the state it wants to update. LangGraph then merges that update into the existing state according to the schema you provided. This deterministic merging can append, overwrite, or combine data depending on how the state is defined.

Simple State Definition with TypedDict

from typing_extensions import TypedDict

class State(TypedDict):
    messages: list
    extra_field: int

The TypedDict defines the shape of the memory that flows through the graph. Beyond conversation history, the state can hold tool outputs, metadata, task status, counters, retrieved documents, and more.

Why TypedDict Is Preferred Over Pydantic or Dataclasses

  • Partial Updates: Nodes can return a dict with just the keys they want to change. LangGraph merges these partial updates automatically.
  • Performance: Updating a single field does not require reconstructing the entire object, which is costly for large histories.
  • Immutability Guarantees: LangGraph relies on deterministic merging; mutating a full Pydantic or dataclass instance can break these guarantees.
  • Caching Issues: Pydantic’s internal metadata may vary between instantiations, leading to unexpected cache behavior.

Because of these trade‑offs, most production LangGraph graphs use TypedDict for state schemas.

Reducers: Controlling How Updates Are Merged

Reducers define how to combine an existing value with a new one. The signature is:

def reducer(current_value, new_value) -> merged_value:
    ...
  • Default behavior: If no reducer is specified, the new value overwrites the old one.
  • Custom behavior: Use reducers to append to lists, accumulate counters, or merge dictionaries.

Using Python’s operator.add

from typing import Annotated
from operator import add
from typing_extensions import TypedDict

class State(TypedDict):
    history: Annotated[list[str], add]
    count: int

The add reducer concatenates lists, so multiple updates to history are appended rather than overwritten.

Built‑in add_messages Reducer for LangChain Messages

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import add_messages

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

add_messages intelligently merges LangChain HumanMessage, AIMessage, and ToolMessage objects, handling deduplication by ID and ensuring correct ordering during streaming or tool calls.

Practical Example

from operator import add
from typing import Annotated
from typing_extensions import TypedDict

class MyState(TypedDict):
    logs: Annotated[list[str], add]
    counter: Annotated[int, add]

Node Implementations

def start_node(state: MyState):
    return {"logs": ["Started"], "counter": 1}

def step_node(state: MyState):
    return {"logs": ["Step done"], "counter": 2}

def finish_node(state: MyState):
    return {"logs": ["Finished"], "counter": 3}

Execution Flow

  1. After start_nodelogs = ["Started"], counter = 1
  2. After step_nodelogs = ["Started", "Step done"], counter = 3
  3. After finish_nodelogs = ["Started", "Step done", "Finished"], counter = 6

Reducers make the logs append and the counters accumulate; without them each update would overwrite the previous state.

Writing Custom Reducers

LangGraph allows any callable with the reducer signature. Custom reducers can:

  • Trim old conversation context when a list grows too large.
  • Merge dictionaries while preserving existing keys.
  • Accumulate only distinct items.
  • Implement complex transformation logic specific to your application.
def unique_items(current: list, new: list) -> list:
    return list(dict.fromkeys(current + new))

class CustomState(TypedDict):
    items: Annotated[list, unique_items]

In this example, unique_items ensures that the items list contains no duplicates after each merge.

Back to Blog

Related posts

Read more »