@@ -153,138 +153,77 @@ async def __call__(
153153 Returns:
154154 Child Context with output, route, and interrupt_ids set.
155155 """
156- curr_node = node
157- curr_name = node_name or node .name
158- curr_run_id = run_id
159- curr_input = node_input
160- curr_parent_ctx : Context | None = ctx
161-
162- while True :
163- curr_parent_path = curr_parent_ctx .node_path if curr_parent_ctx else None
164- base_path_builder = (
165- _NodePathBuilder .from_string (curr_parent_path )
166- if curr_parent_path
167- else _NodePathBuilder ([])
168- )
169- node_path = str (base_path_builder .append (curr_name , curr_run_id ))
170-
171- # Rehydration chronological sequence barrier setup for the parent path
172- parent_path = curr_parent_ctx .node_path if curr_parent_ctx else ''
173- if parent_path and parent_path not in self ._parent_sequence_barriers :
174- seq = self ._scan_parent_child_sequence (curr_parent_ctx , parent_path )
175- self ._parent_sequence_barriers [parent_path ] = ReplaySequenceBarrier (seq )
176-
177- # Runtime schema validation.
178- if curr_input is not None :
179- try :
180- curr_input = curr_node ._validate_input_data (curr_input )
181- except ValidationError as e :
182- raise ValueError (
183- 'Runtime schema validation failed for dynamic node'
184- f" '{ curr_name } '. Input does not match input_schema: { e } "
185- ) from e
186-
187- logger .debug ('node %s schedule start.' , node_path )
188-
189- # Phase 1: Lazy rehydration from session events.
190- if node_path not in self ._state .runs :
191- self ._rehydrate_from_events (curr_parent_ctx , node_path )
156+ curr_parent_path = ctx .node_path if ctx else None
157+ base_path_builder = (
158+ _NodePathBuilder .from_string (curr_parent_path )
159+ if curr_parent_path
160+ else _NodePathBuilder ([])
161+ )
162+ node_path = str (base_path_builder .append (node_name or node .name , run_id ))
163+
164+ # Rehydration chronological sequence barrier setup for the parent path
165+ parent_path = ctx .node_path if ctx else ''
166+ if parent_path and parent_path not in self ._parent_sequence_barriers :
167+ seq = self ._scan_parent_child_sequence (ctx , parent_path )
168+ self ._parent_sequence_barriers [parent_path ] = ReplaySequenceBarrier (seq )
169+
170+ # Runtime schema validation.
171+ if node_input is not None :
172+ try :
173+ node_input = node ._validate_input_data (node_input )
174+ except ValidationError as e :
175+ raise ValueError (
176+ 'Runtime schema validation failed for dynamic node'
177+ f" '{ node_name or node .name } '. Input does not match"
178+ f' input_schema: { e } '
179+ ) from e
180+
181+ logger .debug ('node %s schedule start.' , node_path )
182+
183+ # Phase 1: Lazy rehydration from session events.
184+ if node_path not in self ._state .runs :
185+ self ._rehydrate_from_events (ctx , node_path )
186+
187+ # Check existing run and determine if fresh execution is needed.
188+ child_ctx , run_completed = await self ._check_existing_run (
189+ ctx ,
190+ node ,
191+ node_name or node .name ,
192+ node_path ,
193+ run_id ,
194+ node_input ,
195+ use_as_output ,
196+ use_sub_branch ,
197+ override_branch ,
198+ override_isolation_scope = override_isolation_scope ,
199+ )
192200
193- # Check existing run and determine if fresh execution is needed.
194- child_ctx , run_completed = await self ._check_existing_run (
195- curr_parent_ctx ,
196- curr_node ,
197- curr_name ,
201+ if not run_completed :
202+ # Phase 3: Fresh execution.
203+ logger .debug ('node %s schedule: Fresh execution.' , node_path )
204+ child_ctx = await self ._run_node_internal (
205+ ctx ,
206+ node ,
207+ node_name or node .name ,
198208 node_path ,
199- curr_run_id ,
200- curr_input ,
209+ run_id ,
210+ node_input ,
201211 use_as_output ,
202- use_sub_branch ,
203- override_branch ,
212+ is_fresh = True ,
213+ use_sub_branch = use_sub_branch ,
214+ override_branch = override_branch ,
204215 override_isolation_scope = override_isolation_scope ,
205216 )
206217
207- if not run_completed :
208- # Phase 3: Fresh execution.
209- logger .debug ('node %s schedule: Fresh execution.' , node_path )
210- child_ctx = await self ._run_node_internal (
211- curr_parent_ctx ,
212- curr_node ,
213- curr_name ,
214- node_path ,
215- curr_run_id ,
216- curr_input ,
217- use_as_output ,
218- is_fresh = True ,
219- use_sub_branch = use_sub_branch ,
220- override_branch = override_branch ,
221- override_isolation_scope = override_isolation_scope ,
222- )
223-
224- logger .debug ('node %s schedule end.' , node_path )
225-
226- # Advance chronological sequence for this parent path and key
227- parent_path = curr_parent_ctx .node_path if curr_parent_ctx else ''
228- key = f'{ curr_name } @{ curr_run_id } '
229- if parent_path in self ._parent_sequence_barriers :
230- self ._parent_sequence_barriers [parent_path ].check_and_advance (key )
231-
232- # Check for transfer_to_agent signal.
233- transfer_to_agent = (
234- child_ctx .actions .transfer_to_agent if child_ctx else None
235- )
236- if isinstance (transfer_to_agent , str ):
237- target_name = transfer_to_agent
238- root_agent = getattr (curr_node , 'root_agent' , None )
239- if not root_agent :
240- raise ValueError (f'Cannot find root_agent on node { curr_node .name } ' )
241-
242- # Local import to avoid runtime circular dependencies with Context
243- from .utils ._transfer_utils import resolve_and_derive_transfer_context
244-
245- target_agent , next_parent_ctx = resolve_and_derive_transfer_context (
246- target_name = target_name ,
247- current_agent = curr_node ,
248- root_agent = root_agent ,
249- curr_ctx = child_ctx ,
250- curr_parent_ctx = curr_parent_ctx ,
251- )
252- if not target_agent :
253- raise ValueError (f"Transfer target agent '{ target_name } ' not found." )
254- if not next_parent_ctx :
255- available = []
256- if hasattr (curr_node , '_get_available_agent_names' ):
257- available = curr_node ._get_available_agent_names ()
258- available_str = (
259- f"\n Available agents: { ', ' .join (available )} " if available else ''
260- )
261- raise ValueError (
262- f"Cannot transfer from '{ curr_name } ' to unrelated agent"
263- f" '{ target_name } '.{ available_str } "
264- )
265- curr_parent_ctx = next_parent_ctx
266-
267- # Set up parameters for next iteration.
268- curr_node = target_agent
269- curr_name = target_agent .name
270-
271- if not curr_parent_ctx :
272- raise AssertionError (
273- 'curr_parent_ctx cannot be None during active workflow execution'
274- )
275-
276- curr_parent_ctx ._child_run_counters [target_agent .name ] = (
277- curr_parent_ctx ._child_run_counters .get (target_agent .name , 0 ) + 1
278- )
279- curr_run_id = str (
280- curr_parent_ctx ._child_run_counters [target_agent .name ]
281- )
282- curr_input = None # Input for transfer target is usually empty.
218+ logger .debug ('node %s schedule end.' , node_path )
283219
284- # Loop continues to execute the next agent
285- continue
220+ # Advance chronological sequence for this parent path and key
221+ parent_path = ctx .node_path if ctx else ''
222+ key = f'{ node_name or node .name } @{ run_id } '
223+ if parent_path in self ._parent_sequence_barriers :
224+ self ._parent_sequence_barriers [parent_path ].check_and_advance (key )
286225
287- return child_ctx
226+ return child_ctx
288227
289228 async def _check_existing_run (
290229 self ,
0 commit comments