child graphs can interrupt/resume

This commit is contained in:
lda
2026-05-25 05:11:16 +07:00 Verified
parent 7477a12b51
commit 7aa562c619
14 changed files with 397 additions and 95 deletions
+39 -12
View File
@@ -8,11 +8,19 @@ from wf_core.errors import WorkflowExecutionError
from wf_core.local_paths import LocalPathError, set_local_value
from wf_core.models.steps import InputPathBinding, InputValueBinding, InterruptNode
from wf_core.models.workflow import Workflow
from wf_core.run_state import InterruptRequest, RunState, StepExecutionResult
from wf_core.run_state import (
FrameStatus,
InterruptRequest,
InterruptRoute,
RunState,
StepExecutionResult,
)
from wf_core.runtime.lineage import commit_patch_for_frame
from wf_core.runtime.ops.flow import advance_frame, append_step_result_trace
from wf_core.runtime.ops.index import WorkflowIndex
from wf_core.runtime.ops.merges import ReducerDefinition
from wf_core.runtime.ops.state import apply_output_bindings
from wf_core.runtime.ops.overlays import state_view_for_frame
from wf_core.runtime.ops.state import build_output_patch
def build_interrupt_request(
@@ -22,6 +30,9 @@ def build_interrupt_request(
state: dict[str, Any],
workflow_input: dict[str, Any],
context: dict[str, Any],
public_frame_id: str | None = None,
public_node_id: str | None = None,
route: InterruptRoute | None = None,
) -> InterruptRequest:
payload: dict[str, Any] = {}
for binding in node.request:
@@ -43,11 +54,12 @@ def build_interrupt_request(
except LocalPathError as exc:
raise WorkflowExecutionError(str(exc)) from exc
return InterruptRequest(
id=f"interrupt:{node.id}",
frame_id=frame_id,
node_id=node.id,
id=f"interrupt:{public_node_id or node.id}",
frame_id=public_frame_id or frame_id,
node_id=public_node_id or node.id,
kind=node.kind,
payload=payload,
route=route,
)
@@ -60,14 +72,28 @@ def resume_interrupt(
resume_outcome: str,
reducers: Mapping[str, ReducerDefinition] | None = None,
) -> None:
if run.current_frame_id is None:
raise WorkflowExecutionError("interrupted run has no current frame")
if run.current_node_id is None:
raise WorkflowExecutionError("interrupted run has no current node")
if run.interrupt is None:
raise WorkflowExecutionError("run is interrupted but has no interrupt request")
frame = run.current_frame()
route = run.interrupt.route
if route is not None:
frame = run.frames.get(route.frame_id)
if (
frame is None
or frame.scope_id != route.scope_id
or frame.lineage_id != route.lineage_id
or frame.node_id != route.node_id
or frame.status != FrameStatus.INTERRUPTED
):
raise WorkflowExecutionError("child interrupt route is no longer resumable")
run.current_frame_id = frame.id
run.sync_from_current_frame()
else:
if run.current_frame_id is None:
raise WorkflowExecutionError("interrupted run has no current frame")
if run.current_node_id is None:
raise WorkflowExecutionError("interrupted run has no current node")
frame = run.current_frame()
step = index.nodes_by_id[frame.node_id]
if not isinstance(step, InterruptNode):
raise WorkflowExecutionError(
@@ -78,14 +104,15 @@ def resume_interrupt(
f"interrupt node {step.id!r} does not declare resume outcome {resume_outcome!r}"
)
state_changes = apply_output_bindings(
patch = build_output_patch(
workflow,
step.resume,
resume_payload,
run.state,
state_view_for_frame(run, frame),
reducers=reducers,
missing_field_message="interrupt resume payload is missing required field {field}",
)
state_changes = commit_patch_for_frame(run, frame, patch)
next_node_id = index.next_node_id(frame.node_id, resume_outcome)
append_step_result_trace(
run,