On Python 3.10, google.adk.agents.ParallelAgent uses _merge_agent_run_pre_3_11() instead of asyncio.TaskGroup. If one sub-agent async generator raises,
_merge_agent_run_pre_3_11() cancels sibling processing tasks in finally but does not await those tasks before ParallelAgent._run_async_impl() closes each sub-agent
generator.
That can raise a second cleanup exception:
RuntimeError: aclose(): asynchronous generator is already running
This can mask or pollute the original error, for example a normal Pydantic validation failure from a structured-output sub-agent.
Environment
- Python: 3.10.12
- google-adk: 1.26.0
- litellm: 1.82.1
- OS: Linux
- Agent shape:
SequentialAgent containing a ParallelAgent with multiple LlmAgent children using output_schema and output_key
Minimal repro
importasynciofromgoogle.adk.agents.parallel_agentimport_merge_agent_run_pre_3_11asyncdefslow_agent():
try:
awaitasyncio.sleep(10)
yield"slow-event"finally:
awaitasyncio.sleep(0.1)
asyncdeffailing_agent():
awaitasyncio.sleep(0.01)
raiseValueError("simulated sub-agent validation failure")
yield"unreachable"asyncdefmain():
agent_runs= [slow_agent(), failing_agent()]
try:
asyncforeventin_merge_agent_run_pre_3_11(agent_runs):
print("event", event)
exceptExceptionasexc:
print("merge raised:", type(exc).__name__, exc)
fori, ageninenumerate(agent_runs):
try:
awaitagen.aclose()
print("closed", i)
exceptExceptionasexc:
print("close raised:", i, type(exc).__name__, exc)
asyncio.run(main())
## Actual outputmergeraised: ValueErrorsimulatedsub-agentvalidationfailurecloseraised: 0RuntimeErroraclose(): asynchronousgeneratorisalreadyrunningclosed1## Expected behaviorADKshouldpropagatetheoriginalsub-agentfailurecleanlyandclose/cancelsiblingsub-agentrunswithoutraisingaclose(): asynchronousgeneratorisalreadyrunning.
## Suspected root causeIn_merge_agent_run_pre_3_11(), thefinallyblockcancelsprocessingtasks:
finally:
fortaskintasks:
task.cancel()
butdoesnotawaittheircancellation. ThenParallelAgent._run_async_impl() closesthesameasyncgenerators:
finally:
forsub_agent_runinagent_runs:
awaitsub_agent_run.aclose()
Ifataskisstillinsideasyncforonasiblinggenerator, aclose() racesthatstill-runninggenerator.
## Suggested fixAftercancelingtasksin_merge_agent_run_pre_3_11(), awaitthembeforetheparentclosessub-agentgenerators:
finally:
fortaskintasks:
task.cancel()
iftasks:
awaitasyncio.gather(*tasks, return_exceptions=True)
Inalocalsimulation, thispreservedtheoriginalValueErrorandallowedbothasyncgeneratorstoclosecleanly.
On Python 3.10,
google.adk.agents.ParallelAgentuses_merge_agent_run_pre_3_11()instead ofasyncio.TaskGroup. If one sub-agent async generator raises,_merge_agent_run_pre_3_11()cancels sibling processing tasks infinallybut does not await those tasks beforeParallelAgent._run_async_impl()closes each sub-agentgenerator.
That can raise a second cleanup exception:
RuntimeError: aclose(): asynchronous generator is already runningThis can mask or pollute the original error, for example a normal Pydantic validation failure from a structured-output sub-agent.
Environment
SequentialAgentcontaining aParallelAgentwith multipleLlmAgentchildren usingoutput_schemaandoutput_keyMinimal repro