Streaming subgraphs results in duplicate messages as subgraph updates parent

I have a multi-agent setup with a router and multiple sub-agents. I am streaming with `subgraphs=True`.

Streaming the subgraphs is working perfectly fine except at the very end. When the subagent completes, the router agent streams its own update which contains the list of all the messages, including the original HumanMessage which it got from the sub agent node.

I’m not sure if I’m missing something here or if I’m just supposed to handle that filtering of the duplicate messages outside of langgraph? I suppose I can keep each message id and only stream “new” messages but it feels like I might be missing something?

Also to be clear, I am using the “add_messages” reducer on my messages field in my state, so I don’t think it’s related to that.

In fact, the behavior here is exactly the same as the example in the docs Stream outputs . I just am unclear what the expectation here is around filtering when streaming for a chat agent. FWIW when I run my graph from LangGraph Studio I do not get the duplicate messages.

Thanks!

Hi @SpyMachine

Streaming subgraphs shows “duplicates” - is this expected?

Short answer: yes, this is expected when subgraphs=True. You’ll receive updates from both the subgraph and the parent. The parent’s final update often includes the subgraph’s contributions (e.g., appended messages), so if you render both verbatim you’ll see apparent duplicates. This affects the stream of events, not your state reducer correctness.

Why it happens

  • Streaming semantics: With subgraphs=True, the stream emits outputs from the subgraph namespace and then a parent namespace update that reflects the parent’s state after incorporating those subgraph updates. This is visible in the “Stream subgraph outputs” examples where events arrive for the subgraph, and then the parent emits its own update afterward.
  • Reducers (like add_messages): Using add_messages is fine here - this isn’t a double-append bug. It’s simply that the stream includes both the subgraph event and the parent’s state update that includes the subgraph’s changes.

How to handle it in a chat UI

  1. Filter by namespace (recommended for stream_mode="updates")

    • Only surface updates from non-empty namespaces (i.e., subgraphs) for your live message feed, and treat the empty-namespace parent update as a summary event; or
    • Surface only the “leaf” updates (deepest namespace), ignoring the parent’s roll-up when rendering messages.

    Example sketch:

for namespace, delta in graph.stream(inputs, stream_mode="updates", subgraphs=True):
    is_parent = len(namespace) == 0
    if is_parent:
        # Option A: ignore parent updates for message rendering
        continue
    # Option B: or only pass through certain keys from parent/subgraphs
    render(delta)
  1. Track message IDs (works across modes)

    • Keep a set of message.id (or a stable hash of role+content+tool_call_id) you’ve already emitted. When a new chunk arrives, only forward messages whose IDs you haven’t seen.

    Example sketch:

seen = set()
for namespace, delta in graph.stream(inputs, stream_mode="updates", subgraphs=True):
    for msg in delta.get("messages", []):
        msg_id = getattr(msg, "id", None) or (msg.role, msg.content)
        if msg_id in seen:
            continue
        seen.add(msg_id)
        render(msg)
  1. If you’re token-streaming, filter by node metadata
    • With stream_mode="messages", filter using metadata["langgraph_node"] or metadata["tags"] so you only render from the specific node(s) you want (e.g., a sub-agent node), avoiding the parent’s final message tokens.

FWIW: Why LangGraph Studio doesn’t show “duplicates”

References

Thank you very much @pawel-twardziak. That jives with my understanding I just wanted to make sure I wasn’t missing something obvious. I thought something like this would have some prebuilt solution given how ubiquitous the “chat bot” use case is.

Appreciate your help!

1 Like