fix(workflow): isolate and clean up single_turn LlmAgent node_input events - #7320
Open
abhayjoshi201 wants to merge 1 commit into
Open
abhayjoshi201 wants to merge 1 commit into
abhayjoshi201 wants to merge 1 commit into
Conversation
nanhe17
reviewed
Sep 28, 2026
nanhe17
left a comment
There was a problem hiding this comment.
Verified locally on Windows (Python 3.12, current main 044a1ec vs this branch):
tests/unittests/workflow/test_workflow_dynamic_nodes.py::test_inner_llm_agent_node_input_survives_tool_round_trippasses on this branch. Worth flagging for reviewers: the earlierisolation_scope-assignment approach discussed in #7227 breaks exactly this test (a scoped node request no longer sees its own unscoped FC/FR parts after a tool round trip), so thenode_info.path-based filtering + cleanup design here avoids that failure mode entirely.- The two new tests (
test_single_turn_node_input_does_not_leak_across_sequential_tools,test_parallel_single_turn_nodes_only_see_own_node_input) pass. - Full
tests/unittests/workflow/suite: 694 passed, 1 skipped, 5 xfailed, no failures.
One non-blocking nit: the finally cleanup uses injected_input_event in agent_ctx.session.events / .remove(), which goes through pydantic value equality. Today Event.id (a uuid) keeps two equal-content synthetic events distinct, so this is safe as written — but an identity-based check (any(e is injected_input_event ...) would be robust even if a future Event change drops or reuses ids. Not a merge blocker.
The approach also keeps real user turns untouched, since Event.node_info defaults to NodeInfo() (empty path) via default_factory and the filter only fires when ev_node_path is non-empty.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7227
Summary
When a
Workflowcontainingsingle_turnLlmAgentnodes is run from a root chat agent's tool (await tool_context.run_node(workflow, ...)),branchisNoneandisolation_scopeisNone. Previously,prepare_llm_agent_inputappended a syntheticEvent(author='user', message=agent_input)directly to the shared in-memoryctx.session.eventslist without scoping it to the target node or removing it after the node finished.During sequential multi-tool turns (
fc-a -> fr-afollowed byfc-b -> fr-bin the same user turn),_rearrange_events_for_async_function_responses_in_historymovedfr-anext tofc-aand left the unpersisted syntheticuser_event(including any inline binary/PDF parts) inresult_events, leaking it verbatim into the root chat agent's LLM request. Similarly, concurrentsingle_turnLlmAgentnodes running viaasyncio.gatheronbranch=Nonecould see each other's syntheticuser_eventwhile in flight.Why
isolation_scopeIs Not Used for Unscopedsingle_turnNodesAssigning a synthetic
isolation_scopeto unscopedsingle_turnnodes causes two side effects:_get_contents(_contents.py) treats any non-Noneisolation_scopeas a task-scoped agent and invokes_build_task_input_user_content, prepending the root workflow'suser_content+_SINGLE_TURN_NUDGE.test_inner_llm_agent_node_input_survives_tool_round_trip),NodeRunner._enrich_eventstamps the node'sfunction_callandfunction_responseevents withctx.isolation_scope(None), so a syntheticic.isolation_scopecauses_should_include_event_in_contextto drop thesingle_turnagent's own tool call/response events on the post-tool LLM request.Changes
src/google/adk/workflow/_llm_agent_wrapper.py:user_event.node_info.path = ctx.node_pathinprepare_llm_agent_inputwhenctx.node_pathis non-empty, and returnuser_event.run_llm_agent_as_node, remove the injected syntheticuser_eventfromagent_ctx.session.eventsin afinally:block onceagent.run_async(ic)completes.src/google/adk/flows/llm_flows/context/_contents.py:node_path=invocation_context.node_pathinto_get_contents,_get_current_turn_contents, and_should_include_event_in_context.event.author == 'user'with a non-emptyevent.node_info.path) wheneverevent.node_info.path != (node_path or '').tests/unittests/workflow/test_llm_agent_as_node.py:test_single_turn_node_input_does_not_leak_across_sequential_tools(verifying text + inline binary PDF parts reach the worker across sequential tool calls without leaking into any root agent LLM request).test_parallel_single_turn_nodes_only_see_own_node_input(verifying concurrentsingle_turnnodes underbranch=Noneonly see their ownnode_input).