Repository navigation
Description
Summary
init --write can select the first literal Google ADK sub-agent name as agent.name even when the module explicitly constructs an App(root_agent=...) with a different coordinator.
This creates a schema-valid but semantically misleading manifest.
Reproduction
Observed against google/adk-samples#1745 with Agents Shipgate 0.16.0b7, contract 19.
The source has two literal sub-agents:
salesforce_agent = LlmAgent(name="SalesforceAgent", ...)
sap_agent = LlmAgent(name="SapAgent", ...)The coordinator is separately bound as the application root:
root_agent = LlmAgent(
name=AGENT_NAME,
sub_agents=[salesforce_agent, sap_agent],
...
)
app = App(name="smart_closer_app", root_agent=root_agent)AGENT_NAME defaults to SmartCloserAgent in the adjacent config module.
Observed init result:
{
"agent_name": "SalesforceAgent",
"agent_name_candidates": [
{"value":"SalesforceAgent","source":"Agent_name_literal"},
{"value":"SapAgent","source":"Agent_name_literal"}
]
}The generated manifest therefore declared the Salesforce worker as the reviewed agent identity.
Expected behavior
Prefer structural root evidence over source-order literal candidates:
- Resolve
App(root_agent=<symbol>). - Follow the assignment for that symbol to the corresponding
Agent/LlmAgentconstruction. - Resolve simple local constants/defaults when safe and static.
- If the root name remains dynamic or ambiguous, emit
CHANGE_MEplus a diagnostic instead of asserting a sub-agent identity.
This is related to candidate-quality issue #320, but it is a distinct hierarchy problem: both literal names are plausible agent names; neither is the application root.
Acceptance criteria
- An ADK
App(root_agent=root_agent)fixture selects the root coordinator, not the first sub-agent encountered. - Root assignment and sub-agent relationships are represented in detection evidence.
- Simple imported/default configuration constants can contribute evidence without importing user code.
- Ambiguous dynamic names fail closed to a placeholder and explicit review action.
-
agent_name_candidatesinclude role/provenance metadata sufficient to explain the ranking.
Activity
pengfei-threemoonslab commentedon Aug 7, 2026
Confirmed at
7e0bfdf5. As traced in #320,_agent_name_candidates(cli/discovery/signals.py:567) appends literals in file-then-AST order with no ranking, andcli/_register_init.py:363-364takes the first with an accepted source — soSalesforceAgentwon by position alone. Nothing in candidate generation consultsApp(root_agent=...)at all; the structural root evidence is not merely out-ranked, it is never collected.Worth noting the detection layer already knows the relevant class surface —
signals.py:101hasGOOGLE_ADK_AGENT_CLASSES = {"Agent", "LlmAgent"}and:315resolves literal constructions — so this is an extraction gap in one function, not a missing understanding of ADK.This is the more severe half of the #320 pair and should lead. #320 produces an obviously wrong name (
t) that a human notices immediately. This produces a plausible wrong name:SalesforceAgentis a real agent in the repository, so a reviewer skimming the manifest has no signal that the declared identity is a worker rather than the coordinator. A wrong-but-plausible identity is the one that survives review.The proposed resolution order is right, and step 3 ("resolve simple local constants/defaults when safe and static") is the one to bound carefully:
AGENT_NAMEliving in an adjacent config module means the safe version is a single-hop, same-package, literal-assignment lookup. Anything more — conditionals, f-strings, imports from outside the package — should fail closed to the placeholder rather than growing into partial evaluation of user code. The static-only boundary is easy to erode one convenience at a time here.The last acceptance criterion —
agent_name_candidatescarrying role and provenance metadata — is the durable one. Without it, whichever ranking lands is untestable from the output and the next regression looks identical to correct behavior.Labeled P1 / v0.17.0.
fix(init): read agent-name bindings the way Python does, or decline
Scope the unresolvable-root rejection to its own project (#398)
Scope the unresolvable-root rejection to its own project (#398) (#532)