Skip to content

state_delta passed to /run endpoint is silently ignored in Workflow (NodeRunner) path #5763

@trongthanht3

Description

@trongthanht3

🔴 Required Information

Describe the Bug:
When triggering an agent via the /run or /run_sse API endpoints, clients can provide a state_delta payload to initialize or update the session state before the agent executes.

This works correctly for traditional agents running through _run_with_trace. However, it is silently ignored when the agent uses the modern Workflow/NodeRunner architecture. The state_delta gets dropped during the transition from run_async to _run_node_async, meaning nodes never receive the intended initial state.

Steps to Reproduce:

  1. Start the ADK web server.
  2. Make a POST request to /run (or /run_sse), providing both new_message and a state_delta for an LlmAgent/Agent:
    {
      "appName": "normal_agent",
      "userId": "user",
      "sessionId": "test_state_delta",
      "newMessage": {
        "parts": [{"text": "test message state delta"}],
        "role": "user"
      },
      "streaming": false,
      "stateDelta": {
        "test_state": "must_change"
      }
    }
  3. Observe that the agent executes, but the session state remains empty ({}).

Expected Behavior:
The state_delta provided in the API request should be applied to the session.state (and persisted as an EventActions payload on the initial user event) BEFORE the workflow starts executing, making it immediately available to all nodes via ctx.state.

Observed Behavior:
The state_delta is silently dropped.

Environment Details:

  • ADK Library Version: 2.0.0-beta.1
  • Desktop OS: Windows / macOS / Linux
  • Python Version: Python 3.10+

Model Information:

  • Are you using LiteLLM: Yes
  • Which model is being used: gemini-3-flash-preview

🟡 Optional Information

Regression:
Yes. This is a severe regression introduced in version 2.0.0-beta.1 (specifically during the transition of LlmAgent to the new NodeRunner architecture).

  • In version v1.34.0 (and 1.33.0), calling /run with a state_delta for a normal Agent (which is a TypeAlias for LlmAgent) worked perfectly. This is because in older versions, LlmAgent ran through the traditional _run_with_trace execution path, which correctly applies state_delta.
  • In v2.0.0-beta.1, a hardcoded branch was introduced in runners.py (run_async):
    if isinstance(self.agent, LlmAgent):
        # ... forces mode='chat' and routes to NodeRunner ...
        self._run_node_async(...)
    By forcing all LlmAgent instances into the new _run_node_async path (which completely lacks state_delta support), backward compatibility for session initialization was broken.

Additional Context (Code Trace):

I have traced the exact point where the data is dropped in src/google/adk/runners.py:

  1. The API endpoint correctly passes state_delta to runner.run_async(...) (Line 775).
  2. run_async branches based on agent type:
    • Traditional Path: Passes state_delta through _setup_context_for_new_invocation -> _handle_new_message -> _append_new_message_to_session, which successfully appends an Event(actions=EventActions(state_delta=state_delta)).
    • Workflow Path: For LlmAgent and BaseNode, it delegates to self._run_node_async().
  3. The Gap: The signature for _run_node_async (Line 413) does not accept a state_delta parameter. The callers simply omit it.
  4. Furthermore, inside _run_node_async, it calls _append_user_event (Line 577), which currently has no logic to handle state_delta or attach it to the EventActions, unlike its traditional counterpart _append_new_message_to_session.

Proposed Fix:

  1. Add state_delta: Optional[dict[str, Any]] = None to the signature of _run_node_async.
  2. Update _append_user_event to accept state_delta and construct the event with EventActions(state_delta=state_delta) if present (matching the logic in _append_new_message_to_session).
  3. Pass state_delta=state_delta in the call sites to _run_node_async inside run_async.

Minimal Reproduction Code:

1. Agent Code (normal_agent):

from google.adk.agents import Agent
from google.adk.models import LiteLlm
from google.adk.agents.callback_context import CallbackContext

import logging
logger = logging.getLogger(__name__)

def show_state(callback_context: CallbackContext):
    logger.info(f"[DEBUG] current events: {callback_context.session.events}")

# Define the chat agent
root_agent = Agent(
    name="chat_agent",
    model=LiteLlm(model="litellm_proxy/gemini-3-flash-preview"),
    instruction="You are a helpful assistant.",
    before_agent_callback=[show_state],
)

2. Curl Request to /run:

curl --request POST \
  --url http://localhost:8000/run \
  --header 'authorization: Bearer sk-123' \
  --header 'content-type: application/json' \
  --data '{
  "appName": "normal_agent",
  "userId": "user",
  "sessionId": "test_state_delta",
  "newMessage": {
    "parts": [{"text": "test message state delta"}],
    "role": "user"
  },
  "streaming": false,
  "stateDelta": {
    "test_state": "must_change"
  }
}'

3. Logs in v1.33.0 (Working correctly):

2026-05-20 14:27:59.916 | INFO | normal_agent.agent | [DEBUG] current events: [Event(model_version=None, content=Content(
  parts=[
    Part(
      text='test message state delta'
    ),
  ],
  role='string'
), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=None, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=None, live_session_resumption_update=None, live_session_id=None, go_away=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, interaction_id=None, invocation_id='e-ab863237-b161-4f7d-a1ff-559c82c0ec1a', author='user', actions=EventActions(skip_summarization=None, state_delta={'test_state': 'must_change'}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None, render_ui_widgets=None), long_running_tool_ids=None, branch=None, id='69d7e17f-48d6-46c3-9090-1c0cc37b9135', timestamp=1779262079.8746789)]

 

4. Logs in v2.0.0-beta.1 (Regression - state is empty):

2026-05-20 14:40:47.104 | INFO | normal_agent.agent | [DEBUG] current events: [Event(model_version=None, content=Content(
  parts=[
    Part(
      text='test message state delta'
    ),
  ],
  role='string'
), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=None, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=None, live_session_resumption_update=None, live_session_id=None, go_away=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, interaction_id=None, invocation_id='e-60553fe6-5e11-4639-aac2-a4ca41086e05', author='user', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None, route=None, request_task=None, finish_task=None, render_ui_widgets=None, set_model_response=None), output=None, node_info=NodeInfo(path='', output_for=None, message_as_output=None), long_running_tool_ids=None, branch=None, id='ff8512e8-19b6-42e7-a6b4-c36110a3d7ab', timestamp=1779262839.6619968)]

How often has this issue occurred?:

  • Always (100%)

Metadata

Metadata

Assignees

Labels

services[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etcv2Affects only 2.0 versionworkflow[Component] This issue is related to ADKworkflow

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions