Streaming context leaks between nested graph invocations when using .astream() and .ainvoke() #645

Closed
opened 2026-02-20 17:41:06 -05:00 by yindo · 2 comments
Owner

Originally created by @nsgoneape on GitHub (May 26, 2025).

Originally assigned to: @nfcampos on GitHub.

Checked other resources

  • This is a bug, not a usage question. For questions, please use GitHub Discussions.
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

# Simplified structure of the tool that causes the issue
@tool
async def send_message_to_agent_rep_of_friend_post(
    state: Annotated[State, InjectedState],
    config: RunnableConfig,
    tool_call_id: Annotated[str, InjectedToolCallId],
    *,
    message_to_agent_rep: str,
):
    # ... tool logic ...
    
    # This internal call invokes Graph-B
    response, _ = await TalkService.agent_talk_to_agent(
        message_to_agent_rep, 
        from_agent_id, 
        to_agent_id, 
        from_memory_id, 
        to_memory_id
    )
    
    return Command(
        update={
            "messages": [
                ToolMessage(content="Successfully sent message...", tool_call_id=tool_call_id),
                response
            ]
        }
    )

Error Message and Stack Trace (if applicable)


Description

Title: Streaming context leaks between nested graph invocations when using .astream() and .ainvoke()

Description

When a parent graph that is streaming (using .astream()) invokes a child graph via .ainvoke(), the child graph's outputs are incorrectly streamed to the parent graph's HTTP response stream, even though the child graph is explicitly using non-streaming invocation.

Environment

  • LangGraph version: [your version]
  • Python version: [your version]
  • OS: Windows 10.0.26100

Code Structure

I have a multi-agent system where:

  1. Graph-A (agent 3af5a761...) handles HTTP requests and streams responses to users
  2. Graph-A has a tool that can invoke Graph-B (agent b65705ba...) via an internal service call
  3. Each graph is created fresh via a factory function to ensure instance isolation
# Simplified structure of the tool that causes the issue
@tool
async def send_message_to_agent_rep_of_friend_post(
    state: Annotated[State, InjectedState],
    config: RunnableConfig,
    tool_call_id: Annotated[str, InjectedToolCallId],
    *,
    message_to_agent_rep: str,
):
    # ... tool logic ...
    
    # This internal call invokes Graph-B
    response, _ = await TalkService.agent_talk_to_agent(
        message_to_agent_rep, 
        from_agent_id, 
        to_agent_id, 
        from_memory_id, 
        to_memory_id
    )
    
    return Command(
        update={
            "messages": [
                ToolMessage(content="Successfully sent message...", tool_call_id=tool_call_id),
                response
            ]
        }
    )

The service method that invokes the child graph:

@staticmethod
async def agent_talk_to_agent(agent_input: str, from_agent_id: str, to_agent_id: str, from_memory_id: str, to_memory_id: str):
    # Get Graph-B instance
    to_agent_graph = await get_graph_for_agent(to_agent_id, str(to_memory_id))
    
    # Create isolated config - attempting to prevent streaming
    isolated_config = {
        "configurable": {
            "thread_id": str(to_memory_id),
            "agent_id": to_agent_id,
            "from_agent_id": from_agent_id,
            "from_memory_id": from_memory_id
        },
        "callbacks": None,  # Attempted fix: explicitly set to None
        "recursion_limit": 25,
        "tags": ["nested_invocation", f"parent_agent_{from_agent_id}"],
    }
    
    # Use ainvoke (NOT astream) to avoid streaming
    result = await to_agent_graph.ainvoke(input_data, isolated_config)
    
    return last_ai_message, to_memory_id

The parent graph streaming setup:

async def generator():
    yield f"MEMORY_ID:{thread_id}\n"
    
    async for chunk, _ in agent_graph.astream(
        {"messages": [{"role": "user", "content": user_input, "name": app_user_name}]},
        {"configurable": {
            "thread_id": thread_id, 
            "agent_id": agent_id,
            "app_user_id": str(app_user_id) if app_user_id else None
        }},
        stream_mode="messages"
    ):
        if chunk.content:
            logger.debug(f"Streaming message chunk for agent {agent_id}: {chunk.content}")
            yield chunk.content

return StreamingResponse(generator(), media_type="text/plain")

The Problem

Despite Graph-B being invoked with .ainvoke() (non-streaming) and with an isolated configuration:

  • Graph-B's LLM outputs appear in Graph-A's HTTP stream
  • Log lines show: Streaming message chunk for agent 3af5a7… : [content from Graph-B]
  • This happens BEFORE the tool call returns, so it's not Graph-A appending the result

What I've Tried

  1. Creating fresh graph instances - Each graph is created new via factory functions, no caching
  2. Setting callbacks: None in the child graph's config to break callback propagation
  3. Using asyncio.create_task() to isolate the execution context
  4. Removing all streaming-related configuration from the child graph invocation

Expected Behavior

When Graph-A invokes Graph-B via .ainvoke():

  • Graph-B's outputs should NOT appear in Graph-A's stream
  • Graph-B should execute in complete isolation
  • Only the explicit return value from Graph-B should be available to Graph-A

Actual Behavior

Graph-B's token-by-token LLM outputs leak into Graph-A's HTTP response stream, causing:

  • Confusing user experience (seeing internal agent communications)
  • Incorrect message attribution (Graph-B's outputs appear as if from Graph-A)
  • Violation of execution boundaries between graphs

Additional Context

This appears to be related to how LangGraph propagates streaming contexts through async execution. Even when using .ainvoke() on a child graph, if the parent is in a streaming context, that context seems to leak to the child.

The issue suggests that LangGraph's streaming infrastructure uses some form of context variables or callbacks that persist across graph boundaries, even when explicitly trying to isolate them.

Reproduction

  1. Create a parent graph that streams responses via HTTP
  2. Add a tool to the parent graph that internally calls another graph via .ainvoke()
  3. Observe that the child graph's outputs appear in the parent's stream

This is a critical issue for multi-agent systems where agents need to communicate internally without exposing those communications to end users.

System Info

(.venv) (base) PS C:\ONEAPE\ONEAPE> python -m langchain_core.sys_info

System Information

OS: Windows
OS Version: 10.0.26100
Python Version: 3.12.4 | packaged by Anaconda, Inc. | (main, Jun 18 2024, 15:03:56) [MSC v.1929 64 bit (AMD64)]

Package Information

langchain_core: 0.3.49
langchain: 0.3.22
langchain_community: 0.3.20
langsmith: 0.3.21
langchain_anthropic: 0.3.10
langchain_openai: 0.3.11
langchain_text_splitters: 0.3.7
langgraph_sdk: 0.1.60

Optional packages not installed

langserve

Other Dependencies

aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
anthropic<1,>=0.49.0: Installed. No version info available.
async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
dataclasses-json<0.7,>=0.5.7: Installed. No version info available.
httpx: 0.28.1
httpx-sse<1.0.0,>=0.4.0: Installed. No version info available.
jsonpatch<2.0,>=1.33: Installed. No version info available.
langchain-anthropic;: Installed. No version info available.
langchain-aws;: Installed. No version info available.
langchain-azure-ai;: Installed. No version info available.
langchain-cohere;: Installed. No version info available.
langchain-community;: Installed. No version info available.
langchain-core<1.0.0,>=0.3.45: Installed. No version info available.
langchain-core<1.0.0,>=0.3.49: Installed. No version info available.
langchain-deepseek;: Installed. No version info available.
langchain-fireworks;: Installed. No version info available.
langchain-google-genai;: Installed. No version info available.
langchain-google-vertexai;: Installed. No version info available.
langchain-groq;: Installed. No version info available.
langchain-huggingface;: Installed. No version info available.
langchain-mistralai;: Installed. No version info available.
langchain-ollama;: Installed. No version info available.
langchain-openai;: Installed. No version info available.
langchain-text-splitters<1.0.0,>=0.3.7: Installed. No version info available.
langchain-together;: Installed. No version info available.
langchain-xai;: Installed. No version info available.
langchain<1.0.0,>=0.3.21: Installed. No version info available.
langsmith-pyo3: Installed. No version info available.
langsmith<0.4,>=0.1.125: Installed. No version info available.
langsmith<0.4,>=0.1.17: Installed. No version info available.
numpy<3,>=1.26.2: Installed. No version info available.
openai-agents: Installed. No version info available.
openai<2.0.0,>=1.68.2: Installed. No version info available.
opentelemetry-api: Installed. No version info available.
opentelemetry-exporter-otlp-proto-http: Installed. No version info available.
opentelemetry-sdk: Installed. No version info available.
orjson: 3.10.16
packaging: 24.2
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.11.1
pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available.
pydantic<3.0.0,>=2.5.2;: Installed. No version info available.
pydantic<3.0.0,>=2.7.4: Installed. No version info available.
pydantic<3.0.0,>=2.7.4;: Installed. No version info available.
pytest: Installed. No version info available.
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.3
requests-toolbelt: 1.0.0
requests<3,>=2: Installed. No version info available.
rich: Installed. No version info available.
SQLAlchemy<3,>=1.4: Installed. No version info available.
tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
tiktoken<1,>=0.7: Installed. No version info available.
typing-extensions>=4.7: Installed. No version info available.
zstandard: 0.23.0
(.venv) (base) PS C:\ONEAPE\ONEAPE>

Originally created by @nsgoneape on GitHub (May 26, 2025). Originally assigned to: @nfcampos on GitHub. ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use GitHub Discussions. - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python # Simplified structure of the tool that causes the issue @tool async def send_message_to_agent_rep_of_friend_post( state: Annotated[State, InjectedState], config: RunnableConfig, tool_call_id: Annotated[str, InjectedToolCallId], *, message_to_agent_rep: str, ): # ... tool logic ... # This internal call invokes Graph-B response, _ = await TalkService.agent_talk_to_agent( message_to_agent_rep, from_agent_id, to_agent_id, from_memory_id, to_memory_id ) return Command( update={ "messages": [ ToolMessage(content="Successfully sent message...", tool_call_id=tool_call_id), response ] } ) ``` ### Error Message and Stack Trace (if applicable) ```shell ``` ### Description ## Title: Streaming context leaks between nested graph invocations when using `.astream()` and `.ainvoke()` ### Description When a parent graph that is streaming (using `.astream()`) invokes a child graph via `.ainvoke()`, the child graph's outputs are incorrectly streamed to the parent graph's HTTP response stream, even though the child graph is explicitly using non-streaming invocation. ### Environment - LangGraph version: [your version] - Python version: [your version] - OS: Windows 10.0.26100 ### Code Structure I have a multi-agent system where: 1. **Graph-A** (agent `3af5a761...`) handles HTTP requests and streams responses to users 2. **Graph-A** has a tool that can invoke **Graph-B** (agent `b65705ba...`) via an internal service call 3. Each graph is created fresh via a factory function to ensure instance isolation ```python # Simplified structure of the tool that causes the issue @tool async def send_message_to_agent_rep_of_friend_post( state: Annotated[State, InjectedState], config: RunnableConfig, tool_call_id: Annotated[str, InjectedToolCallId], *, message_to_agent_rep: str, ): # ... tool logic ... # This internal call invokes Graph-B response, _ = await TalkService.agent_talk_to_agent( message_to_agent_rep, from_agent_id, to_agent_id, from_memory_id, to_memory_id ) return Command( update={ "messages": [ ToolMessage(content="Successfully sent message...", tool_call_id=tool_call_id), response ] } ) ``` The service method that invokes the child graph: ```python @staticmethod async def agent_talk_to_agent(agent_input: str, from_agent_id: str, to_agent_id: str, from_memory_id: str, to_memory_id: str): # Get Graph-B instance to_agent_graph = await get_graph_for_agent(to_agent_id, str(to_memory_id)) # Create isolated config - attempting to prevent streaming isolated_config = { "configurable": { "thread_id": str(to_memory_id), "agent_id": to_agent_id, "from_agent_id": from_agent_id, "from_memory_id": from_memory_id }, "callbacks": None, # Attempted fix: explicitly set to None "recursion_limit": 25, "tags": ["nested_invocation", f"parent_agent_{from_agent_id}"], } # Use ainvoke (NOT astream) to avoid streaming result = await to_agent_graph.ainvoke(input_data, isolated_config) return last_ai_message, to_memory_id ``` The parent graph streaming setup: ```python async def generator(): yield f"MEMORY_ID:{thread_id}\n" async for chunk, _ in agent_graph.astream( {"messages": [{"role": "user", "content": user_input, "name": app_user_name}]}, {"configurable": { "thread_id": thread_id, "agent_id": agent_id, "app_user_id": str(app_user_id) if app_user_id else None }}, stream_mode="messages" ): if chunk.content: logger.debug(f"Streaming message chunk for agent {agent_id}: {chunk.content}") yield chunk.content return StreamingResponse(generator(), media_type="text/plain") ``` ### The Problem Despite Graph-B being invoked with `.ainvoke()` (non-streaming) and with an isolated configuration: - Graph-B's LLM outputs appear in Graph-A's HTTP stream - Log lines show: `Streaming message chunk for agent 3af5a7… : [content from Graph-B]` - This happens BEFORE the tool call returns, so it's not Graph-A appending the result ### What I've Tried 1. **Creating fresh graph instances** - Each graph is created new via factory functions, no caching 2. **Setting `callbacks: None`** in the child graph's config to break callback propagation 3. **Using `asyncio.create_task()`** to isolate the execution context 4. **Removing all streaming-related configuration** from the child graph invocation ### Expected Behavior When Graph-A invokes Graph-B via `.ainvoke()`: - Graph-B's outputs should NOT appear in Graph-A's stream - Graph-B should execute in complete isolation - Only the explicit return value from Graph-B should be available to Graph-A ### Actual Behavior Graph-B's token-by-token LLM outputs leak into Graph-A's HTTP response stream, causing: - Confusing user experience (seeing internal agent communications) - Incorrect message attribution (Graph-B's outputs appear as if from Graph-A) - Violation of execution boundaries between graphs ### Additional Context This appears to be related to how LangGraph propagates streaming contexts through async execution. Even when using `.ainvoke()` on a child graph, if the parent is in a streaming context, that context seems to leak to the child. The issue suggests that LangGraph's streaming infrastructure uses some form of context variables or callbacks that persist across graph boundaries, even when explicitly trying to isolate them. ### Reproduction 1. Create a parent graph that streams responses via HTTP 2. Add a tool to the parent graph that internally calls another graph via `.ainvoke()` 3. Observe that the child graph's outputs appear in the parent's stream --- This is a critical issue for multi-agent systems where agents need to communicate internally without exposing those communications to end users. ### System Info (.venv) (base) PS C:\ONEAPE\ONEAPE> python -m langchain_core.sys_info System Information ------------------ > OS: Windows > OS Version: 10.0.26100 > Python Version: 3.12.4 | packaged by Anaconda, Inc. | (main, Jun 18 2024, 15:03:56) [MSC v.1929 64 bit (AMD64)] Package Information ------------------- > langchain_core: 0.3.49 > langchain: 0.3.22 > langchain_community: 0.3.20 > langsmith: 0.3.21 > langchain_anthropic: 0.3.10 > langchain_openai: 0.3.11 > langchain_text_splitters: 0.3.7 > langgraph_sdk: 0.1.60 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp<4.0.0,>=3.8.3: Installed. No version info available. > anthropic<1,>=0.49.0: Installed. No version info available. > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > dataclasses-json<0.7,>=0.5.7: Installed. No version info available. > httpx: 0.28.1 > httpx-sse<1.0.0,>=0.4.0: Installed. No version info available. > jsonpatch<2.0,>=1.33: Installed. No version info available. > langchain-anthropic;: Installed. No version info available. > langchain-aws;: Installed. No version info available. > langchain-azure-ai;: Installed. No version info available. > langchain-cohere;: Installed. No version info available. > langchain-community;: Installed. No version info available. > langchain-core<1.0.0,>=0.3.45: Installed. No version info available. > langchain-core<1.0.0,>=0.3.49: Installed. No version info available. > langchain-deepseek;: Installed. No version info available. > langchain-fireworks;: Installed. No version info available. > langchain-google-genai;: Installed. No version info available. > langchain-google-vertexai;: Installed. No version info available. > langchain-groq;: Installed. No version info available. > langchain-huggingface;: Installed. No version info available. > langchain-mistralai;: Installed. No version info available. > langchain-ollama;: Installed. No version info available. > langchain-openai;: Installed. No version info available. > langchain-text-splitters<1.0.0,>=0.3.7: Installed. No version info available. > langchain-together;: Installed. No version info available. > langchain-xai;: Installed. No version info available. > langchain<1.0.0,>=0.3.21: Installed. No version info available. > langsmith-pyo3: Installed. No version info available. > langsmith<0.4,>=0.1.125: Installed. No version info available. > langsmith<0.4,>=0.1.17: Installed. No version info available. > numpy<3,>=1.26.2: Installed. No version info available. > openai-agents: Installed. No version info available. > openai<2.0.0,>=1.68.2: Installed. No version info available. > opentelemetry-api: Installed. No version info available. > opentelemetry-exporter-otlp-proto-http: Installed. No version info available. > opentelemetry-sdk: Installed. No version info available. > orjson: 3.10.16 > packaging: 24.2 > packaging<25,>=23.2: Installed. No version info available. > pydantic: 2.11.1 > pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available. > pydantic<3.0.0,>=2.5.2;: Installed. No version info available. > pydantic<3.0.0,>=2.7.4: Installed. No version info available. > pydantic<3.0.0,>=2.7.4;: Installed. No version info available. > pytest: Installed. No version info available. > PyYAML>=5.3: Installed. No version info available. > requests: 2.32.3 > requests-toolbelt: 1.0.0 > requests<3,>=2: Installed. No version info available. > rich: Installed. No version info available. > SQLAlchemy<3,>=1.4: Installed. No version info available. > tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available. > tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available. > tiktoken<1,>=0.7: Installed. No version info available. > typing-extensions>=4.7: Installed. No version info available. > zstandard: 0.23.0 (.venv) (base) PS C:\ONEAPE\ONEAPE>
yindo closed this issue 2026-02-20 17:41:06 -05:00
Author
Owner

@nfcampos commented on GitHub (May 27, 2025):

Hi, thanks for opening this issue, I tend to agree this should be changed, so that the current behavior happens only if you call the top graph with stream(..., subgraphs=True), let me discuss w the team, we might include this in the upcoming 1.0 release

@nfcampos commented on GitHub (May 27, 2025): Hi, thanks for opening this issue, I tend to agree this should be changed, so that the current behavior happens only if you call the top graph with `stream(..., subgraphs=True)`, let me discuss w the team, we might include this in the upcoming 1.0 release
Author
Owner

@nfcampos commented on GitHub (May 27, 2025):

Fixing it in #4843, will be included in 1.0 release

@nfcampos commented on GitHub (May 27, 2025): Fixing it in #4843, will be included in 1.0 release
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#645