native subgraph execution

This commit is contained in:
lda
2026-05-25 04:28:06 +07:00 Verified
parent 3880a86c26
commit a4eeb506be
20 changed files with 746 additions and 146 deletions
+4
View File
@@ -80,6 +80,10 @@ def advance_frame(
frame.activated_incoming_edge = frame.node_id
frame.node_id = next_node_id
if next_node_id == END:
if frame.kind in {"workflow", "subgraph_root"}:
# Legacy terminal routing emits the workflow-level `ok` outcome.
# Explicit EndNode execution stores its declared outcome first.
frame.metadata.setdefault("workflow_outcome", "ok")
frame.status = FrameStatus.COMPLETED
frame.finished_at_node_id = END
wake_parent_for_child_progress(run, frame.id)
+7 -16
View File
@@ -14,18 +14,18 @@ from wf_core.runtime.foreach_state import (
)
from wf_core.runtime.lineage import (
add_lineage,
append_lineage_writes,
is_root_lineage_frame,
commit_patch_for_frame,
lineage_patch,
scope_input_for_frame,
)
from wf_core.runtime.ops.flow import advance_frame, append_step_result_trace
from wf_core.runtime.ops.frames import frame_context_values
from wf_core.runtime.ops.index import WorkflowIndex
from wf_core.runtime.ops.merges import ReducerDefinition
from wf_core.runtime.ops.overlays import state_view_for_frame
from wf_core.runtime.ops.state import (
StatePatch,
build_barrier_patch,
commit_state_patch,
)
from wf_core.runtime.scheduler import (
ForeachIterationMetadata,
@@ -182,8 +182,8 @@ def _resolve_foreach_iterable(
) -> list[object]:
iterable = safe_resolve_path(
str(step.over),
state=run.state,
workflow_input=run.workflow_input,
state=state_view_for_frame(run, frame),
workflow_input=scope_input_for_frame(run, frame),
context=frame_context_values(frame),
)
if not isinstance(iterable, list):
@@ -346,19 +346,10 @@ def _finish_concurrent_foreach(
combined = build_barrier_patch(
workflow,
item_patches,
run.state,
state_view_for_frame(run, frame),
reducers=reducers,
)
if is_root_lineage_frame(frame):
state_changes = commit_state_patch(run.state, combined)
else:
append_lineage_writes(
run,
scope_id=frame.scope_id,
lineage_id=frame.lineage_id,
writes=combined.writes,
)
state_changes = {}
state_changes = commit_patch_for_frame(run, frame, combined)
append_step_result_trace(
run,
frame_id=frame.id,
+4 -2
View File
@@ -3,9 +3,11 @@ from __future__ import annotations
from wf_core.conditions import eval_condition
from wf_core.models.steps import ConditionNode, InterruptNode
from wf_core.run_state import FrameStatus, RunState, RunStatus, StepExecutionResult
from wf_core.runtime.lineage import scope_input_for_frame
from wf_core.runtime.ops.flow import append_trace
from wf_core.runtime.ops.frames import frame_context_values
from wf_core.runtime.ops.interrupts import build_interrupt_request
from wf_core.runtime.ops.overlays import state_view_for_frame
def handle_condition_step(
@@ -15,8 +17,8 @@ def handle_condition_step(
frame = run.current_frame()
predicate = eval_condition(
step.check,
run.state,
run.workflow_input,
state_view_for_frame(run, frame),
scope_input_for_frame(run, frame),
frame.prior_outcome,
)
outcome = "true" if predicate else "false"
+9 -16
View File
@@ -18,12 +18,16 @@ from wf_core.run_state import (
StepExecutionResult,
)
from wf_core.runtime.foreach_state import ForeachBarrierState, item_frame_owner
from wf_core.runtime.lineage import append_lineage_writes, is_root_lineage_frame
from wf_core.runtime.lineage import (
append_lineage_writes,
commit_patch_for_frame,
scope_input_for_frame,
)
from wf_core.runtime.ops.frames import frame_context_values
from wf_core.runtime.ops.merges import ReducerDefinition
from wf_core.runtime.ops.overlays import state_view_for_frame
from wf_core.runtime.ops.schemas import validate_payload_against_schema
from wf_core.runtime.ops.state import StatePatch, build_output_patch, commit_state_patch
from wf_core.runtime.ops.state import StatePatch, build_output_patch
NodeHandler = Callable[[dict[str, Any], RuntimeContext], NodeResult | dict[str, Any]]
AsyncNodeHandler = Callable[
@@ -62,7 +66,7 @@ def _resolve_node_execution(
value = safe_resolve_path(
str(binding.path),
state=state_view,
workflow_input=run.workflow_input,
workflow_input=scope_input_for_frame(run, frame),
context=context_values,
)
else:
@@ -121,18 +125,7 @@ def _finalize_node_execution(
)
owner = item_frame_owner(frame)
if owner is None:
if is_root_lineage_frame(frame):
state_changes = commit_state_patch(run.state, patch)
else:
# Non-root frames are future subgraph/fork branch execution: writes
# become lineage-local until an explicit boundary/barrier commits.
append_lineage_writes(
run,
scope_id=frame.scope_id,
lineage_id=frame.lineage_id,
writes=patch.writes,
)
state_changes = {}
state_changes = commit_patch_for_frame(run, frame, patch)
else:
parent_frame_id, foreach_node_id, item_index = owner
parent_frame = run.frames[parent_frame_id]
@@ -155,7 +148,7 @@ def _finalize_node_execution(
barrier.save_to_frame(parent_frame, foreach_node_id)
state_changes = {}
else:
state_changes = commit_state_patch(run.state, patch)
state_changes = commit_patch_for_frame(run, parent_frame, patch)
return StepExecutionResult(
outcome=result.outcome,
resolved_input=resolved_input,
+10 -1
View File
@@ -18,12 +18,20 @@ from wf_core.run_state import (
from wf_core.runtime.scheduler import add_frame
def create_run_state(workflow: Workflow, workflow_input: dict[str, object]) -> RunState:
def initial_state(
workflow: Workflow, workflow_input: dict[str, object]
) -> dict[str, object]:
"""Create one scope's committed state from defaults plus workflow input."""
state: dict[str, object] = {}
for field in workflow.state_schema.fields:
if field.default is not None:
set_nested_value(state, list(field.path.parts), deepcopy(field.default))
state.update(dict(workflow_input))
return state
def create_run_state(workflow: Workflow, workflow_input: dict[str, object]) -> RunState:
state = initial_state(workflow, workflow_input)
run = RunState(
workflow_name=workflow.name,
status=RunStatus.PENDING,
@@ -33,6 +41,7 @@ def create_run_state(workflow: Workflow, workflow_input: dict[str, object]) -> R
ROOT_SCOPE_ID: RuntimeScope(
id=ROOT_SCOPE_ID,
workflow_name=workflow.name,
workflow_input=dict(workflow_input),
committed_state=state,
)
},