-
Couldn't load subscription status.
- Fork 2.1k
Description
Description
When a Custom agent overrides the _run_async_impl method, it does not correctly pause its execution when a sub-agent enters a PAUSED state due to tool_context.request_confirmation(). The custom agent is built as described in: https://google.github.io/adk-docs/agents/custom-agents/#design-pattern-example-storyflowagent
The parent agent's async for event in sub_agent.run_async(ctx): loop completes as soon as the first confirmation is requested. The parent agent's logic then immediately continues to the next step, instead of waiting for the user to provide the confirmation and for the sub-agent to resume and complete its task.
In the provided example, DeepAnalyticsAgent (parent custom agent) invokes task_brief_agent (sub-agent). This sub-agent correctly requests user confirmation and pauses. However, DeepAnalyticsAgent does not wait; it proceeds instantly to invoke task_supervisor_agent, breaking the intended sequential workflow.
My Custom Agent has 2 sub-agents
- Sub A and Sub B.
- Sub A uses a Advanced Tool confirmation as described in: https://google.github.io/adk-docs/tools/confirmation/#advanced-confirmation
- Once Sub A makes the user request then Sub B starts immediately before the user replies in the payload of Sub A.
- Parent agent does not resume and kicks off Sub B once the payload is submitted.
To Reproduce
-
Define a tool (_confirm_research_brief) that uses tool_context.request_confirmation().
-
Assign this tool to a sub-agent (task_brief_agent).
-
Define a custom parent agent (DeepAnalyticsAgent) that overrides _run_async_impl.
-
Inside _run_async_impl, sequentially invoke task_brief_agent and then another agent (task_supervisor_agent) using run_async.
-
Execute the DeepAnalyticsAgent.
Observe that task_supervisor_agent begins execution as soon as the confirmation UI for task_brief_agent is displayed, without waiting for user input.
def _confirm_research_brief(research_brief: str, tool_context: ToolContext) -> str:
"""Function tool to request user confirmation of the research brief."""
tool_confirmation = tool_context.tool_confirmation
if not tool_confirmation:
tool_context.request_confirmation(
hint=(
'Please review the research brief and confirm if it is satisfactory.'
),
payload={
'approved': 'no',
'edit_brief': ''
},
)
return {'status': 'User Approval Requested'}
approved = tool_confirmation.payload.get('approved', 'no').lower()
if approved == 'yes':
# SUCCESS: Set the final state with the original brief.
tool_context.state["final_task_brief"] = research_brief
return {"status": "The user approved the brief."}
elif approved == 'edit':
edited_brief = tool_confirmation.payload.get('edit_brief')
if edited_brief:
# SUCCESS: Set the final state with the EDITED brief.
tool_context.state["final_task_brief"] = edited_brief
return {"status": "The user edited and approved the brief."}
else:
# Handle case where user chose 'edit' but provided no text.
tool_context.state["final_task_brief"] = None
return {"status": "User chose edit but provided no changes. Rejection assumed."}
else: # 'no' or any other case
# REJECTION: Explicitly set the state to None to signal failure.
tool_context.state["final_task_brief"] = None
return {"status": "The user rejected the brief."}
task_brief_agent = LlmAgent(
model=LLM_MODEL,
name="task_brief_agent",
description="Takes user question and generates a research brief to guide the process of addressing it.",
instruction=ENV_PROMPT.get_template("task_brief.j2").render(),
tools=[AgentTool(agent=rag_agent), _confirm_research_brief],
output_key="task_brief",
)
class DeepAnalyticsAgent(BaseAgent):
task_brief_agent: LlmAgent
task_supervisor_agent: TaskSupervisorAgent
model_config = {"arbitrary_types_allowed": True}
def __init__(
self,
name: str,
task_brief_agent: LlmAgent,
task_supervisor_agent: TaskSupervisorAgent,
):
sub_agents_list = [
task_brief_agent,
task_supervisor_agent,
]
super().__init__(
name=name,
task_brief_agent=task_brief_agent,
task_supervisor_agent=task_supervisor_agent,
sub_agents=sub_agents_list,
)
@override
async def _run_async_impl(
self, ctx: InvocationContext
) -> AsyncGenerator[Event, None]:
if not self.sub_agents:
return
async for event in self.task_brief_agent.run_async(ctx):
logger.info(f"[{self.name}] Task Sequence Event: {event.model_dump_json(indent=2, exclude_none=True)}")
yield event
task_brief = ctx.session.state["task_brief"]
print(task_brief)
async for event in self.task_supervisor_agent.run_async(ctx):
logger.info(f"[{self.name}] Task Sequence Event: {event.model_dump_json(indent=2, exclude_none=True)}")
yield event
root_agent = DeepAnalyticsAgent(
name="DeepAnalyticsAgent",
task_brief_agent=task_brief_agent,
task_supervisor_agent=task_supervisor_agent,
)
Expected behavior
The execution of the parent DeepAnalyticsAgent should pause when task_brief_agent requests user confirmation. The async for loop iterating over self.task_brief_agent.run_async(ctx) should not complete until the sub-agent's lifecycle for the current task is fully finished (i.e., after the user responds and the agent processes that response). The task_supervisor_agent should only begin its execution after this confirmation is received and handled.
Desktop (please complete the following information):
- OS: macOS
- Python version(python - v.12:
- ADK version(pip show google-adk): 1.16.0
Model Information:
- Are you using LiteLLM: No
- Which model is being used: gemini-2.5-flash