Description
When V1_LLM_AGENT is disabled (using the V2 workflow-based LlmAgent), the _Mesh.run_node_impl orchestration loop breaks out of the coordinator's event generator before the inner _SingleLlmAgent Workflow can schedule and run execute_tools. This prevents all tool execution, including RequestTaskTool (task-mode sub-agents) and transfer_to_agent.
Environment
- google-adk version: 2.0.0a3
- Python: 3.12 / 3.14
ADK_DISABLE_V1_LLM_AGENT=true
Setup
Multi-agent system with a root agent (mode='chat') and several sub-agents (mode='task'):
root = Agent(
name="beto",
model="gemini-2.5-flash",
mode="chat",
sub_agents=[
Agent(name="casa", mode="task", tools=[...]),
Agent(name="planner", mode="task", tools=[...]),
# ... more task-mode sub-agents
],
tools=[memory_tools],
)
runner = Runner(agent=root, app_name="beto", session_service=InMemorySessionService())
async for event in runner.run_async(user_id="user", session_id=sess.id, new_message=msg):
...
Expected Behavior
_Mesh runs the coordinator (_SingleLlmAgent)
- Coordinator's inner Workflow runs
call_llm → gets function_call for casa(goal="...")
call_llm yields route event to execute_tools
- Inner Workflow schedules
execute_tools
execute_tools runs RequestTaskTool, sets event.actions.request_task
_Mesh detects request_task and routes to the casa task agent
Actual Behavior
The flow stops at step 3. The _Mesh.run_node_impl iterates the coordinator's events via async for event in run_gen. After call_llm yields its events (model response + route event), the _Mesh checks for transfer_to_agent and request_task on these events — finds neither (because execute_tools hasn't run yet) — and falls through to break at line 228:
# _mesh.py, run_node_impl, ~line 219-228
else:
# No transfer, no task action.
agent_mode = getattr(current_node, 'mode', 'chat')
if agent_mode == 'task' and not self._is_coordinator_name(current_node.name):
break
break
This closes the coordinator's async generator, cancelling all inner Workflow tasks (including the pending execute_tools scheduling).
Root Cause
The _Mesh.run_node_impl uses a synchronous event iteration pattern (async for event in current_node.run(...)) that is incompatible with the _SingleLlmAgent's async task-based Workflow engine.
The inner Workflow (_SingleLlmAgent._run_async_impl / Workflow._run_async_impl) runs call_llm as an async task. When call_llm completes, its _NodeCompletion is processed by _handle_node_completion → _process_triggers → _schedule_node, which schedules execute_tools. But this scheduling happens AFTER call_llm's events are yielded.
The _Mesh consumes these yielded events, finds no actionable info (no transfer_to_agent, no request_task), and breaks. The inner Workflow's event loop never gets a chance to process call_llm's completion and schedule execute_tools.
V1 mode works because BaseLlmFlow executes tools inline (synchronously within the flow) before yielding events to the parent, so the _Mesh never needed to handle this.
Workaround
Use V1 mode (default, V1_LLM_AGENT=True). V1's BaseLlmFlow handles call_llm → execute_tools inline.
Suggested Fix
The _Mesh.run_node_impl should not break out of the coordinator's generator until the generator is exhausted naturally. The "no transfer, no task action" break should only apply when the generator has finished, not after each batch of events. Alternatively, the _Mesh could detect that the coordinator is still running (has pending internal nodes) and continue iterating.
Description
When
V1_LLM_AGENTis disabled (using the V2 workflow-basedLlmAgent), the_Mesh.run_node_implorchestration loop breaks out of the coordinator's event generator before the inner_SingleLlmAgentWorkflow can schedule and runexecute_tools. This prevents all tool execution, includingRequestTaskTool(task-mode sub-agents) andtransfer_to_agent.Environment
ADK_DISABLE_V1_LLM_AGENT=trueSetup
Multi-agent system with a root agent (mode='chat') and several sub-agents (mode='task'):
Expected Behavior
_Meshruns the coordinator (_SingleLlmAgent)call_llm→ gets function_call forcasa(goal="...")call_llmyields route event toexecute_toolsexecute_toolsexecute_toolsrunsRequestTaskTool, setsevent.actions.request_task_Meshdetectsrequest_taskand routes to thecasatask agentActual Behavior
The flow stops at step 3. The
_Mesh.run_node_impliterates the coordinator's events viaasync for event in run_gen. Aftercall_llmyields its events (model response + route event), the_Meshchecks fortransfer_to_agentandrequest_taskon these events — finds neither (becauseexecute_toolshasn't run yet) — and falls through tobreakat line 228:This closes the coordinator's async generator, cancelling all inner Workflow tasks (including the pending
execute_toolsscheduling).Root Cause
The
_Mesh.run_node_impluses a synchronous event iteration pattern (async for event in current_node.run(...)) that is incompatible with the_SingleLlmAgent's async task-based Workflow engine.The inner Workflow (
_SingleLlmAgent._run_async_impl/Workflow._run_async_impl) runscall_llmas an async task. Whencall_llmcompletes, its_NodeCompletionis processed by_handle_node_completion→_process_triggers→_schedule_node, which schedulesexecute_tools. But this scheduling happens AFTERcall_llm's events are yielded.The
_Meshconsumes these yielded events, finds no actionable info (notransfer_to_agent, norequest_task), and breaks. The inner Workflow's event loop never gets a chance to processcall_llm's completion and scheduleexecute_tools.V1 mode works because
BaseLlmFlowexecutes tools inline (synchronously within the flow) before yielding events to the parent, so the_Meshnever needed to handle this.Workaround
Use V1 mode (default,
V1_LLM_AGENT=True). V1'sBaseLlmFlowhandlescall_llm → execute_toolsinline.Suggested Fix
The
_Mesh.run_node_implshould not break out of the coordinator's generator until the generator is exhausted naturally. The "no transfer, no task action"breakshould only apply when the generator has finished, not after each batch of events. Alternatively, the_Meshcould detect that the coordinator is still running (has pending internal nodes) and continue iterating.