🔴 Required Information
Describe the Bug:
HITL (Human-in-the-Loop) tool confirmation fails with VerifyException: Tool not found when a sub-agent that uses beforeToolCallbackSync with requestConfirmation() is wrapped inside a SequentialAgent (or any non-LlmAgent workflow agent).
After the user approves the tool execution, RequestConfirmationLlmRequestProcessor attempts to re-execute the confirmed tool. It resolves tools from invocationContext.agent(), but at that point the agent in context is the root LlmAgent, not the sub-agent that originally requested the confirmation. The root agent does not have the sub-agent's tools registered, causing the VerifyException.
The root cause is in Runner.findAgentToRun(): after HITL approval, it walks session events backwards to find the agent to resume. It calls isTransferableAcrossAgentTree(), which requires every agent in the parent chain to be an instance of LlmAgent. A SequentialAgent in the chain breaks this requirement, so the runner falls back to the root agent — which doesn't know the sub-agent's tools.
HITL Confirmation Flow Context:
This bug affects the HITL (Human-in-the-Loop) tool confirmation flow where an agent's beforeToolCallbackSync calls toolContext.requestConfirmation() to pause tool execution and ask the user for approval before proceeding.
Steps to Reproduce:
- Create a root
LlmAgent (e.g. rootAgent) with a SequentialAgent as a sub-agent.
- The
SequentialAgent wraps an LlmAgent sub-agent (e.g. childAgent) that has a tool (e.g. myTool) registered via FunctionTool.create(...).
- Set
beforeToolCallbackSync on childAgent that calls toolContext.requestConfirmation(...) to request user approval before executing myTool.
- Send a user message that triggers
rootAgent to delegate to the SequentialAgent, which delegates to childAgent, which calls myTool.
childAgent's beforeToolCallbackSync fires and requests confirmation — the agent pauses.
- Resume the session with a user message containing a
FunctionResponse for adk_request_confirmation with confirmed: true.
- Observe the crash:
com.google.common.base.VerifyException: Tool not found: myTool
at com.google.adk.flows.llmflows.Functions.handleFunctionCalls(Functions.java:146)
at com.google.adk.flows.llmflows.RequestConfirmationLlmRequestProcessor.lambda$assembleEvent$11(RequestConfirmationLlmRequestProcessor.java:225)
Expected Behavior:
After the user confirms the tool execution, RequestConfirmationLlmRequestProcessor should resolve the tool from the correct sub-agent (the one that originally requested the confirmation), not from the root agent. The tool should execute successfully.
Observed Behavior:
Runner.findAgentToRun() cannot transfer back to the LlmAgent sub-agent because isTransferableAcrossAgentTree() fails when a SequentialAgent (which is not an LlmAgent) is in the parent chain. The runner falls back to the root LlmAgent, which does not have the sub-agent's tools. Functions.handleFunctionCalls() throws VerifyException: Tool not found.
Environment Details:
- ADK Library Version: 1.1.0
- OS: macOS / Linux
- Java Version: 21
Model Information:
- Which model is being used: gemini-2.5-flash (not model-specific — this is a framework-level issue)
🟡 Optional Information
Additional Context:
The issue is in Runner.isTransferableAcrossAgentTree() (Runner.java:752-767):
private boolean isTransferableAcrossAgentTree(BaseAgent agentToRun) {
BaseAgent current = agentToRun;
while (current != null) {
if (!(current instanceof LlmAgent)) { // ← SequentialAgent fails here
return false;
}
LlmAgent agent = (LlmAgent) current;
if (agent.disallowTransferToParent()) {
return false;
}
current = current.parentAgent();
}
return true;
}
This method rejects any agent whose parent chain includes a non-LlmAgent (like SequentialAgent or ParallelAgent). This means HITL confirmation cannot work for any tool inside an agent that is wrapped in a workflow agent.
A possible fix would be to either:
- Skip non-
LlmAgent agents in the isTransferableAcrossAgentTree check (treating them as transparent wrappers), or
- Have
RequestConfirmationLlmRequestProcessor.assembleEvent() resolve tools by searching the full agent tree (including sub-agents) rather than only looking at invocationContext.agent().
Note: This is a separate issue from #688, which describes an ID mismatch bug in the same RequestConfirmationLlmRequestProcessor class.
Workaround:
Remove the SequentialAgent wrapper and register the sub-agents directly as sub-agents of the root LlmAgent. This allows findAgentToRun() to correctly identify and transfer to the sub-agent after HITL approval.
Minimal Reproduction Code:
// Sub-agent with a tool that requires HITL confirmation
LlmAgent childAgent = LlmAgent.builder()
.name("child_agent")
.model(model)
.instruction("Use myTool when asked.")
.tools(FunctionTool.create(myToolInstance, "myTool"))
.beforeToolCallbackSync((ctx, tool, input, toolCtx) -> {
if (toolCtx.toolConfirmation().isPresent()) {
return toolCtx.toolConfirmation().get().confirmed()
? Optional.empty()
: Optional.of(Map.of("status", "cancelled"));
}
toolCtx.requestConfirmation("Please confirm execution", Map.of());
return Optional.of(Map.of("status", "awaiting_approval"));
})
.build();
// Wrapping in a SequentialAgent causes HITL to break
SequentialAgent workflow = SequentialAgent.builder()
.name("workflow")
.subAgents(childAgent)
.build();
LlmAgent rootAgent = LlmAgent.builder()
.name("root")
.model(model)
.instruction("Delegate to workflow when needed.")
.subAgents(workflow)
.build();
Runner runner = Runner.builder()
.agent(rootAgent)
.appName("test-app")
.sessionService(new InMemorySessionService())
.build();
// Step 1: Send message that triggers the tool → gets confirmation request
// Step 2: Resume with adk_request_confirmation approved → VerifyException: Tool not found: myTool
How often has this issue occurred?:
🔴 Required Information
Describe the Bug:
HITL (Human-in-the-Loop) tool confirmation fails with
VerifyException: Tool not foundwhen a sub-agent that usesbeforeToolCallbackSyncwithrequestConfirmation()is wrapped inside aSequentialAgent(or any non-LlmAgentworkflow agent).After the user approves the tool execution,
RequestConfirmationLlmRequestProcessorattempts to re-execute the confirmed tool. It resolves tools frominvocationContext.agent(), but at that point the agent in context is the rootLlmAgent, not the sub-agent that originally requested the confirmation. The root agent does not have the sub-agent's tools registered, causing theVerifyException.The root cause is in
Runner.findAgentToRun(): after HITL approval, it walks session events backwards to find the agent to resume. It callsisTransferableAcrossAgentTree(), which requires every agent in the parent chain to be an instance ofLlmAgent. ASequentialAgentin the chain breaks this requirement, so the runner falls back to the root agent — which doesn't know the sub-agent's tools.HITL Confirmation Flow Context:
This bug affects the HITL (Human-in-the-Loop) tool confirmation flow where an agent's
beforeToolCallbackSynccallstoolContext.requestConfirmation()to pause tool execution and ask the user for approval before proceeding.Steps to Reproduce:
LlmAgent(e.g.rootAgent) with aSequentialAgentas a sub-agent.SequentialAgentwraps anLlmAgentsub-agent (e.g.childAgent) that has a tool (e.g.myTool) registered viaFunctionTool.create(...).beforeToolCallbackSynconchildAgentthat callstoolContext.requestConfirmation(...)to request user approval before executingmyTool.rootAgentto delegate to theSequentialAgent, which delegates tochildAgent, which callsmyTool.childAgent'sbeforeToolCallbackSyncfires and requests confirmation — the agent pauses.FunctionResponseforadk_request_confirmationwithconfirmed: true.Expected Behavior:
After the user confirms the tool execution,
RequestConfirmationLlmRequestProcessorshould resolve the tool from the correct sub-agent (the one that originally requested the confirmation), not from the root agent. The tool should execute successfully.Observed Behavior:
Runner.findAgentToRun()cannot transfer back to theLlmAgentsub-agent becauseisTransferableAcrossAgentTree()fails when aSequentialAgent(which is not anLlmAgent) is in the parent chain. The runner falls back to the rootLlmAgent, which does not have the sub-agent's tools.Functions.handleFunctionCalls()throwsVerifyException: Tool not found.Environment Details:
Model Information:
🟡 Optional Information
Additional Context:
The issue is in
Runner.isTransferableAcrossAgentTree()(Runner.java:752-767):This method rejects any agent whose parent chain includes a non-
LlmAgent(likeSequentialAgentorParallelAgent). This means HITL confirmation cannot work for any tool inside an agent that is wrapped in a workflow agent.A possible fix would be to either:
LlmAgentagents in theisTransferableAcrossAgentTreecheck (treating them as transparent wrappers), orRequestConfirmationLlmRequestProcessor.assembleEvent()resolve tools by searching the full agent tree (including sub-agents) rather than only looking atinvocationContext.agent().Note: This is a separate issue from #688, which describes an ID mismatch bug in the same
RequestConfirmationLlmRequestProcessorclass.Workaround:
Remove the
SequentialAgentwrapper and register the sub-agents directly as sub-agents of the rootLlmAgent. This allowsfindAgentToRun()to correctly identify and transfer to the sub-agent after HITL approval.Minimal Reproduction Code:
How often has this issue occurred?: