A2A sub-agent completion ends parent agent flow without returning to LLM
- Lingua principale
- Python
- Stelle
- 21.5k
- Fork
- 4k
- Merge medio
- 1g 14h
- PR unite (30g)
- 37
Descrizione
## 🔴 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%)
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.