-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Bug Report: output_key Does Not Capture Delegated Sub-Agent Responses
Bug Description
When a root agent with output_key configured delegates to a sub-agent, the sub-agent's response is not saved to session state. This contradicts the official documentation which states that output_key should capture "the agent's final textual response for a turn" regardless of which agent generated it (root or delegated sub-agent).
Actual behavior: Only responses directly from the root agent are saved to state[output_key]. Delegated sub-agent responses are silently ignored.
Expected behavior: ALL final responses (including delegated ones) should be saved to state[output_key].
Steps to Reproduce
Installation
pip install google-adk==1.19.0
# OR
uv add google-adk==1.19.0Minimal Reproduction Code
from google.adk.agents import Agent
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
# Create sub-agent
def say_hello():
return "Hello, there!"
greeting_agent = Agent(
name="greeting_agent",
model="gemini-2.0-flash",
tools=[say_hello]
)
# Create root agent with output_key
root_agent = Agent(
name="root_agent",
model="gemini-2.0-flash",
sub_agents=[greeting_agent],
output_key="last_response" # Should capture ALL responses
)
# Setup session and runner
session_service = InMemorySessionService()
runner = Runner(agent=root_agent, app_name="test", session_service=session_service)
# Turn 1: Direct response from root agent
await runner.run_async("user_1", "session_1", new_message="test direct")
# ✅ Saved to state["last_response"]
# Turn 2: Delegated response from greeting_agent
await runner.run_async("user_1", "session_1", new_message="hello")
# ❌ NOT saved to state["last_response"] - BUG!
# Check final state
session = await session_service.get_session("test", "user_1", "session_1")
print(session.state["last_response"]) # Still shows Turn 1 response, not Turn 2Execution Steps
- Save the code above as
test_output_key.py - Set
GOOGLE_API_KEYenvironment variable - Run:
python test_output_key.py - Observe that
state["last_response"]still contains Turn 1's response - Expected: Should contain Turn 2's delegated greeting response
Observed Output
After running the minimal reproduction code:
# Turn 1 output
state["last_response"] = "This is a direct response from the root agent" # ✅ Saved
# Turn 2 output
state["last_response"] = "This is a direct response from the root agent" # ❌ NOT updated!
# Expected: state["last_response"] = "Hello, there!" (from greeting_agent)Issue: When the root agent delegates to greeting_agent in Turn 2, the delegated response is not saved to state["last_response"]. The state retains the old value from Turn 1 instead of being updated with the greeting.
Expected Behavior
According to the official ADK tutorial:
The
output_keyconfiguration causes the agent to "automatically save the agent's final textual response for a turn" regardless of which agent generated it—root or delegated sub-agent.
Expected state after Turn 2:
state["last_response"] = "Hello, there!" # Should be updated with greeting_agent's responseThe output_key should capture delegated sub-agent responses, not just root agent responses.
Screenshots
N/A - This is a state management issue without visual components.
System Information
- Operating System: Linux (tested on Ubuntu/Debian)
- Python Version:
$ python --version Python 3.12
- ADK Version:
$ pip show google-adk Version: 1.19.0
Model Configuration
- Using LiteLLM: Yes (for OpenAI models in multi-agent setup)
- Models Used:
- Root agent:
openai/gpt-4.1(via LiteLLM) - Sub-agents:
gemini-2.0-flash(native Gemini)
- Root agent:
Additional Context
Root Cause
The bug is in google/adk/agents/llm_agent.py:784-816 in the __maybe_save_output_to_state() method:
def __maybe_save_output_to_state(self, event: Event):
"""Saves the model output to state if needed."""
# skip if the event was authored by some other agent (e.g. current agent
# transferred to another agent)
if event.author != self.name: # ← BUG: Skips delegated responses
logger.debug(
'Skipping output save for agent %s: event authored by %s',
self.name,
event.author,
)
return # ← Exits early, never saves delegated responses!
# Only executes if event.author == self.name
if (
self.output_key
and event.is_final_response()
and event.content
and event.content.parts
):
result = ''.join(
part.text
for part in event.content.parts
if part.text and not part.thought
)
event.actions.state_delta[self.output_key] = resultWhy it fails: When the root agent delegates to a sub-agent, event.author contains the sub-agent's name, not the root agent's name. The check event.author != self.name evaluates to True, causing the method to return early without saving to state.
Impact
- Severity: Medium-High
- Affects: All multi-agent systems using
output_keywith agent delegation - Workaround: Manually save responses to state within tools using
tool_context.state[key] = valueinstead of relying onoutput_key
Implementation vs Documentation Discrepancy
The code comment in the source explicitly mentions this behavior: "skip if the event was authored by some other agent (e.g. current agent transferred to another agent)" - but this directly contradicts the documentation's promise that output_key captures responses "regardless of which agent generated it."
The code comment suggests this is intentional behavior, but the official tutorial explicitly demonstrates a test case where delegated responses should be saved to state via output_key. This creates confusion and breaks documented functionality.
Related Resources
- Official Tutorial: Agent Team - Step 4: State Management
- Source Code:
google/adk/agents/llm_agent.py:784-816(ADK v1.19.0)