Subject: operator.add reducer causes exponential duplication in Annotated List state fields when tools update state

Issue Description:

Problem:

When using Annotated[List[Dict], operator.add] for state fields that are updated by tools via Command(update=…), the state accumulates exponentially instead of properly merging list items. Each tool update appears to wrap the previous state in a new array, causing nested concatenation rather than flat list merging.

Reproduction Steps:

  1. Define a state field: current_context_source: Annotated[List[Dict[str, str]], operator.add]

  2. Create tools that update this field: Command(update={“current_context_source”: [{“id”: “A”, “value”: [“source1”]}]})

  3. Run multiple queries through the graph

  4. Observe exponential growth: [A] → [[A], B] → [[[A], B], C]

Expected Behavior:

python

# Query 1: [{“id”: “A”, “value”: [“source1”]}]

# Query 2: [{“id”: “A”, “value”: [“source1”]}, {“id”: “B”, “value”: [“source2”]}]

# Query 3: [{“id”: “A”, “value”: [“source1”]}, {“id”: “B”, “value”: [“source2”]}, {“id”: “C”, “value”: [“source3”]}]

Actual Behavior:

python

# Query 1: [{“id”: “A”, “value”: [“source1”]}]

# Query 2: [{“id”: “A”, “value”: [“source1”]}, {“id”: “A”, “value”: [“source1”]}, {“id”: “B”, “value”: [“source2”]}]

# Query 3: [{“id”: “A”, “value”: [“source1”]}, {“id”: “A”, “value”: [“source1”]}, {“id”: “B”, “value”: [“source2”]}, {“id”: “A”, “value”: [“source1”]}, {“id”: “A”, “value”: [“source1”]}, {“id”: “B”, “value”: [“source2”]}, {“id”: “C”, “value”: [“source3”]}]

Code Example:

python

class MyState(AgentState):

**current_context_source: Annotated\[List\[Dict\[str, str\]\], operator.add\] = Field(*default_factory*=list)**

@tool(“my_tool”)

async def my_tool(query_id: str):

***return***** Command(*update*={**

    **"current_context_source": \[{"id": query_id, "value": \["source"\]}\]**

**})**

Attempted Solutions (All Failed):

1. Custom Lambda Reducer:

python

current_context_source: Annotated[List[Dict], lambda x, y: (x or []) + (y or [])]

Result: Same behavior as operator.add - still creates nested arrays

2. Messages-Style Manual Concatenation:

python

# In tools

existing_sources = state.get(“current_context_source”, [])

“current_context_source”: existing_sources + [{“id”: query_id, “value”: sources}]

Result: Failed because tools don’t have access to state parameter

3. Different Reducer Functions:

  • operator.setitem - Overwrites instead of merging

  • operator.add - Same nested behavior

  • Custom functions - All produce same nested concatenation

4. State Structure Changes:

  • Tried different Annotated syntax

  • Attempted to flatten in custom reducers

  • Modified update patterns

All approaches failed because the issue appears to be in LangGraph’s internal handling of Command(update=…) with operator.add reducers.

Root Cause Analysis:

The issue seems to be that LangGraph internally wraps tool updates in arrays before applying the operator.add reducer, causing:

python

# What we send: [{“id”: “A”, “value”: [“source1”]}]

# What LangGraph does internally: existing + [new_array]

# Instead of: existing + new_items

Workaround:

Currently using response-level deduplication:

python

# Deduplicate at response creation time

unique_sources =

seen_ids = set()

for source in raw_sources:

*if* source\["id"\] not in seen_ids:

    unique_sources.append(source)

    seen_ids.add(source\["id"\])

Request:

Please investigate the operator.add reducer behavior with tool updates and provide a proper solution for flat list merging in state management. Also explain why this does not happen with the in built messafes state in AgentState ?

operator.add is just the python stdlib “+” sign. If you want deduplication then the function should be something different than naive +.

You can check out the add_messages reducer as an example.

1 Like

thank you for the reminder ! I wrote the custom reducer and fixed the issue.