google / google/adk-python

Agents under different parents may share a name: no warning, and find_agent() silently resolves to the first

Abierto
#7,110 3 comentarios 0 reacciones 1 asignado Reclamado por @surajksharma07 Ver en GitHub
agent engine
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 22 h
PR fusionados (30 d)
31

Descripción

## 🔴 Required Information

**Describe the Bug:**

`validate_sub_agents_unique_names` checks one `sub_agents` list at a time, so two agents that share a name under **different parents** are accepted with no warning. Every by-name resolution then silently picks the first match in a depth-first walk, and the other agent cannot be reached by name at all.

`find_agent` ([`base_agent.py:512`](https://github.com/google/adk-python/blob/main/src/google/adk/agents/base_agent.py#L512)) returns the first descendant whose name matches. Two callers make that visible:

- [`llm_agent.py:1001`](https://github.com/google/adk-python/blob/main/src/google/adk/agents/llm_agent.py#L1001) — `transfer_to_agent` resolves the target with `root_agent.find_agent(agent_name)`. The second agent can never be transferred to.
- [`_agent_router.py:133`](https://github.com/google/adk-python/blob/main/src/google/adk/agents/_agent_router.py#L133) — a resumed invocation picks the agent to continue with `root_agent.find_agent(event.author)`. When two agents share a name, the one that resumes is whichever the walk reaches first, which is not necessarily the one that authored the event. I have verified `find_agent`'s behaviour by execution; this second consequence follows from reading the call, and I have not reproduced a full resume.

The sibling case is already treated as a problem — `validate_sub_agents_unique_names` logs *"Found duplicate sub-agent names ... All sub-agents must have unique names."* The check just does not extend past one level.

The node layer already solves the same ambiguity: the docstring of `restore_branch_from_history` says nodes are *"matched by their static path (run ids stripped) so that two nodes sharing a name (e.g. the same sub-agent mounted under two parents) are disambiguated"*. That disambiguation is not available to `find_agent`.

**Steps to Reproduce:**

1. `pip install google-adk==2.9.0`
2. Save the script under *Minimal Reproduction Code* as `repro.py`
3. `python repro.py`

**Expected Behavior:**

Either the uniqueness check covers the whole tree rather than one list of siblings, or by-name resolution is path-aware the way node matching already is.

**Observed Behavior:**

```
A. two agents named "alpha", under DIFFERENT parents
warning at construction? NO
find_agent("alpha") -> the one under branch1
the one under branch2 is reachable by name? False <- unreachable

B. NEGATIVE CONTROL — two agents named "alpha" as SIBLINGS
warning at construction? YES

C. NEGATIVE CONTROL — unique names
find_agent("beta") -> under branch2 (correct)
```

B is the control that matters: the existing validator does fire, so the case is already recognised — it is the scope of the check that differs, not the intent.

**Environment Details:**

- ADK Library Version: `google-adk 2.9.0` (same code on `main`)
- Desktop OS: macOS 26.6.2 (arm64)
- Python Version: 3.12.13

**Model Information:**

- Are you using LiteLLM: No
- Which model is being used: N/A — no model is involved

---

## 🟡 Optional Information

**Additional Context — scope, stated restrictively:**

- Verified by execution: construction raises no warning for the cousin case, and `find_agent` returns the first match so the second agent is unreachable by name.
- **Not** verified by execution: the effect on a real resumed invocation, or on a live `transfer_to_agent` round trip with a model. Those follow from the two call sites above, which I read rather than ran.
- Whether this is worth fixing depends on how likely you consider a tree with repeated names. I am not claiming it is common — only that nothing reports it, and that the sibling check shows the intent is for names to be unique.
- I have deliberately not proposed a patch: widening the check to the whole tree would reject trees that work today, which is a compatibility decision that is yours.

**Minimal Reproduction Code:**

```python
"""Two agents can share a name when they sit under different parents.
Nothing warns, and every by-name resolution silently picks the first."""
import logging, io
from google.adk.agents.base_agent import BaseAgent

log = io.StringIO()
logging.getLogger('google_adk').addHandler(logging.StreamHandler(log))
logging.getLogger('google_adk').setLevel(logging.WARNING)

def build(label, tree):
log.truncate(0); log.seek(0)
root = tree()
warned = 'duplicate' in log.getvalue().lower()
print(f'{label}\n warning at construction? {"YES" if warned else "NO"}')
return root

root_a = build('A. two agents named "alpha", under DIFFERENT parents',
lambda: BaseAgent(name='root', sub_agents=[
BaseAgent(name='branch1', sub_agents=[BaseAgent(name='alpha')]),
BaseAgent(name='branch2', sub_agents=[BaseAgent(name='alpha')]),
]))
first = root_a.find_agent('alpha')
under_b2 = root_a.find_agent('branch2').find_agent('alpha')
print(f' find_agent("alpha") -> the one under {first.parent_agent.name}')
print(f' the one under branch2 is reachable by name? '
f'{first is under_b2} <- unreachable\n')

root_b = build('B. NEGATIVE CONTROL — two agents named "alpha" as SIBLINGS',
lambda: BaseAgent(name='root', sub_agents=[
BaseAgent(name='alpha'), BaseAgent(name='alpha')]))
print()

root_c = build('C. NEGATIVE CONTROL — unique names',
lambda: BaseAgent(name='root', sub_agents=[
BaseAgent(name='branch1', sub_agents=[BaseAgent(name='alpha')]),
BaseAgent(name='branch2', sub_agents=[BaseAgent(name='beta')]),
]))
print(f' find_agent("beta") -> under {root_c.find_agent("beta").parent_agent.name} (correct)')
```

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

- Always (100%) — deterministic; no model, no network, no timing involved.

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.