Skip to content

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

Description

@vasilisnasopoulos

🔴 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:1001transfer_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:

  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:

"""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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agent engine[Component] This issue is related to Vertex AI Agent Engine

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions