Skip to content

Commit 83623d2

Browse files
committed
fix: patch stale rootAgent references in ADK agent tree
ADK's BaseAgent computes rootAgent once at construction. Module-level singleton sub-agents get rootAgent=self since no parent exists yet. When the planning agent later sets parentAgent via setParentAgentForSubAgents(), rootAgent is never recomputed — so all sub-agent transfers fail with "Agent X not found in the agent tree." even for the root agent itself. Add fixAgentTreeRoots() to recursively patch rootAgent on all descendants after the planning agent hierarchy is constructed.
1 parent a8701ee commit 83623d2

File tree

3 files changed

+37
-14
lines changed

3 files changed

+37
-14
lines changed

claude.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,8 @@ Before pushing any code changes, you **must** complete the following verificatio
1010

1111
After making any code changes, always start by running the code simplifier plugin to ensure the code is clean and follows best practices.
1212

13-
The code-simplifier is an Anthropic-published plugin for Claude Code. Run it via:
13+
The code-simplifier is an Anthropic-published agent plugin for Claude Code. Run it via: the code-simplifier:codesimplifier agent
1414

15-
```bash
16-
/simplify
17-
```
18-
19-
Or use the Claude Code CLI with the code-simplifier plugin enabled.
2015

2116
### 2. Run Unit Tests
2217

src/agents/planning.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@ function getDynamicInstruction(context: InstructionContext): string {
131131
return baseInstruction.replace('{plan_state}', currentPlan);
132132
}
133133

134+
/**
135+
* Fix stale rootAgent references in the agent tree.
136+
*
137+
* ADK's BaseAgent computes `rootAgent` once at construction time by traversing
138+
* `parentAgent` upward. But when agents are created as module-level singletons
139+
* (before any parent exists), their `rootAgent` points to themselves. When the
140+
* parent later calls `setParentAgentForSubAgents()`, it sets `parentAgent` but
141+
* never recomputes `rootAgent`. This causes `getAgentByName()` to fail when
142+
* sub-agents attempt transfers — they only search their own empty subtree.
143+
*/
144+
function fixAgentTreeRoots(agent: LlmAgent, root: LlmAgent): void {
145+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- rootAgent is not exposed in ADK's public types
146+
(agent as any).rootAgent = root;
147+
for (const sub of agent.subAgents) {
148+
fixAgentTreeRoots(sub as LlmAgent, root);
149+
}
150+
}
151+
134152
/**
135153
* Creates a planning agent with dynamic MCP tools
136154
* Use this when you need MCP tools to be fully initialized
@@ -163,12 +181,21 @@ Transfer back to planning_agent immediately.`,
163181
...additionalSubAgents,
164182
];
165183

166-
return new LlmAgent({
184+
const planningAgent = new LlmAgent({
167185
name: 'planning_agent',
168186
model: modelName,
169187
description: 'Orchestrates multi-step tasks across specialist agents.',
170188
instruction: getDynamicInstruction,
171189
afterModelCallback: saveMemoriesOnFinalResponse,
172190
subAgents,
173191
});
192+
193+
// Fix stale rootAgent references (ADK bug workaround)
194+
fixAgentTreeRoots(planningAgent, planningAgent);
195+
196+
agentLogger.debug(
197+
'[Planning] Agent tree rootAgent fix applied — all sub-agents now reference planning_agent as root'
198+
);
199+
200+
return planningAgent;
174201
}

src/agents/runner.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ function serializeError(error: unknown): string {
4848
}
4949

5050
/**
51-
* Debug: Log the agent hierarchy
51+
* Debug: Log the agent hierarchy with model and rootAgent info
5252
*/
53-
export function logAgentHierarchy(agent: LlmAgent, indent = 0) {
53+
export function logAgentHierarchy(agent: LlmAgent, indent = 0): void {
5454
const prefix = ' '.repeat(indent);
55-
agentLogger.debug(`${prefix}[Agent] ${agent.name} (model: ${agent.model})`);
56-
if (agent.subAgents && agent.subAgents.length > 0) {
57-
for (const subAgent of agent.subAgents) {
58-
logAgentHierarchy(subAgent as LlmAgent, indent + 1);
59-
}
55+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- rootAgent is not exposed in ADK's public types
56+
const rootName = (agent as any).rootAgent?.name ?? 'unknown';
57+
agentLogger.debug(`${prefix}[Agent] ${agent.name} (model: ${agent.model}, root: ${rootName})`);
58+
for (const subAgent of agent.subAgents) {
59+
logAgentHierarchy(subAgent as LlmAgent, indent + 1);
6060
}
6161
}
6262

@@ -66,6 +66,7 @@ export function logAgentHierarchy(agent: LlmAgent, indent = 0) {
6666
*/
6767
export async function createRunner(): Promise<InMemoryRunner> {
6868
const initializedRootAgent = await createPlanningAgent();
69+
logAgentHierarchy(initializedRootAgent);
6970
return new InMemoryRunner({
7071
agent: initializedRootAgent,
7172
appName: APP_NAME,

0 commit comments

Comments
 (0)