2121 WorkflowGotoError ,
2222 WorkflowMaxRetriesError ,
2323 WorkflowNextError ,
24+ WorkflowPauseError ,
2425 WorkflowRepeatError ,
2526)
2627from .logging import setup_logger
@@ -160,12 +161,19 @@ async def _apply_plan(
160161 workflow_run_id : str ,
161162 idx : int ,
162163 blocks : list [WorkflowBlock ],
163- ) -> tuple [int | None , float | None ]:
164+ block_output : Any = None ,
165+ ) -> tuple [int | None , float | None , bool ]:
164166 if plan_result is None :
165- return idx + 1 , None
167+ return idx + 1 , None , False
166168
167169 if plan_result .schedule_type == ScheduleType .STOP :
168- return None , None
170+ return None , None , False
171+
172+ if plan_result .schedule_type == ScheduleType .PAUSE :
173+ await self ._schedule_pause (
174+ workflow_run_id , block_output , plan_result .reason
175+ )
176+ return idx + 1 , None , True
169177
170178 next_idx = plan_result .next_block_index
171179 if next_idx is None :
@@ -186,7 +194,7 @@ async def _apply_plan(
186194 if plan_result .schedule_type == ScheduleType .DELAY :
187195 delay = plan_result .delay_seconds
188196
189- return next_idx , delay
197+ return next_idx , delay , False
190198
191199 async def _schedule_delay (
192200 self ,
@@ -211,6 +219,28 @@ async def _schedule_delay(
211219 )
212220 raise LoopPausedError ()
213221
222+ async def _schedule_pause (
223+ self ,
224+ workflow_run_id : str ,
225+ block_output : Any ,
226+ reason : str | None = None ,
227+ ) -> None :
228+ """Pause a workflow indefinitely until resumed via API."""
229+ await self .state_manager .set_workflow_block_output (
230+ workflow_run_id , block_output
231+ )
232+ await self .state_manager .update_workflow_status (
233+ workflow_run_id , LoopStatus .PAUSED
234+ )
235+ logger .info (
236+ "Workflow paused until resumed" ,
237+ extra = {
238+ "workflow_run_id" : workflow_run_id ,
239+ "reason" : reason ,
240+ },
241+ )
242+ raise LoopPausedError ()
243+
214244 async def _run (
215245 self ,
216246 func : Callable [..., Any ],
@@ -267,6 +297,14 @@ async def _run(
267297 workflow_run_id
268298 )
269299 )
300+ context .resume_payload = (
301+ await self .state_manager .get_workflow_resume_payload (
302+ workflow_run_id
303+ )
304+ )
305+ await self .state_manager .set_workflow_resume_payload (
306+ workflow_run_id , None
307+ )
270308
271309 try :
272310 block_output = await _call_with_result (
@@ -293,10 +331,13 @@ async def _run(
293331 },
294332 )
295333
296- next_idx , delay = await self ._apply_plan (
297- plan_result , workflow_run_id , idx , blocks
334+ next_idx , delay , paused = await self ._apply_plan (
335+ plan_result , workflow_run_id , idx , blocks , block_output
298336 )
299337
338+ if paused :
339+ continue
340+
300341 if next_idx is None :
301342 await _call (on_block_complete , context , current_block , None )
302343 raise LoopStoppedError ()
@@ -358,6 +399,11 @@ async def _run(
358399 workflow_run_id , e .delay_seconds , None , e .reason
359400 )
360401
402+ except WorkflowPauseError as e :
403+ await self ._schedule_pause (
404+ workflow_run_id , context .block_output , e .reason
405+ )
406+
361407 except (asyncio .CancelledError , LoopPausedError , LoopStoppedError ):
362408 raise
363409
@@ -431,10 +477,11 @@ async def _run(
431477 )
432478 await _call (on_stop , context )
433479 except LoopPausedError :
434- await self .state_manager .update_workflow_status (
435- workflow_run_id , LoopStatus .IDLE
436- )
437- # Don't call on_stop - workflow is just paused, not finished
480+ workflow = await self .state_manager .get_workflow (workflow_run_id )
481+ if workflow .status != LoopStatus .PAUSED :
482+ await self .state_manager .update_workflow_status (
483+ workflow_run_id , LoopStatus .IDLE
484+ )
438485 finally :
439486 self .tasks .pop (workflow_run_id , None )
440487
0 commit comments