Repository navigation
Description
🔴 Required Information
Describe the Bug:
Run an LlmAgent as a single_turn workflow node with an isolation scope (ctx.run_node(agent, node_input=..., override_isolation_scope=...)). Its model request starts with the invocation's user message plus the single-turn nudge; the node input comes second. Without the scope, the request holds the node input only.
Cause (2.13.0, under src/google/adk/): for a scoped agent the content builder inserts a leading user turn (flows/llm_flows/context/_contents.py:659-667). It is meant to be the delegated task, but a workflow node has no delegation call, so it falls back to ic.user_content (:340-345), the workflow's message. Task mode overwrites ic.user_content with the node input (workflow/_llm_agent_wrapper.py:451-452); single_turn does not.
Steps to Reproduce:
pip install google-adk==2.13.0- Run the script below (scripted model, no API key). It runs the same agent once without and once with a scope.
Expected Behavior:
Both runs print model saw ['user: node input: add 2 and 3'].
Observed Behavior:
google-adk 2.13.0
isolation scope=None: model saw ['user: node input: add 2 and 3']
isolation scope='worker-scope': model saw ['user: workflow message | Important: You will not receive any user replies or clarifications. Complete the task using only the information provided above.', 'user: node input: add 2 and 3']
Environment Details:
- ADK Library Version (pip show google-adk): 2.13.0 (same on 2.3.0, 2.11.0 and 2.12.0)
- Desktop OS: macOS 15
- Python Version (python -V): Python 3.12.8
Model Information:
- Are you using LiteLLM: No
- Which model is being used: N/A (scripted
BaseLlm)
🟡 Optional Information
Regression: No, unchanged since 2.3.0.
Logs: See Observed Behavior.
Screenshots / Video: N/A
Additional Context: #7650 reports the same fallback after a tool-confirmation resume. Workaround in use: set _invocation_context.user_content = None on the scoped single_turn node after prepare_llm_agent_context.
Minimal Reproduction Code:
import asyncio
import google.adk
from google.adk.agents import Context, LlmAgent
from google.adk.apps import App
from google.adk.models.base_llm import BaseLlm
from google.adk.models.llm_response import LlmResponse
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.workflow import START, Workflow, node
from google.genai import types
WORKFLOW_MESSAGE = "workflow message"
NODE_INPUT = "node input: add 2 and 3"
model_calls: list[list[str]] = []
class RecordingLlm(BaseLlm):
model: str = "recording"
async def generate_content_async(self, llm_request, stream=False):
model_calls.append(
[f"{c.role}: {' | '.join(p.text for p in c.parts if p.text)}" for c in llm_request.contents]
)
yield LlmResponse(content=types.Content(role="model", parts=[types.Part(text="5")]))
def make_workflow(scope: str | None) -> Workflow:
worker = LlmAgent(name="worker", model=RecordingLlm(), instruction="Add the numbers.", mode="single_turn")
@node(rerun_on_resume=True)
async def orchestrator(ctx: Context, node_input):
yield await ctx.run_node(worker, node_input=NODE_INPUT, override_isolation_scope=scope)
return Workflow(name="root", edges=[(START, orchestrator)])
async def run(scope: str | None) -> None:
model_calls.clear()
sessions = InMemorySessionService()
runner = Runner(app=App(name="repro", root_agent=make_workflow(scope)), session_service=sessions)
session = await sessions.create_session(app_name="repro", user_id="u")
async for _ in runner.run_async(
user_id="u", session_id=session.id, new_message=types.UserContent(WORKFLOW_MESSAGE)
):
pass
print(f"isolation scope={scope!r}: model saw {model_calls[0]}")
async def main():
print("google-adk", google.adk.__version__)
await run(None)
await run("worker-scope")
asyncio.run(main())
Activity
surajksharma07 commentedon Nov 2, 2026
@TomShlaim. Reproduced this on 2.13.0 and on current main: with a scope set, the single_turn worker gets the workflow message plus the nudge before its node input. It looks like the same user_content fallback as #7650.
As a workaround, clearing user_content on the scoped single_turn node’s own context, like you’re already doing, gives the same contents as the unscoped run in my test. A proper fix would probably be to set user_content to None for scoped single_turn nodes in run_llm_agent_as_node, right next to the task-mode override.
Could you check whether that works for your real workflows too? If you’d like to raise a PR please test it well first (scoped/unscoped single_turn, task mode and the resume case from #7738) and add a unit test.
willie3838 commentedon Nov 2, 2026
An internal team will take this on! Thanks for flagging this @TomShlaim
TomShlaim commentedon Nov 3, 2026
Hi @surajksharma07 @willie3838
Perfect, thanks a lot!