Skip to content

fix: scope single-turn node inputs to workflow branch#6008

Open
he-yufeng wants to merge 1 commit into
google:mainfrom
he-yufeng:fix/node-input-branch-scope
Open

fix: scope single-turn node inputs to workflow branch#6008
he-yufeng wants to merge 1 commit into
google:mainfrom
he-yufeng:fix/node-input-branch-scope

Conversation

@he-yufeng
Copy link
Copy Markdown
Contributor

Summary

Fixes #5989.

prepare_llm_agent_input() appends the single-turn node input directly to
session.events, so it bypasses NodeRunner._enrich_event(). In a dynamic
workflow sub-branch, that left the private node input as a branchless user event.
Branchless user events are visible to sibling branches, which can make another
worker pick the wrong "current turn" boundary and slice out its own function
call.

This stamps the single-turn input event with the current invocation branch when
one exists, while preserving the existing isolation-scope stamping.

To verify

  • PYTHONPATH=src python -m pytest tests\unittests\workflow\test_llm_agent_as_node.py -q -k "single_turn_input_event_inherits_branch_and_scope"
  • PYTHONPATH=src python -m pytest tests\unittests\workflow\test_llm_agent_as_node.py -q
  • python -m py_compile src\google\adk\workflow\_llm_agent_wrapper.py tests\unittests\workflow\test_llm_agent_as_node.py
  • python -m pyink --check src\google\adk\workflow\_llm_agent_wrapper.py tests\unittests\workflow\test_llm_agent_as_node.py
  • python -m isort --check-only src\google\adk\workflow\_llm_agent_wrapper.py tests\unittests\workflow\test_llm_agent_as_node.py
  • git diff --check

@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Jun 7, 2026
@adk-bot
Copy link
Copy Markdown
Collaborator

adk-bot commented Jun 7, 2026

Response from ADK Triaging Agent

Hello @he-yufeng, thank you for creating this PR!

While reviewing your PR, we noticed it is mostly following the contribution guidelines. However, to help speed up the review process, could you please provide:

  • A summary of the passed pytest results.
  • Logs or screenshots after the fix is applied (or the output showing the issue resolved) to help reviewers better understand the fix.

You can find more details in our contribution guidelines.

Thank you!

@adk-bot
Copy link
Copy Markdown
Collaborator

adk-bot commented Jun 7, 2026

🔍 ADK Pull Request Analysis: PR #6008

Title: fix: scope single-turn node inputs to workflow branch
Author: @he-yufeng
Status: OPEN
Impact: 27 additions, 2 deletions across 2 files

Executive Summary

  1. Core Objective: Prevents the single-turn agent node-input user-role event from being treated as globally visible across concurrent execution branches, avoiding subsequent "turn-boundary" truncation errors.
  2. Justification & Value: [Justified Fix] - A highly valuable fix addressing a severe intermittent concurrency bug (ValueError: No function call event found for function responses ids) when executing parallel tool-using agents as dynamic workflows.
  3. Alignment with Principles: [Pass] - Follows all modern python conventions (annotations, standard types), keeps boundaries clean without breaking public API compatibility, and provides clean test coverage using the standard Arrange-Act-Assert pattern.
  4. Recommendation: Approve
Detailed Findings & Analysis

1. Objectives & Impact ("What does it do?")

  • Context & Background: Resolves linked Issue #5989. When executing concurrent tool-using LlmAgents via a dynamic sub-branch orchestration flow (use_sub_branch=True), the orchestrator concurrently executes node iterations. Because the input user event for single-turn mode was previously appended without a branch property, it was globally matched as belonging to every branch. During multi-round tool loops, a sibling's input event could interleave into another worker's timeline, acting as an erroneous "latest turn boundary" and slicing out that worker's own function_call event context, raising a ValueError.
  • Implementation Mechanism:
    In _llm_agent_wrapper.py, during the assembly of the user_event, the branch is now dynamically stamped from the active invocation context if it exists:
    branch = getattr(ctx._invocation_context, 'branch', None)
    if branch:
      user_event.branch = branch
    This matches the behavior of NodeRunner._enrich_event() and ensures that the local agent input is correctly isolated.
  • Affected Surface: It safely restricts scope purely to the private/internal _llm_agent_wrapper.py logic, ensuring zero changes to any public-facing API signatures.

2. Justification & Value ("Is it a valid and useful change?")

  • Workspace Verification:
    • Investigating _llm_agent_wrapper.py verifies that prior to the PR, only containment (via isolation_scope) was stamped, while the branch remained completely absent on the user-role event.
    • Investigating _is_event_belongs_to_branch() confirms that not event.branch forces any event to belong to all branches:
      if not invocation_branch or not event.branch:
        return True
      This confirms the issue description: a branch-less user event erroneously scopes to concurrent sibling nodes.
  • Value Assessment: Highly critical. Without this, multi-agent frameworks using parallel tasks under the same session would consistently crash under heavy concurrency and staggered tool loops.
  • Alternative Approaches: The developer chose the most elegant design—applying the existing branch context to the event rather than patching the branch-matching consumer, keeping the "blast radius" completely localized.
  • Scope & Depth: [Systematic Fix] & [Root Cause] — Rather than applying a bandaid workaround at the parser level, this fixes the root cause: the node-input event was lacking the identifier for the branch it was spawned in.

3. Principle & Style Alignment Checklist ("Does it follow rules?")

  • Public API & Visibility Boundaries:
    • Status: Pass
    • Analysis: No breaking changes. The modified function prepare_llm_agent_input remains fully internal to the private framework workflow orchestration.
  • Code Quality, Typing & Conventions:
    • Status: Pass
    • Analysis: Complies with from __future__ import annotations. Uses standard getattr properties for defensive retrieval, leaving the core implementation clean, typed, and style-compliant. No imports from __init__.py.
  • Robustness & Edge Cases:
    • Status: Pass
    • Analysis: Defensive checks using getattr(..., None) handle the edge case where the context is run outside of an active workflow sub-branch, maintaining backward compatibility seamlessly.
  • Test Integrity & Quality:
    • Status: Pass
    • Analysis: The new test test_single_turn_input_event_inherits_branch_and_scope is beautifully integrated into test_llm_agent_as_node.py. It avoids mocks and uses real ADK invocation wrappers, cleanly formatted using the Arrange-Act-Assert pattern.

Summary of Work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core [Component] This issue is related to the core interface and implementation

Projects

None yet

2 participants