fix: inherit ancestor writes in nested foreach

This commit is contained in:
lda
2026-09-04 18:32:36 +07:00 Verified
parent 7b2f718ad7
commit f28e1cc6b4
2 changed files with 121 additions and 5 deletions
+6 -5
View File
@@ -43,14 +43,15 @@ class LineageStateView:
def lineage_writes_for_frame( def lineage_writes_for_frame(
run: RunState, frame: ExecutionFrame run: RunState, frame: ExecutionFrame
) -> Sequence[StateWrite]: ) -> Sequence[StateWrite]:
"""Return writes visible to this frame's current lineage. """Return ancestor and current-lineage writes visible to this frame.
This is still backed by concurrent foreach barrier metadata. Keeping the An empty child lineage still inherits writes buffered by its ancestors, as
lookup here gives future `RunState.lineages` or subgraph scopes one place to happens when an outer concurrent foreach writes before entering an inner
plug in without making node execution understand foreach internals. foreach. Lineage existence and scope therefore control traversal; the
current lineage having its own writes does not.
""" """
lineage = run.lineages.get(frame.lineage_id) lineage = run.lineages.get(frame.lineage_id)
if lineage is not None and lineage.scope_id == frame.scope_id and lineage.writes: if lineage is not None and lineage.scope_id == frame.scope_id:
return tuple( return tuple(
lineage_state_writes( lineage_state_writes(
run, scope_id=frame.scope_id, lineage_id=frame.lineage_id run, scope_id=frame.scope_id, lineage_id=frame.lineage_id
+115
View File
@@ -539,6 +539,121 @@ def test_nested_foreach_preserves_inner_writes_in_all_modes(
assert sorted(seen, key=repr) == sorted([1, 2, 1, 2], key=repr) assert sorted(seen, key=repr) == sorted([1, 2, 1, 2], key=repr)
@pytest.mark.parametrize("inner_mode", ["serial", "concurrent"])
def test_nested_item_reads_buffered_ancestor_state(inner_mode: str) -> None:
"""An inner item inherits the enclosing concurrent item's state view."""
inner_payload: dict[str, Any] = {
"id": "inner",
"type": "foreach",
"over": "state.inner_items",
"as": "inner_item",
"mode": inner_mode,
}
if inner_mode == "concurrent":
inner_payload["concurrent"] = {"max_active": 1, "max_outstanding": 1}
workflow = Workflow(
name="nested_foreach_reads_ancestor_state",
input_schema=SchemaRef(type="object", properties={}),
state_schema=StateSchema.from_field_map(
{
"items": StateField(type="array"),
"inner_items": StateField(type="array"),
"marker": StateField(type="string", default="root"),
"seen": StateField(
type="array", reducer=ReducerRef(name="wf.std.append")
),
}
),
output_schema=SchemaRef(type="object", properties={"seen": {"type": "array"}}),
node_defs=[
NodeDef(
name="write_marker",
input_schema=SchemaRef(
type="object", properties={"marker": {}}, required=["marker"]
),
output_schema=SchemaRef(
type="object", properties={"marker": {}}, required=["marker"]
),
outcomes=["ok"],
),
NodeDef(
name="observe_marker",
input_schema=SchemaRef(
type="object", properties={"marker": {}}, required=["marker"]
),
output_schema=SchemaRef(
type="object", properties={"seen": {}}, required=["seen"]
),
outcomes=["ok"],
),
],
start="outer",
nodes=[
ForeachNode.model_validate(
{
"id": "outer",
"type": "foreach",
"over": "state.items",
"as": "outer_item",
"mode": "concurrent",
"concurrent": {"max_active": 1, "max_outstanding": 1},
}
),
NodeUse.model_validate(
{
"id": "write_outer",
"type": "node",
"node": "write_marker",
"input": [{"target": "marker", "path": "context.outer_item"}],
"output": [{"source": "marker", "target": "state.marker"}],
}
),
ForeachNode.model_validate(inner_payload),
NodeUse.model_validate(
{
"id": "read_inner",
"type": "node",
"node": "observe_marker",
"input": [{"target": "marker", "path": "state.marker"}],
"output": [{"source": "seen", "target": "state.seen"}],
}
),
],
edges=[
Edge.model_validate(
{"from": "outer", "outcome": "loop", "to": "write_outer"}
),
Edge.model_validate(
{"from": "write_outer", "outcome": "ok", "to": "inner"}
),
Edge.model_validate(
{"from": "inner", "outcome": "loop", "to": "read_inner"}
),
Edge.model_validate({"from": "read_inner", "outcome": "ok", "to": "inner"}),
Edge.model_validate({"from": "inner", "outcome": "done", "to": "outer"}),
Edge.model_validate({"from": "outer", "outcome": "done", "to": END}),
],
)
run = execute_workflow(
workflow,
{"items": ["outer"], "inner_items": [1]},
{
"write_marker": lambda payload, _ctx: {
"outcome": "ok",
"output": {"marker": payload["marker"]},
},
"observe_marker": lambda payload, _ctx: {
"outcome": "ok",
"output": {"seen": payload["marker"]},
},
},
)
assert run.status == RunStatus.COMPLETED
assert run.state["seen"] == ["outer"]
def _three_level_workflow( def _three_level_workflow(
*, outer_mode: str, middle_mode: str, inner_mode: str *, outer_mode: str, middle_mode: str, inner_mode: str
) -> Workflow: ) -> Workflow: