Summary
ADK 2.0.0 GA disabled the documented "task agent as workflow graph node" pattern via a new validator in Workflow. The validator raises:
Agent '<name>' has mode='task' and cannot be used as a workflow graph node. Use a chat coordinator with task sub-agents, or dispatch dynamically via ctx.run_node from a function node.
This breaks code that worked under 2.0.0b1 and uses the canonical agent_in_workflow pattern shipped in upstream samples (contributing/task_samples/agent_in_workflow/). It is not flagged as a breaking change in CHANGELOG-v2.md — line 14 still advertises "task agents as workflow nodes" as a 2.0 feature.
The validator's own docstring and the xfail(strict=True) markers attached to previously-passing tests describe the gate as temporary, pending a scheduler fix. Filing this so external users have a tracking handle for re-enablement.
Source
src/google/adk/workflow/_workflow.py:192-219 — _validate_no_task_mode_graph_nodes:
def _validate_no_task_mode_graph_nodes(self) -> None:
"""Reject ``mode='task'`` LlmAgents that appear as static graph nodes.
Task-mode agents are multi-turn — they pause for user replies and
expect the original ``node_input`` (the task brief) to remain visible
across re-dispatches. The workflow scheduler currently overwrites
``node_input`` with the latest user message on every re-entry, so the
task brief is lost and the agent loses context. Until the scheduler
preserves the originating ``node_input`` on resume, task agents may
only be used:
* as chat sub-agents of an LlmAgent coordinator (FC delegation
via ``_TaskAgentTool`` / ``_dispatch_task_fc``), or
* dispatched dynamically via ``ctx.run_node`` from a custom
function node — never as static workflow graph nodes.
"""
Introduced in commit 16227935 ("chore: switch main to v2.0.0 GA (transition to v2)", 2026-05-19), which appears to be a squash of the GA prep work — no granular PR is visible in the public history.
Upstream xfail evidence
These previously-passing tests were marked xfail(strict=True) in the same commit with the message:
"mode='task' workflow graph nodes temporarily disabled; re-enable when scheduler preserves originating node_input on resume."
tests/unittests/workflow/test_llm_agent_as_node.py — at least lines 261, 320, 384
tests/unittests/workflow/test_workflow_live.py — lines 104, 247, 331, 557
tests/unittests/workflow/test_workflow_llm_agent_interruptions.py — lines 130, 421
strict=True means these are upstream's own admission that the prior behavior worked and is now intentionally blocked — not flaky tests being skipped.
Reproduction
from google.adk.agents import Agent
from google.adk import Workflow
task_agent = Agent(name="my_task", mode="task", instruction="...")
# 2.0.0b1: constructs fine.
# 2.0.0 GA: raises ValueError at construction time.
wf = Workflow(name="root", edges=[("START", task_agent)])
Environment:
google-adk==2.0.0
- Python 3.14
- macOS / Linux (platform-independent — fails at module import)
Impact
- Anyone using the
agent_in_workflow pattern documented in contributing/task_samples/agent_in_workflow/ cannot upgrade from 2.0.0b1 → 2.0.0 without restructuring.
- The two supported workarounds (chat coordinator with task sub-agents via
_TaskAgentTool/_dispatch_task_fc, or dynamic dispatch via ctx.run_node from a function node) both work but have meaningfully different routing semantics from the static-edges shape, so this is a real refactor, not a one-line swap.
- The CHANGELOG should call this out as a breaking change for
2.0.0b1 → 2.0.0 until the gate is lifted.
Tracking ask
- Confirm the gate is temporary as the docstring states.
- Track the scheduler fix (preserving originating
node_input on resume) here or link the canonical tracking issue.
- Add a CHANGELOG entry for the GA breaking change so users upgrading from
2.0.0b1 aren't surprised.
Related issues
Summary
ADK 2.0.0 GA disabled the documented "task agent as workflow graph node" pattern via a new validator in
Workflow. The validator raises:This breaks code that worked under
2.0.0b1and uses the canonicalagent_in_workflowpattern shipped in upstream samples (contributing/task_samples/agent_in_workflow/). It is not flagged as a breaking change inCHANGELOG-v2.md— line 14 still advertises "task agents as workflow nodes" as a 2.0 feature.The validator's own docstring and the
xfail(strict=True)markers attached to previously-passing tests describe the gate as temporary, pending a scheduler fix. Filing this so external users have a tracking handle for re-enablement.Source
src/google/adk/workflow/_workflow.py:192-219—_validate_no_task_mode_graph_nodes:Introduced in commit
16227935("chore: switch main to v2.0.0 GA (transition to v2)", 2026-05-19), which appears to be a squash of the GA prep work — no granular PR is visible in the public history.Upstream
xfailevidenceThese previously-passing tests were marked
xfail(strict=True)in the same commit with the message:tests/unittests/workflow/test_llm_agent_as_node.py— at least lines 261, 320, 384tests/unittests/workflow/test_workflow_live.py— lines 104, 247, 331, 557tests/unittests/workflow/test_workflow_llm_agent_interruptions.py— lines 130, 421strict=Truemeans these are upstream's own admission that the prior behavior worked and is now intentionally blocked — not flaky tests being skipped.Reproduction
Environment:
google-adk==2.0.0Impact
agent_in_workflowpattern documented incontributing/task_samples/agent_in_workflow/cannot upgrade from2.0.0b1→2.0.0without restructuring._TaskAgentTool/_dispatch_task_fc, or dynamic dispatch viactx.run_nodefrom a function node) both work but have meaningfully different routing semantics from the static-edges shape, so this is a real refactor, not a one-line swap.2.0.0b1→2.0.0until the gate is lifted.Tracking ask
node_inputon resume) here or link the canonical tracking issue.2.0.0b1aren't surprised.Related issues
ctx.run_node()sequential calls to multiplesingle_turnagents — second agent receives empty/ignorednode_input#5686 —ctx.run_node()sequential calls to multiplesingle_turnagents — second agent receives empty/ignorednode_input(samenode_input-handling family, dynamic-dispatch surface)node_inputlost across tool rounds within a singlerun_nodeinvocation (same family, inner-LlmAgent surface)event.contentassigned tochild.outputwithout transformation in_reconstruct_node_statesduring node re-run (adjacent re-run/resume area)