1616
1717from __future__ import annotations
1818
19+ import logging
1920from typing import AsyncGenerator
2021from typing import ClassVar
2122from typing import Type
2425
2526from ..events .event import Event
2627from ..utils .context_utils import Aclosing
28+ from ..utils .feature_decorator import experimental
2729from .base_agent import BaseAgent
28- from .base_agent import BaseAgentConfig
30+ from .base_agent import BaseAgentState
31+ from .base_agent_config import BaseAgentConfig
2932from .invocation_context import InvocationContext
3033from .llm_agent import LlmAgent
3134from .sequential_agent_config import SequentialAgentConfig
3235
36+ logger = logging .getLogger ('google_adk.' + __name__ )
37+
38+
39+ @experimental
40+ class SequentialAgentState (BaseAgentState ):
41+ """State for SequentialAgent."""
42+
43+ current_sub_agent : str = ''
44+ """The name of the current sub-agent to run."""
45+
3346
3447class SequentialAgent (BaseAgent ):
3548 """A shell agent that runs its sub-agents in sequence."""
@@ -41,18 +54,70 @@ class SequentialAgent(BaseAgent):
4154 async def _run_async_impl (
4255 self , ctx : InvocationContext
4356 ) -> AsyncGenerator [Event , None ]:
44- for sub_agent in self .sub_agents :
45- should_exit = False
57+ if not self .sub_agents :
58+ return
59+
60+ # Initialize or resume the execution state from the agent state.
61+ agent_state = self ._load_agent_state (ctx , SequentialAgentState )
62+ start_index = self ._get_start_index (agent_state )
63+
64+ pause_invocation = False
65+ resuming_sub_agent = agent_state is not None
66+ for i in range (start_index , len (self .sub_agents )):
67+ sub_agent = self .sub_agents [i ]
68+ if not resuming_sub_agent :
69+ # If we are resuming from the current event, it means the same event has
70+ # already been logged, so we should avoid yielding it again.
71+ if ctx .is_resumable :
72+ agent_state = SequentialAgentState (current_sub_agent = sub_agent .name )
73+ yield self ._create_agent_state_event (ctx , agent_state = agent_state )
74+
75+ # Reset the sub-agent's state in the context to ensure that each
76+ # sub-agent starts fresh.
77+ ctx .reset_agent_state (sub_agent .name )
78+
4679 async with Aclosing (sub_agent .run_async (ctx )) as agen :
4780 async for event in agen :
4881 yield event
49- if event .actions .escalate :
50- should_exit = True
51- break
52-
53- if should_exit :
82+ if ctx .should_pause_invocation (event ):
83+ pause_invocation = True
84+ if event .actions and event .actions .escalate :
85+ return
86+ # Skip the rest of the sub-agents if the invocation is paused.
87+ if pause_invocation :
5488 return
5589
90+ # Reset the flag for the next sub-agent.
91+ resuming_sub_agent = False
92+
93+ if ctx .is_resumable :
94+ yield self ._create_agent_state_event (ctx , end_of_agent = True )
95+
96+ def _get_start_index (
97+ self ,
98+ agent_state : SequentialAgentState ,
99+ ) -> int :
100+ """Calculates the start index for the sub-agent loop."""
101+ if not agent_state :
102+ return 0
103+
104+ if not agent_state .current_sub_agent :
105+ # This means the process was finished.
106+ return len (self .sub_agents )
107+
108+ try :
109+ sub_agent_names = [sub_agent .name for sub_agent in self .sub_agents ]
110+ return sub_agent_names .index (agent_state .current_sub_agent )
111+ except ValueError :
112+ # A sub-agent was removed so the agent name is not found.
113+ # For now, we restart from the beginning.
114+ logger .warning (
115+ 'Sub-agent %s was removed so the agent name is not found. Restarting'
116+ ' from the beginning.' ,
117+ agent_state .current_sub_agent ,
118+ )
119+ return 0
120+
56121 @override
57122 async def _run_live_impl (
58123 self , ctx : InvocationContext
@@ -68,6 +133,9 @@ async def _run_live_impl(
68133 Args:
69134 ctx: The invocation context of the agent.
70135 """
136+ if not self .sub_agents :
137+ return
138+
71139 # There is no way to know if it's using live during init phase so we have to init it here
72140 for sub_agent in self .sub_agents :
73141 # add tool
0 commit comments