Repository navigation
single_turn workflow nodes overwrite explicit include_contents #6046
Description
🔴 Required Information
Please ensure all items in this section are completed to allow for efficient
triaging. Requests without complete information may be rejected / deprioritized.
If an item is not applicable to you - please mark it as N/A
Describe the Bug:
When an LlmAgent is run as a workflow node, ADK defaults the agent mode to single_turn when agent.mode is None. That default behavior is useful.
However, the workflow wrapper also forces every single_turn node to include_contents = "none", even when the user explicitly configured include_contents = "default".
This changes the user-provided agent configuration at runtime and makes it impossible for a single_turn workflow node to intentionally receive relevant conversation/session history.
Steps to Reproduce:
- Create an
LlmAgentwithmode="single_turn"and explicitinclude_contents="default":
from google.adk.agents import LlmAgent
agent = LlmAgent(
name="planner",
mode="single_turn",
include_contents="default",
)- Use the agent as a workflow node.
- Run the workflow node through the workflow wrapper.
- Inspect the agent's
include_contentsbehavior during execution.
Expected Behavior:
single_turn workflow nodes should default to include_contents="none" only when the user did not explicitly configure include_contents.
If a user explicitly sets include_contents, ADK should preserve that value.
Expected behavior:
| Agent config | Workflow behavior |
|---|---|
mode=None, no explicit include_contents |
default to mode="single_turn" and include_contents="none" |
mode="single_turn", no explicit include_contents |
default to include_contents="none" |
mode="single_turn", include_contents="default" |
preserve "default" |
mode="single_turn", include_contents="none" |
preserve "none" |
Observed Behavior:
The workflow wrapper overwrites the explicit include_contents="default" value with "none".
In src/google/adk/workflow/_llm_agent_wrapper.py:
if agent.mode == 'single_turn':
agent.include_contents = 'none'This runs for all single_turn agents, including agents constructed with an explicit include_contents value.
Environment Details:
- ADK Library Version (pip show google-adk): source checkout from
main - Desktop OS: macOS
- Python Version (python -V): Python 3.14.3
Model Information:
- Are you using LiteLLM: N/A
- Which model is being used: N/A
🟡 Optional Information
Providing this information greatly speeds up the resolution process.
Regression:
N/A.
Logs:
N/A.
Screenshots / Video:
N/A.
Additional Context:
Some workflow nodes are deterministic one-shot LLM calls, so single_turn mode is semantically correct. But they may still need relevant prior workflow/session events as model context.
Using chat mode as a workaround changes workflow semantics:
- chat-mode nodes are treated differently by workflow graph construction
- chat mode can imply
wait_for_output=True - chat mode is designed for coordinator-style conversation/task delegation
- the node is no longer clearly expressing "one model call for this workflow step"
So the desired setup is:
LlmAgent(
name="planner",
mode="single_turn",
include_contents="default",
)That should mean "run once as a workflow node, but include relevant session history." Today it silently becomes include_contents="none".
Root cause: the wrapper applies a mode-based override after resolving default mode:
if agent.mode is None:
agent.mode = 'single_turn'
...
if agent.mode == 'single_turn':
agent.include_contents = 'none'The issue is not the single_turn default itself. The issue is that the override does not distinguish between:
include_contentsomitted by the userinclude_contentsexplicitly configured by the user
Since LlmAgent.include_contents has a Pydantic default of "default", a simple truthiness check such as if not agent.include_contents is not sufficient. The code should check whether the field was explicitly set, e.g. via agent.model_fields_set / __pydantic_fields_set__.
Proposed fix:
include_contents_explicit = "include_contents" in agent.model_fields_set
if agent.mode == "single_turn" and not include_contents_explicit:
agent.include_contents = "none"This preserves the current default behavior while respecting explicit user configuration.
Minimal Reproduction Code:
from google.adk.agents import LlmAgent
agent = LlmAgent(
name="planner",
mode="single_turn",
include_contents="default",
)
# Use this agent as a workflow node.
# Expected: include_contents remains "default".
# Current behavior: workflow execution forces include_contents to "none".Suggested tests:
-
LlmAgent(name="a")used as a workflow node:- mode defaults to
single_turn - include contents defaults to
none
- mode defaults to
-
LlmAgent(name="a", mode="single_turn"):- include contents defaults to
none
- include contents defaults to
-
LlmAgent(name="a", mode="single_turn", include_contents="default"):- include contents remains
default
- include contents remains
-
LlmAgent(name="a", mode="single_turn", include_contents="none"):- include contents remains
none
- include contents remains
Also verify that cloning through workflow.node(agent) preserves model_fields_set for include_contents. In current behavior, it appears to do so:
node(LlmAgent(name="b", include_contents="default")).model_fields_set
# includes "include_contents"How often has this issue occurred?:
- Always (100%)
Activity
surajksharma07 commentedon Jun 10, 2026
/adk-issue-analyze
surajksharma07 commentedon Jun 10, 2026
@spectaclehong the model_fields_set check in #6047 is the right approach to distinguish implicit vs. explicit include_contents.
One thing blocking review is the CLA check — once that's signed we should be good to move forward.
In the meantime, could you do a quick end-to-end run with your actual workflow to confirm all four cases (implicit default, explicit "default", explicit "none", mode=None) behave as expected?
Happy to prioritize the review once those are checked off!
spectaclehong commentedon Jun 11, 2026
Thanks @surajksharma07! The CLA check is now passing.
I also ran a local end-to-end workflow check through the actual
Runner -> Workflow -> LlmAgent node -> model requestpath using a two-turn session. The four requested cases behaved as expected:mode=None: the execution node runs assingle_turn, defaultsinclude_contentsto"none", and does not include the previous turn.mode="single_turn"with unsetinclude_contents: defaultsinclude_contentsto"none"and does not include the previous turn.include_contents="default": preserves"default"and includes prior turn context in the second model request.include_contents="none": preserves"none"and does not include the previous turn.The targeted regression test covers the same implicit/explicit
include_contentsbranches, and the local test summaries are in the PR description.surajksharma07 commentedon Jun 11, 2026
@spectaclehong CLA is green and the four-case E2E through the Runner → workflow node → model request path is exactly the confirmation we needed, especially the prior-turn context showing up only in the explicit "default" case.
Reviewed #6047 too — the model_fields_set gate is the right mechanism, and the parametrized test doubling as a check that build_node() cloning preserves the fields-set is a nice touch.
One non-blocking note: agents built from YAML config still can't opt in, since from_config only forwards include_contents when it differs from 'default' (llm_agent.py), so an explicit include_contents: default in config never reaches model_fields_set.
Worth a follow-up issue rather than scope creep here.
Nothing blocking from my side — moving the PR into review now!
Team is looking more into it.
Thanks!
fix(workflow): Preserve explicit single-turn contents
fix(workflow): Preserve explicit single-turn contents