Skip to content

single_turn workflow nodes overwrite explicit include_contents #6046

Closed

Description

@spectaclehong

🔴 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:

  1. Create an LlmAgent with mode="single_turn" and explicit include_contents="default":
from google.adk.agents import LlmAgent

agent = LlmAgent(
    name="planner",
    mode="single_turn",
    include_contents="default",
)
  1. Use the agent as a workflow node.
  2. Run the workflow node through the workflow wrapper.
  3. Inspect the agent's include_contents behavior 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:

  1. include_contents omitted by the user
  2. include_contents explicitly 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:

  1. LlmAgent(name="a") used as a workflow node:

    • mode defaults to single_turn
    • include contents defaults to none
  2. LlmAgent(name="a", mode="single_turn"):

    • include contents defaults to none
  3. LlmAgent(name="a", mode="single_turn", include_contents="default"):

    • include contents remains default
  4. LlmAgent(name="a", mode="single_turn", include_contents="none"):

    • include contents remains none

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

  1. added
    workflow[Component] This issue is related to ADKworkflow
    on Jun 10, 2026
  2. added theissue type on Jun 10, 2026
  3. surajksharma07 commented on Jun 10, 2026

    @surajksharma07
    Collaborator

    /adk-issue-analyze

  4. surajksharma07 commented on Jun 10, 2026

    @surajksharma07
    Collaborator

    @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!

  5. spectaclehong commented on Jun 11, 2026

    @spectaclehong
    ContributorAuthor

    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 request path using a two-turn session. The four requested cases behaved as expected:

    • implicit mode=None: the execution node runs as single_turn, defaults include_contents to "none", and does not include the previous turn.
    • explicit mode="single_turn" with unset include_contents: defaults include_contents to "none" and does not include the previous turn.
    • explicit include_contents="default": preserves "default" and includes prior turn context in the second model request.
    • explicit include_contents="none": preserves "none" and does not include the previous turn.

    The targeted regression test covers the same implicit/explicit include_contents branches, and the local test summaries are in the PR description.

  6. surajksharma07 commented on Jun 11, 2026

    @surajksharma07
    Collaborator

    @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!

  7. added
    needs review[Status] The PR/issue is awaiting review from the maintainer
    and removed
    request clarification[Status] The maintainer need clarification or more information from the author
    on Jun 11, 2026
  8. added a commit that references this issue on Jun 12, 2026
    59f7bdf
  9. added a commit that references this issue on Aug 11, 2026
    9bab93a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

needs review[Status] The PR/issue is awaiting review from the maintainerworkflow[Component] This issue is related to ADKworkflow

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions