-
Notifications
You must be signed in to change notification settings - Fork 1
feat: resume runtime on fired triggers #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
radu-mocanu
commented
Jan 22, 2026
- resume runtime on fired triggers
29cd15f to
740e611
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds “auto-resume” behavior to the resumable runtime so that if resume triggers are already fired at the time of suspension, the runtime immediately resumes without requiring an external resume call.
Changes:
- Implement auto-resume flow in
UiPathResumableRuntime.execute()and.stream()when fired triggers are detected. - Introduce
UiPathSuspensionResultto carry both the suspensionUiPathRuntimeResultand any fired trigger resume-map. - Add async tests covering auto-resume scenarios (none/partial/all fired; multiple auto-resumes; stream behavior).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
src/uipath/runtime/resumable/runtime.py |
Adds fired-trigger detection at suspension time and recursive auto-resume for execute() and stream(). |
src/uipath/runtime/result.py |
Adds UiPathSuspensionResult model used to return both runtime result and fired trigger map. |
tests/test_resumable.py |
Adds coverage for auto-resume behavior across several trigger-firing patterns. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # some triggers are already fired, runtime can be resumed | ||
| resume_options = options or UiPathExecuteOptions(resume=True) | ||
| if not resume_options.resume: | ||
| resume_options = UiPathExecuteOptions(resume=True) | ||
| return await self.execute( | ||
| fired_triggers_map=suspension_result.fired_triggers_map, | ||
| options=resume_options, | ||
| ) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auto-resume is implemented via recursive self.execute(...) calls with no guardrail. If the delegate keeps returning SUSPENDED while triggers appear “fired” immediately (or if there’s a logic loop), this can lead to unbounded recursion and eventually RecursionError/stack growth. Consider rewriting as an iterative loop and/or enforcing a maximum auto-resume depth / progress check.
| resume_options = options or UiPathStreamOptions(resume=True) | ||
| if not resume_options.resume: | ||
| resume_options = UiPathStreamOptions(resume=True) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When auto-resuming in stream(), the code constructs a new UiPathStreamOptions(resume=True) if options.resume is false, which drops any other option fields (e.g., breakpoints). Preserve the caller’s options by copying/updating resume=True rather than replacing the options object.
| # some triggers are already fired, runtime can be resumed | ||
| resume_options = options or UiPathStreamOptions(resume=True) | ||
| if not resume_options.resume: | ||
| resume_options = UiPathStreamOptions(resume=True) | ||
| async for event in self.stream( | ||
| fired_triggers_map=suspension_result.fired_triggers_map, | ||
| options=resume_options, | ||
| ): | ||
| yield event |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auto-resume in stream() uses recursive calls to self.stream(...) with no depth/progress guard. A run that repeatedly suspends with triggers that immediately appear fired can recurse indefinitely and grow the call stack. Consider an iterative loop and/or a maximum auto-resume depth similar to execute().
| assert result.triggers is not None | ||
| assert {t.interrupt_id for t in result.triggers} == {"int-1", "int-2"} | ||
|
|
||
| # Delegate should have been executed only once) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra closing parenthesis in comment: “Delegate should have been executed only once)”.
| # Delegate should have been executed only once) | |
| # Delegate should have been executed only once. |
| return UiPathSuspensionResult( | ||
| runtime_result=suspended_result, | ||
| fired_triggers_map=await self._build_resume_map(triggers) | ||
| if triggers | ||
| else None, | ||
| ) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_handle_suspension() now calls _build_resume_map() during suspension to decide whether to auto-resume. UiPathResumeTriggerReaderProtocol.read_trigger() is documented to return None when no data is available; treat that as still pending. Ensure _build_resume_map() does not delete triggers / include {interrupt_id: None} when read_trigger() returns None, otherwise triggers can be prematurely consumed and the runtime can auto-resume with missing data.
| resume_options = options or UiPathExecuteOptions(resume=True) | ||
| if not resume_options.resume: | ||
| resume_options = UiPathExecuteOptions(resume=True) | ||
| return await self.execute( |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When auto-resuming, the code replaces user-provided options with a fresh UiPathExecuteOptions(resume=True) if options.resume is false. This drops any other option fields (e.g., breakpoints). Preserve existing options by copying/updating resume=True instead of creating a new instance that loses other settings.