🔴 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) returns the first descendant whose name matches. Two callers make that visible:
llm_agent.py:1001 — 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 — 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:
pip install google-adk==2.9.0
- Save the script under Minimal Reproduction Code as
repro.py
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:
"""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.
🔴 Required Information
Describe the Bug:
validate_sub_agents_unique_nameschecks onesub_agentslist 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) returns the first descendant whose name matches. Two callers make that visible:llm_agent.py:1001—transfer_to_agentresolves the target withroot_agent.find_agent(agent_name). The second agent can never be transferred to._agent_router.py:133— a resumed invocation picks the agent to continue withroot_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 verifiedfind_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_nameslogs "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_historysays 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 tofind_agent.Steps to Reproduce:
pip install google-adk==2.9.0repro.pypython repro.pyExpected 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:
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:
google-adk 2.9.0(same code onmain)Model Information:
🟡 Optional Information
Additional Context — scope, stated restrictively:
find_agentreturns the first match so the second agent is unreachable by name.transfer_to_agentround trip with a model. Those follow from the two call sites above, which I read rather than ran.Minimal Reproduction Code:
How often has this issue occurred?: