google / google/adk-python

A2A sub-agent completion ends parent agent flow without returning to LLM

Abierto
#5,977 3 comentarios 0 reacciones 2 asignados Reclamado por @GWeale Ver en GitHub
core needs review
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 14 h
PR fusionados (30 d)
37

Descripción

## 🔴 Required Information

**Describe the Bug:**
When using `RemoteA2aAgent` registered as a `sub_agent` on an `LlmAgent`, the parent agent's flow terminates immediately after the A2A sub-agent completes, without returning control to the parent LLM for summarization or further processing. According to the ADK A2A documentation (adk.dev/a2a/intro/), interacting with a remote agent should feel like "interacting with a local tool or function" where "the parent agent maintains full control" and "responses are returned as standard tool/function call return values." However, the actual behavior is that the LLM wrapper's chat-mode loop detects `event.actions.transfer_to_agent` and sets `transferred = True`, causing the outer loop to `return` immediately instead of re-entering the LLM with the sub-agent's result.

**Steps to Reproduce:**
1. Create an `LlmAgent` with a `RemoteA2aAgent` registered via `sub_agents=[remote_agent]`
2. Ensure the parent LLM's system prompt instructs it to use `transfer_to_agent` when it needs to delegate to the sub-agent
3. Run the parent agent with a query that triggers the `transfer_to_agent` call
4. Observe that after the A2A sub-agent completes and returns its response, the entire flow ends — the parent LLM never gets another turn to process or summarize the sub-agent's output

**Expected Behavior:**
After the A2A sub-agent completes, control should return to the parent LLM with the sub-agent's result as a `FunctionResponse` (similar to how task delegation via `AgentTool` works). The parent LLM should then see the result and be able to summarize, analyze, or continue the conversation based on the sub-agent's output. This matches the ADK A2A documentation: "Parent LLM sees results as regular Tool Output."

**Observed Behavior:**
The flow terminates immediately after the A2A sub-agent completes. The `_llm_agent_wrapper.py` chat-mode loop (`run_llm_agent_as_node`) detects `event.actions.transfer_to_agent` and:
1. Sets `transferred = True`
2. Breaks the inner loop
3. Outer loop checks `if not had_task_fc or transferred: return`
4. Returns immediately — the parent LLM never re-enters to process the result

**Environment Details:**

- ADK Library Version: 2.0 (latest as of 2026-06-05)
- Desktop OS: Windows
- Python Version: 3.13

**Model Information:**

- Are you using LiteLLM: Yes
- Which model is being used: qwen 3.6 plus (via Model Gateway)

---

## 🟡 Optional Information

**Regression:**
N/A — This appears to be the intended behavior of `transfer_to_agent` (scheduler-level routing), but it conflicts with the A2A documentation which describes sub-agents as tool-like call-and-return interactions. This is the first version of ADK with A2A support that we have tested.

**Logs:**
```text
# Source code analysis from _llm_agent_wrapper.py (lines ~415-445):

while True:
had_task_fc = False
transferred = False
run_method = agent.run_live(ic) if is_live else agent.run_async(ic)
async with aclosing(run_method) as run_iter:
async for event in run_iter:
yield event
task_fcs = _extract_task_delegation_fcs(event, tools_dict)
for fc in task_fcs:
output = await _dispatch_task_fc(agent, fc, ctx)
yield _synthesize_task_fr_event(fc, output)
if task_fcs:
had_task_fc = True
break
if event.actions.transfer_to_agent:
target_name = event.actions.transfer_to_agent
if target_name != agent.name:
transferred = True
break
if not had_task_fc or transferred:
# LLM finished without delegating (or transferred away);
# nothing more for this wrapper to do.
return
# Otherwise: loop back to re-enter agent.run_async so the LLM
# sees the synthesized FR(s) and can emit follow-up actions.
```

**Screenshots / Video:**
N/A — This is a control flow issue, not a visual/rendering bug.

**Additional Context:**
The root cause is a semantic mismatch between `transfer_to_agent` and the documented A2A call-and-return model:

- `transfer_to_agent` is designed as a **scheduler routing signal** (handoff control to another agent permanently)
- A2A sub-agents are documented as **call-and-return** (like a tool call, parent resumes after result)

The `_dynamic_node_scheduler.py` `while True` loop correctly handles `transfer_to_agent` by routing to the target agent and continuing. However, `_llm_agent_wrapper.py`'s outer loop interprets `transferred = True` as "flow is done, return to caller." This works for true handoff scenarios but breaks the A2A use case where the parent should see the result and continue.

For contrast, `AgentTool` (wrapping a sub-agent as a function tool) correctly returns a `FunctionResponse` to the LLM, which then autonomously decides next steps — this is the behavior described in the A2A docs.

**Minimal Reproduction Code:**
```python
from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService

# Create a remote A2A agent
remote_agent = RemoteA2aAgent(
name="sub_agent",
agent_card="http://localhost:8080/.well-known/agent-card.json",
description="A test sub-agent",
)

# Create parent LlmAgent with sub_agent
parent_agent = LlmAgent(
name="parent_agent",
model=model,
instruction="You are a helpful assistant. When you need help, transfer to the sub_agent.",
tools=[],
sub_agents=[remote_agent],
)

runner = Runner(
app_name="test",
agent=parent_agent,
session_service=InMemorySessionService(),
)

# Run — after sub_agent completes, flow ends immediately
async for event in runner.run_async(
user_id="test",
session_id="test-session",
new_message=new_message,
):
print(event)
if event.is_final_response():
# This fires after sub_agent completes,
# but parent LLM never got to see the result
break
```

**How often has this issue occurred?:**

- Always (100%)

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.