execution frame carry a lineage id
This commit is contained in:
@@ -71,6 +71,7 @@ class PendingItemResult:
|
||||
index: int
|
||||
frame_id: str
|
||||
status: Literal["succeeded", "failed"]
|
||||
lineage_id: str | None = None
|
||||
patch: StatePatch = field(default_factory=StatePatch)
|
||||
error: ItemErrorRecord | None = None
|
||||
|
||||
@@ -88,10 +89,13 @@ class PendingItemResult:
|
||||
) from exc
|
||||
patch_changes = raw.get("patch_changes", {})
|
||||
patch_writes = raw.get("patch_writes")
|
||||
lineage_id = raw.get("lineage_id")
|
||||
if not isinstance(index, int) or index < 0:
|
||||
raise WorkflowExecutionError("malformed pending foreach result index")
|
||||
if not isinstance(frame_id, str):
|
||||
raise WorkflowExecutionError("malformed pending foreach result frame id")
|
||||
if lineage_id is not None and not isinstance(lineage_id, str):
|
||||
raise WorkflowExecutionError("malformed pending foreach result lineage id")
|
||||
if status not in {"succeeded", "failed"}:
|
||||
raise WorkflowExecutionError("malformed pending foreach result status")
|
||||
if not isinstance(patch_changes, dict):
|
||||
@@ -103,6 +107,7 @@ class PendingItemResult:
|
||||
index=index,
|
||||
frame_id=frame_id,
|
||||
status=status,
|
||||
lineage_id=lineage_id,
|
||||
patch=(
|
||||
StatePatch(
|
||||
writes=[_state_write_from_metadata(item) for item in patch_writes]
|
||||
@@ -122,6 +127,7 @@ class PendingItemResult:
|
||||
"index": self.index,
|
||||
"frame_id": self.frame_id,
|
||||
"status": self.status,
|
||||
"lineage_id": self.lineage_id,
|
||||
"patch_changes": dict(self.patch.changes),
|
||||
"patch_writes": [
|
||||
_state_write_to_metadata(write) for write in self.patch.writes
|
||||
@@ -251,7 +257,12 @@ class ForeachBarrierState:
|
||||
)
|
||||
|
||||
def add_success_patch(
|
||||
self, *, index: int, frame_id: str, patch: StatePatch
|
||||
self,
|
||||
*,
|
||||
index: int,
|
||||
frame_id: str,
|
||||
patch: StatePatch,
|
||||
lineage_id: str | None = None,
|
||||
) -> None:
|
||||
"""Buffer or extend successful item patches by item index.
|
||||
|
||||
@@ -266,6 +277,7 @@ class ForeachBarrierState:
|
||||
index=index,
|
||||
frame_id=frame_id,
|
||||
status="succeeded",
|
||||
lineage_id=lineage_id,
|
||||
patch=patch,
|
||||
)
|
||||
return
|
||||
@@ -274,6 +286,13 @@ class ForeachBarrierState:
|
||||
f"foreach item result for index {index!r} belongs to frame "
|
||||
f"{existing.frame_id!r}, got {frame_id!r}"
|
||||
)
|
||||
if lineage_id is not None and existing.lineage_id not in {None, lineage_id}:
|
||||
raise WorkflowExecutionError(
|
||||
f"foreach item result for index {index!r} belongs to lineage "
|
||||
f"{existing.lineage_id!r}, got {lineage_id!r}"
|
||||
)
|
||||
if existing.lineage_id is None:
|
||||
existing.lineage_id = lineage_id
|
||||
existing.patch.extend(patch)
|
||||
|
||||
def add_failure(self, *, error: ItemErrorRecord) -> None:
|
||||
|
||||
@@ -81,6 +81,7 @@ def _step_foreach_serial(
|
||||
barrier.next_index = loop_index + 1
|
||||
barrier.save_to_frame(frame, step.id)
|
||||
child_id = f"{frame.id}:{step.id}:{loop_index}"
|
||||
child_lineage_id = _child_lineage_id(frame, step, loop_index)
|
||||
add_frame(
|
||||
run,
|
||||
ExecutionFrame(
|
||||
@@ -89,6 +90,9 @@ def _step_foreach_serial(
|
||||
node_id=loop_start,
|
||||
status=FrameStatus.PENDING,
|
||||
parent_frame_id=frame.id,
|
||||
scope_id=frame.scope_id,
|
||||
lineage_id=child_lineage_id,
|
||||
parent_lineage_id=frame.lineage_id,
|
||||
metadata=ForeachIterationMetadata(
|
||||
foreach_node_id=step.id,
|
||||
loop_index=loop_index,
|
||||
@@ -243,6 +247,7 @@ def _admit_concurrent_children(
|
||||
loop_index = barrier.next_index
|
||||
item = iterable[loop_index]
|
||||
child_id = f"{frame.id}:{step.id}:{loop_index}"
|
||||
child_lineage_id = _child_lineage_id(frame, step, loop_index)
|
||||
active_count = len(barrier.active_frame_ids)
|
||||
barrier.next_index = loop_index + 1
|
||||
barrier.start_child(child_id)
|
||||
@@ -254,6 +259,9 @@ def _admit_concurrent_children(
|
||||
node_id=loop_start,
|
||||
status=FrameStatus.PENDING,
|
||||
parent_frame_id=frame.id,
|
||||
scope_id=frame.scope_id,
|
||||
lineage_id=child_lineage_id,
|
||||
parent_lineage_id=frame.lineage_id,
|
||||
metadata=ForeachIterationMetadata(
|
||||
foreach_node_id=step.id,
|
||||
loop_index=loop_index,
|
||||
@@ -344,3 +352,12 @@ def _finish_concurrent_foreach(
|
||||
)
|
||||
advance_frame(run, frame, outcome=outcome, next_node_id=next_node_id)
|
||||
return run
|
||||
|
||||
|
||||
def _child_lineage_id(frame: ExecutionFrame, step: ForeachNode, loop_index: int) -> str:
|
||||
"""Return a deterministic opaque lineage id for one foreach child frame.
|
||||
|
||||
The readable shape is only for diagnostics. Runtime code should compare the
|
||||
full id, not parse it; future structured lineage refs can replace this.
|
||||
"""
|
||||
return f"{frame.lineage_id}/{step.id}[{loop_index}]"
|
||||
|
||||
@@ -7,6 +7,9 @@ def frame_context_values(frame: ExecutionFrame) -> dict[str, object | None]:
|
||||
context: dict[str, object | None] = {
|
||||
"prior_outcome": frame.prior_outcome,
|
||||
"activated_incoming_edge": frame.activated_incoming_edge,
|
||||
"scope_id": frame.scope_id,
|
||||
"lineage_id": frame.lineage_id,
|
||||
"parent_lineage_id": frame.parent_lineage_id,
|
||||
}
|
||||
if frame.kind == "foreach_iteration":
|
||||
loop_item = frame.metadata.get("loop_item")
|
||||
|
||||
@@ -79,6 +79,9 @@ def _resolve_node_execution(
|
||||
context = RuntimeContext(
|
||||
current_node_id=node.id,
|
||||
frame_id=frame.id,
|
||||
scope_id=frame.scope_id,
|
||||
lineage_id=frame.lineage_id,
|
||||
parent_lineage_id=frame.parent_lineage_id,
|
||||
prior_outcome=frame.prior_outcome,
|
||||
activated_incoming_edge=frame.activated_incoming_edge,
|
||||
metadata=dict(frame.metadata),
|
||||
@@ -127,6 +130,7 @@ def _finalize_node_execution(
|
||||
index=item_index,
|
||||
frame_id=frame.id,
|
||||
patch=patch,
|
||||
lineage_id=frame.lineage_id,
|
||||
)
|
||||
barrier.save_to_frame(parent_frame, foreach_node_id)
|
||||
state_changes = {}
|
||||
|
||||
Reference in New Issue
Block a user