8.5 KiB
Concurrent Foreach Phase 4 Roadmap
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Split concurrent foreach execution into safe, independently testable runtime slices.
Architecture: foreach(mode="concurrent") is the workflow mode. Sync runtime should interleave admitted item frames one node call at a time; async runtime may later run admitted async node handlers simultaneously. All item writes must flow through StatePatch and commit at a foreach barrier, not directly from child frames into shared state.
Tech Stack: Python 3.14, dataclasses, Pydantic v2, pytest, wf_core.runtime.scheduler, wf_core.runtime.foreach_state, wf_core.runtime.ops.state.
Current State
Already implemented:
ForeachNode.modeaccepts canonical"concurrent".ForeachConcurrentPolicyexists withmax_active,max_outstanding, andinterrupt="quiesce".- Legacy
mode="parallel"/parallel={...}parse into canonical concurrent shape. ForeachItemErrorPolicyexists withfail,skip, andcollect.completed_with_errorsis derived forskipandcollect.collect_tois validated as a declared array state field.StatePatch,build_output_patch(...), andcommit_state_patch(...)exist.ForeachBarrierState,PendingItemResult, andItemErrorRecordexist.- Serial foreach progress now uses
ForeachBarrierState. - Sync
foreach(mode="concurrent")runs with fail-only item policy, bounded admission, deterministic interleaving, item-local overlays, and barrier commits. - Multi-step concurrent item bodies are supported for fail-only item policy.
- Barrier write validation rejects ambiguous sibling writes: same-path sibling
writes require a
mergeablereducer, and ancestor/descendant sibling writes are rejected. - Concurrent
item_error.action="skip"and"collect"are supported. collectwrites ordered item error records tocollect_to, writes an empty list on clean success, and emitscompleted_with_errorsonly when failures were collected.- Async runtime batches ready concurrent-foreach item node handlers so handler
awaits may overlap up to admitted
max_activework. State finalization and traces still happen sequentially after handler results return.
Non-Goals For Phase 4
- Do not implement Fork/Gather graph nodes.
- Do not turn
JoinNodeinto a real barrier. - Do not run sync node handlers in threads or processes.
- Do not add OpenTelemetry.
- Do not add platform-level source/tool semaphores.
- Do not add persistent run storage.
Slice 1: Sync Concurrent Foreach, Fail-Only
Implement first because it proves the scheduler and frame admission model without async task orchestration or handled item failures.
Scope:
foreach(mode="concurrent", item_error.action="fail")runs in sync runtime.- Parent foreach admits up to
concurrent.max_activeitem frames. - Scheduler interleaves item frames one step at a time.
- Child output writes are buffered as per-item
StatePatchobjects. - Barrier commits all successful item patches only when every item succeeds.
- Any item runtime failure fails the whole run.
skipandcollectremain runtime-unsupported for concurrent mode.
Plan:
Slice 2: Item-Local Overlays
Implemented after Slice 1 because fail-only concurrent foreach needed lineage-local reads before multi-step item bodies could be supported. Overlays let later nodes in one item read earlier buffered writes from the same item without exposing those writes to siblings.
Scope:
state_view_for_frame(...)returns committed parent state plus current item overlay for concurrent foreach item frames.- Parent
RunState.stateremains unchanged until the barrier commits. - Item patches accumulate across multiple nodes in the same item lineage.
- Multi-step concurrent item bodies are supported.
- Sibling item overlays remain isolated.
- Sibling write conflict policy remains deferred to Slice 3.
Plan:
Slice 3: Barrier Commit Conflict Semantics
Implement after Slice 2 so conflict checks operate on real item-local overlays and multi-step item patches.
Scope:
- Detect sibling lineage writes to the same state path.
- If exactly one lineage writes a destination path, default replace is allowed.
- If multiple sibling lineages write the same destination path, a declared
mergeablereducer is required. - Ancestor/descendant writes across sibling lineages are conflicts unless an explicit future merge strategy covers them.
- Commit order is item index order, never completion order.
Files likely touched:
src/wf_core/runtime/foreach_state.pysrc/wf_core/runtime/ops/state.pysrc/wf_core/runtime/ops/foreach.pytests/core/test_concurrent_foreach.py
Key tests:
test_concurrent_foreach_rejects_sibling_writes_without_reducertest_concurrent_foreach_applies_reducer_in_item_index_ordertest_concurrent_foreach_rejects_ancestor_descendant_write_conflict
Plan:
Slice 4: Item Error Policies
Implemented after barrier success commits became correct.
Scope:
item_error.action="skip"continues after item runtime failures.item_error.action="collect"continues and writes structured errors tocollect_to.- Both emit
completed_with_errorsif at least one item failed. collectwrites an empty list and emitsdoneif all items succeed.- Failed item frames remain
FAILED; parent foreach decides whether the failure is handled.
Files likely touched:
src/wf_core/runtime/foreach_state.pysrc/wf_core/runtime/ops/foreach.pytests/core/test_concurrent_foreach_errors.py
Key tests:
test_concurrent_foreach_skip_emits_completed_with_errorstest_concurrent_foreach_collect_writes_ordered_error_recordstest_concurrent_foreach_collect_writes_empty_list_on_clean_success
Slice 5: Async Concurrent Foreach
Implemented after sync semantics stabilized.
Scope:
- Async runtime may have multiple async node handler calls in flight.
concurrent.max_activecaps admitted/running item work.- Sync handlers are still called normally; no thread/process executor.
- Trace remains append-only chronological execution history.
- Barrier commit order remains item index order.
Files likely touched:
src/wf_core/runtime/engine.pysrc/wf_core/runtime/step.pysrc/wf_core/runtime/ops/nodes.pysrc/wf_core/runtime/ops/foreach.pytests/core/test_concurrent_foreach_async.py
Key tests:
test_async_concurrent_foreach_respects_max_activetest_async_concurrent_foreach_commits_in_item_index_order
Slice 6: Interrupt Quiescence
Implemented after async execution exists.
Scope:
- If any concurrent item interrupts, the whole run pauses.
- No new item frames are admitted after the interrupt.
- Already-started async node calls drain to pending results.
- The caller gets control only at a quiescent point.
- Pending results do not commit until resume/commit policy allows it.
- Item frames that route into an
InterruptNodeare prioritized before the parent foreach can refill capacity. - Already-started async handler calls drain at the batch boundary; state finalization remains sequential.
Files likely touched:
src/wf_core/runtime/engine.pysrc/wf_core/runtime/preparation.pysrc/wf_core/runtime/ops/interrupts.pysrc/wf_core/runtime/ops/foreach.pytests/core/test_concurrent_foreach_interrupts.py
Key tests:
test_concurrent_foreach_interrupt_returns_before_refilltest_resume_prioritizes_interrupted_item_frame_before_siblings
Execution Order
- Sync concurrent foreach, fail-only.
- Item-local overlays for multi-step item bodies.
- Barrier conflict semantics.
skip/collectitem error policies.- Async concurrent foreach.
- Interrupt quiescence.
Self-Review
- Spec coverage: the roadmap covers scheduler admission, barrier commits, reducer conflicts, handled item failures, async handler execution, and interrupt quiescence.
- Placeholder scan: each slice has scope, likely files, and named tests; concrete code lives in the slice-specific plan.
- Type consistency: the roadmap uses canonical
concurrent,ForeachConcurrentPolicy,ForeachBarrierState,PendingItemResult, andStatePatch.