[GH-ISSUE #560] stream.subagents shares messages / toolCalls between parallel same-type subagent invocations #279

Open
opened 2026-06-05 17:21:26 -04:00 by yindo · 1 comment
Owner

Originally created by @luizzappa on GitHub (May 30, 2026).
Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/560

Originally assigned to: @christian-bromann on GitHub.

Description

When consuming Deep Agents event streams with stream.subagents, parallel invocations of the same subagent type appear to share the same messages and toolCalls projections.

For example, if the coordinator calls two task tools in parallel with the same subagent_type, each returned subagent.toolCalls stream can receive tool calls from both subagent invocations. This makes the UI show duplicated or cross-contaminated tool calls.

Expected behavior

Each SubagentRunStream yielded by stream.subagents should expose only the messages/tool calls for that specific subagent invocation.

for await (const subagent of stream.subagents) {
  void (async () => {
    for await (const call of subagent.toolCalls) {
      // Expected: only tool calls for this specific subagent invocation
      console.log(subagent.name, call.name);
    }
  })();
}

Actual behavior

When two same-type subagents run in parallel:

  • subagent A’s toolCalls stream receives A and B tool calls
  • subagent B’s toolCalls stream receives A and B tool calls
  • same risk appears to apply to messages

Suspected cause

In createSubagentTransformer, subagent stream logs appear to be keyed by subagentName rather than by a unique subagent invocation id.

That means parallel invocations with the same subagent_type reuse the same internal StreamChannels:

  • messagesLog
  • toolCallsLog
  • nestedSubagentsLog

So multiple SubagentRunStreams can point at the same channels.

Suggested fix

Key subagent projection state by a unique invocation key, such as the task tool call id, instead of by subagent name.

For child tool calls, also key pending tool-call state by both subagent invocation and child tool call id, to avoid collisions across sibling subagents.

Conceptually:

const subagentKey = toolCallId ? `task:${toolCallId}` : `subagent:${nextSubagentKey++}`;
subagentsByKey.set(subagentKey, logs);
toolsNodeToKey.set(namespaceSegment, subagentKey);
const childToolCallKey = `${subagentKey}:${toolCallId}`;

Environment

  • Package: deepagents
  • Version: 1.10.2
  • Runtime: JavaScript / TypeScript
  • Streaming API: agent.streamEvents(input, { version: "v3" })

Additional context

This is especially visible in live UIs that consume coordinator and subagent projections concurrently, as recommended in the event streaming docs.

Originally created by @luizzappa on GitHub (May 30, 2026). Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/560 Originally assigned to: @christian-bromann on GitHub. ## Description When consuming Deep Agents event streams with `stream.subagents`, parallel invocations of the same subagent type appear to share the same `messages` and `toolCalls` projections. For example, if the coordinator calls two `task` tools in parallel with the same `subagent_type`, each returned `subagent.toolCalls` stream can receive tool calls from both subagent invocations. This makes the UI show duplicated or cross-contaminated tool calls. ## Expected behavior Each `SubagentRunStream` yielded by `stream.subagents` should expose only the messages/tool calls for that specific subagent invocation. ```ts for await (const subagent of stream.subagents) { void (async () => { for await (const call of subagent.toolCalls) { // Expected: only tool calls for this specific subagent invocation console.log(subagent.name, call.name); } })(); } ``` ## Actual behavior When two same-type subagents run in parallel: - subagent A’s toolCalls stream receives A and B tool calls - subagent B’s toolCalls stream receives A and B tool calls - same risk appears to apply to messages ## Suspected cause In `createSubagentTransformer`, subagent stream logs appear to be keyed by `subagentName` rather than by a unique subagent invocation id. That means parallel invocations with the same `subagent_type` reuse the same internal `StreamChannels`: - messagesLog - toolCallsLog - nestedSubagentsLog So multiple `SubagentRunStreams` can point at the same channels. ## Suggested fix Key subagent projection state by a unique invocation key, such as the `task` tool call id, instead of by subagent name. For child tool calls, also key pending tool-call state by both subagent invocation and child tool call id, to avoid collisions across sibling subagents. Conceptually: ```js const subagentKey = toolCallId ? `task:${toolCallId}` : `subagent:${nextSubagentKey++}`; subagentsByKey.set(subagentKey, logs); toolsNodeToKey.set(namespaceSegment, subagentKey); const childToolCallKey = `${subagentKey}:${toolCallId}`; ``` ## Environment - Package: `deepagents` - Version: `1.10.2` - Runtime: JavaScript / TypeScript - Streaming API: `agent.streamEvents(input, { version: "v3" })` ## Additional context This is especially visible in live UIs that consume coordinator and subagent projections concurrently, as recommended in the event streaming docs.
Author
Owner

@christian-bromann commented on GitHub (May 30, 2026):

Thanks for reporting, will take a look!

<!-- gh-comment-id:4585142018 --> @christian-bromann commented on GitHub (May 30, 2026): Thanks for reporting, will take a look!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#279