@@ -414,6 +414,9 @@ async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event,
414414 iteration += 1
415415 last_event : Optional [Event ] = None
416416
417+ # Collect ALL non-partial events from leader for delegation signal extraction
418+ all_leader_events : List [Event ] = []
419+
417420 # Update activity tracking
418421 current_activity = "leader planning"
419422
@@ -467,6 +470,7 @@ async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event,
467470 # LongRunningEvent is partial, so will not enter below logic.
468471 if not event .partial :
469472 last_event = event
473+ all_leader_events .append (event )
470474 # Collect text from leader's response
471475 event_text = self ._extract_text_from_event (event )
472476 if event_text :
@@ -486,8 +490,13 @@ async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event,
486490 logger .debug ("TeamAgent: Leader requested transfer to: %s" , transfer_to_agent )
487491 return
488492
489- # Try to extract delegation signals from last event
490- signals = self ._extract_delegation_signals (last_event )
493+ # Try to extract delegation signals from ALL leader events
494+ # (not just last_event, because when tools execute sequentially,
495+ # each tool response is a separate event and only the last one
496+ # would be captured by last_event)
497+ signals : List [DelegationSignal ] = []
498+ for leader_event in all_leader_events :
499+ signals .extend (self ._extract_delegation_signals (leader_event ))
491500
492501 if signals :
493502 # Delegation detected, execute member agent(s)
@@ -786,9 +795,12 @@ async def _execute_delegations_parallel(
786795 is_member_mode : bool = False ,
787796 context_lock : Optional [asyncio .Lock ] = None ,
788797 ) -> AsyncGenerator [Event , None ]:
789- """Execute multiple delegations in parallel.
798+ """Execute multiple delegations in parallel with streaming output.
799+
800+ Uses an asyncio.Queue so that events from whichever member finishes
801+ first are yielded immediately, instead of waiting for all members to
802+ complete before yielding any events.
790803
791- Used when parallel_execution=True and there are multiple delegation signals.
792804 Each _execute_delegation call handles its own interaction recording.
793805
794806 Args:
@@ -800,25 +812,53 @@ async def _execute_delegations_parallel(
800812 context_lock: Lock for thread-safe access to team_run_context.
801813
802814 Yields:
803- Events from all member executions.
815+ Events from member executions, streamed as soon as each member
816+ produces them.
804817 """
818+ # Use a queue to stream events from concurrent tasks as they arrive
819+ queue : asyncio .Queue = asyncio .Queue ()
820+ # Sentinel object to signal that a producer task has finished
821+ _DONE = object ()
805822
806- async def run_delegation (signal : DelegationSignal ) -> List [Event ]:
807- """Run a single delegation and collect events."""
808- events : List [Event ] = []
809- async for event in self ._execute_delegation (ctx , signal , team_run_context , message_builder , is_member_mode ,
810- context_lock ):
811- events .append (event )
812- return events
823+ async def run_delegation (signal : DelegationSignal ) -> None :
824+ """Run a single delegation and push events to the queue."""
825+ try :
826+ async for event in self ._execute_delegation (
827+ ctx ,
828+ signal ,
829+ team_run_context ,
830+ message_builder ,
831+ is_member_mode ,
832+ context_lock ,
833+ ):
834+ await queue .put (event )
835+ except Exception as exc :
836+ # Push the exception so the consumer can re-raise it
837+ await queue .put (exc )
838+ finally :
839+ await queue .put (_DONE )
813840
814841 # Run all delegations in parallel
815- logger .debug ("TeamAgent: Executing %s delegations in parallel" , len (signals ))
816- results = await asyncio .gather (* [run_delegation (signal ) for signal in signals ])
842+ logger .debug ("TeamAgent: Executing %s delegations in parallel (streaming)" , len (signals ))
843+ tasks = [asyncio .create_task (run_delegation (signal )) for signal in signals ]
844+ finished_count = 0
845+ total = len (tasks )
846+
847+ # Consume events from the queue as they arrive
848+ while finished_count < total :
849+ item = await queue .get ()
850+ if item is _DONE :
851+ finished_count += 1
852+ elif isinstance (item , Exception ):
853+ # Cancel remaining tasks and re-raise the first exception
854+ for task in tasks :
855+ task .cancel ()
856+ raise item
857+ else :
858+ yield item
817859
818- # Yield all events (interaction records already added by _execute_delegation)
819- for events in results :
820- for event in events :
821- yield event
860+ # Ensure all tasks are cleaned up (handles edge cases)
861+ await asyncio .gather (* tasks , return_exceptions = True )
822862
823863 def _find_member_by_name (self , name : str ) -> Optional [BaseAgent ]:
824864 """Find a member agent by name.
0 commit comments