Skip to content

Web UI Agent Discovery Returns Non-Agent Directories #5299

@xyl-muse

Description

@xyl-muse

🔴 Web UI Agent Discovery Returns Non-Agent Directories

Describe the Bug:

The /list-apps endpoint returns all subdirectories in the agents directory, regardless of whether they contain valid agent definitions. This causes the ADK Web UI to display non-agent directories (like tools/, schemas/, tests/) in the agent selector dropdown, resulting in a poor user experience.

Steps to Reproduce:

  1. Create a project directory with the following structure:
my_project/
├── my_agent/
│   ├── __init__.py
│   └── agent.py       # valid agent with root_agent
├── utils/              # not an agent
├── data/               # not an agent
└── tmp/                # not an agent
  1. Run adk web . from my_project/
  2. Open the Web UI at http://127.0.0.1:8000/
  3. Observe the agent selector dropdown shows: utils, data, tmp, and my_agent
  4. Select utils or other non-agent directory → Loading error occurs

Expected Behavior:

Only directories containing a valid agent definition (root_agent.yaml, agent.py with root_agent, or __init__.py with root_agent) should appear in the agent list.

Observed Behavior:

All non-hidden subdirectories are listed regardless of whether they contain an agent. Selecting a non-agent directory results in a loading error:

ValueError: No root_agent found for 'utils'. Searched in 'utils.agent.root_agent', 'utils.root_agent' and 'utils\root_agent.yaml'.

Environment Details:

  • ADK Library Version: 1.29.0
  • Desktop OS: Windows 10 (also affects macOS/Linux)
  • Python Version: 3.10

Model Information:

  • Are you using LiteLLM: N/A
  • Which model is being used: N/A (issue is in agent discovery, not model interaction)

🟡 Optional Information

Regression:

No, this has been the behavior since list_agents() was introduced. PR #3430 added a list_agents_detailed() endpoint that filters properly, but the Web UI frontend never adopted it and still calls the plain /list-apps endpoint.

Logs:

N/A - no error on the backend, the non-agent directories are simply returned in the API response.

Screenshots / Video:

The Web UI dropdown shows non-agent directories alongside valid agents, causing confusion for users.

Additional Context:

Root Cause:
The issue is in AgentLoader.list_agents() which only filters by os.path.isdir(), hidden directory prefix, and __pycache__. It does not verify the directory contains a recognized agent structure.

Current Implementation (v1.29.0):

# google/adk/cli/utils/agent_loader.py
def list_agents(self) -> list[str]:
    base_path = Path.cwd() / self.agents_dir
    agent_names = [
        x
        for x in os.listdir(base_path)
        if os.path.isdir(os.path.join(base_path, x))
        and not x.startswith(".")
        and x != "__pycache__"
    ]
    return agent_names  # Returns ALL directories

# google/adk/cli/adk_web_server.py
@app.get("/list-apps")
async def list_apps(
    detailed: bool = Query(
        default=False,  # ⚠️ Default is False
        description="Return detailed app information"
    )
):
  if detailed:
    return self.agent_loader.list_agents_detailed()  # ✅ Would filter
  return self.agent_loader.list_agents()  # ❌ Returns all directories

The Problem:

  • Backend provides detailed parameter to filter invalid agents
  • Default value is False
  • Web UI frontend (pre-compiled Angular app) calls /list-apps without passing ?detailed=true

Proposed Solutions:

  1. Change backend default to True (Recommended):

    • Fixes issue for all users immediately
    • No frontend changes required
    • Backward compatible
  2. Update frontend to pass detailed=true:

    • Requires frontend rebuild
    • More explicit control

Impact on Multi-Agent Systems:

Modern multi-agent architectures typically have:

  • One orchestrator agent (root agent)
  • Multiple sub-agents (implemented as modules/classes)
  • Utility directories (tools, schemas, tests, etc.)

The current behavior creates friction for this common architecture pattern.

Minimal Reproduction Code:

from pathlib import Path
from google.adk.cli.utils.agent_loader import AgentLoader
import tempfile

with tempfile.TemporaryDirectory() as tmp:
    # Create valid agent
    (Path(tmp) / "real_agent").mkdir()
    (Path(tmp) / "real_agent" / "__init__.py").write_text(
        "from google.adk.agents.base_agent import BaseAgent\n"
        "class A(BaseAgent):\n"
        "  def __init__(self): super().__init__(name='a')\n"
        "root_agent = A()"
    )
    
    # Create non-agent directory
    (Path(tmp) / "not_an_agent").mkdir()
    
    loader = AgentLoader(tmp)
    result = loader.list_agents()
    
    print(result)
    # Returns: ['not_an_agent', 'real_agent']
    # Expected: ['real_agent']

How often has this issue occurred?:

  • Always (100%)

Metadata

Metadata

Assignees

No one assigned

    Labels

    web[Component] This issue will be transferred to adk-web

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions