Skip to content

V2 _Mesh.run_node_impl breaks coordinator generator before execute_tools can run (V1_LLM_AGENT=False) #5283

@perrymanuk

Description

@perrymanuk

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

  1. _Mesh runs the coordinator (_SingleLlmAgent)
  2. Coordinator's inner Workflow runs call_llm → gets function_call for casa(goal="...")
  3. call_llm yields route event to execute_tools
  4. Inner Workflow schedules execute_tools
  5. execute_tools runs RequestTaskTool, sets event.actions.request_task
  6. _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.

Metadata

Metadata

Labels

core[Component] This issue is related to the core interface and implementationneeds review[Status] The PR/issue is awaiting review from the maintainer

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions