Skip to content

fix(workflow): keep a node's branch when it relays sub-agent events - #7940

Closed
AtulJoshi1206 wants to merge 1 commit into
google:mainfrom
AtulJoshi1206:fix/node-relayed-branch
Closed

AtulJoshi1206 wants to merge 1 commit into
google:mainfrom
AtulJoshi1206:fix/node-relayed-branch

Conversation

@AtulJoshi1206

Copy link
Copy Markdown
Contributor

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

No existing issue; described below following the bug-report structure.

Describe the Bug:

When an agent that runs its sub-agents on their own branches is run as a node, the node ends up on the branch of whichever sub-agent spoke last. In practice that's a ParallelAgent, on its own or inside a SequentialAgent, either as a Workflow node or as a sub-agent that a root LlmAgent transfers to.

NodeRunner._enrich_event moves the node onto the branch of every event it enqueues, including the sub-agent events the node is only relaying. So after the parallel step, the node's later events are stamped with that sub-branch, and the workflow hands the same sub-branch to the node's successors. _track_event_in_context already ignores relayed events for route and transfer_to_agent, for the same reason (6d7bab6), but the branch never got that check.

Impact:

  • A summarizer or merger after the parallel step only sees one sub-agent's output, because downstream agents read history filtered by that one sub-branch. Which one depends on which sub-agent happened to finish last.
  • Replies after the parallel step land on one sub-agent's branch. On the next turn that sub-agent sees them and its siblings don't.
  • The re-stamp suggested in [Feature] Opt-in visibility of sibling agents' final responses across branches #6496 (a dispatcher BaseAgent setting event.branch = parent_branch on its sub-agent's final answer) gets undone when the dispatcher runs as a node.
  • None of this happens when the same agent tree runs as a legacy root agent, so it shows up when moving it under a Workflow or a root LlmAgent coordinator.

Observed Behavior:

research = ParallelAgent(name='research', sub_agents=[researcher_a, researcher_b])
report = SequentialAgent(name='report', sub_agents=[summarizer])
wf = Workflow(name='wf', edges=[(START, research, report)])
  • The summarizer runs on research.researcher_b, and its LLM request only contains finding from b.
  • With a root LlmAgent that transfers to SequentialAgent([ParallelAgent([worker_a, worker_b]), merger]), the merger's reply is stamped fan_out.worker_b. With the same pipeline as the root agent it's None.
  • With the [Feature] Opt-in visibility of sibling agents' final responses across branches #6496 dispatcher re-stamp running as a Workflow node, the final answer stays on dispatcher.bookings next to its tool call.

Expected Behavior:

A relayed event keeps its own sub-branch, but the node stays on its own branch. The summarizer sees both findings, the merger's reply stays on the pipeline's branch (as on the legacy root path), and a re-stamped answer stays where the dispatcher put it.

Solution:

I moved the existing "is this the node's own event" check out of _track_event_in_context into NodeRunner._is_native_event, and used it in _enrich_event too. Only an event the node authors, or one with no author, can move the node's branch now. A relayed event is still stamped with its own branch; it just doesn't drag the node along.

Unchanged:

  • A node's own event on another branch still moves it there. test_child_event_branch_does_not_mutate_parent_ic and a new test cover this.
  • JoinNode's ""-to-root normalization.

The change is in NodeRunner, so it covers every agent that relays its sub-agents' events, without touching the agents themselves. That includes static and dynamic nodes, and live mode.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

New tests:

  • test_node_runner_ctx.py::test_own_event_branch_moves_node_branch: pins the behaviour that stays, and passes before and after.
  • test_node_runner_ctx.py::test_relayed_event_branch_does_not_move_node_branch
  • test_workflow_agent_as_node.py::test_node_after_parallel_agent_node_sees_every_branch: end to end through Runner, checking the summarizer's actual LLM request.
  • test_workflow_agent_as_node.py::test_answer_relayed_onto_parent_branch_stays_there: the [Feature] Opt-in visibility of sibling agents' final responses across branches #6496 pattern as a node.
  • test_agent_transfer.py::test_auto_to_sequential_with_parallel_keeps_merger_branch[True/False]

On main the new tests give 5 failed, 1 passed; with the fix, 6 passed.

pytest -n 8 tests/unittests/workflow tests/unittests/agents tests/unittests/runners tests/unittests/flows
main: 5 failed (the new tests), 3534 passed
fix:  3539 passed, 1 skipped, 5 xfailed

pyink and isort are clean.

Manual End-to-End (E2E) Tests:

Not run against a real model; the tests use MockModel.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules. (none)

Additional context

#3470 looks similar but is a different problem: branch-prefix matching between nested legacy agents on the legacy path. This one is the node runtime picking up a relayed event's branch.

🤖 Generated with Claude Code

A structured agent run as a node, such as a ParallelAgent or a
SequentialAgent that contains one, re-yields the events its sub-agents
emit. That happens for a Workflow node and for a sub-agent that a root
LlmAgent transfers to. NodeRunner._enrich_event moved the node onto the
branch of every event it enqueued, so after a ParallelAgent finished the
node was left on the sub-branch of whichever sub-agent spoke last. The
node's later events were stamped with that branch, and the workflow
handed it to the node's successors.

An agent after the parallel step then read history filtered by that one
sub-branch and saw only that sub-agent's output, and on later turns only
that sub-agent saw the replies stamped after it. The same agent tree run
as a legacy root agent keeps those replies on the parent's branch. It
also undid a dispatcher agent re-stamping a sub-agent's final answer onto
its own branch: the tool events had already moved the node, so the
branch-less answer was stamped with the sub-branch again.

Route and transfer_to_agent are already taken only from the node's own
events, for the same reason. Apply that rule to the branch too, and keep
it in one helper so the two uses can't drift apart.
copybara-service Bot pushed a commit that referenced this pull request Nov 3, 2026
Merge #7940

A node that relays its sub-agents' events (such as a ParallelAgent or a
SequentialAgent containing one, run as a Workflow node or transferred to from
a root LlmAgent) no longer moves onto the branch of whichever sub-agent spoke
last. Relayed events keep their own sub-branch; the node's later events and
its successors stay on the node's branch, so an agent after a parallel step
sees every sub-agent's output. Resuming a paused workflow recovers the same
branch, so a node after the parallel step that paused for a tool
confirmation resumes where it paused and runs the confirmed tool.

PiperOrigin-RevId: 1013319278
@adk-bot

adk-bot commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Thank you @AtulJoshi1206 for your contribution! 🎉

Your changes have been successfully imported and merged via Copybara in commit 27edb47.

Closing this PR as the changes are now in the main branch.

@adk-bot adk-bot added the merged [Status] This PR is merged label Nov 3, 2026
@adk-bot adk-bot closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged [Status] This PR is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants