This commit is contained in:
lda
2026-05-22 04:40:15 +07:00 Verified
parent 4fa0beab8c
commit 9cd064d6ef
7 changed files with 906 additions and 21 deletions
+172
View File
@@ -69,6 +69,14 @@ _Avoid_: Global committed state
A merge boundary that waits for multiple child or upstream frames and combines their lineage patches before continuation.
_Avoid_: Noop join
**Gather Node**:
A future graph step that exposes an explicit barrier for waiting on multiple branches and merging their results.
_Avoid_: Promise.all node, converge node
**Lineage Token**:
A future runtime marker for one branch lineage that can be consumed and merged by gather-style barriers.
_Avoid_: Edge id
**Run**:
One execution attempt of a workflow with input, state, frames, trace, and final output.
_Avoid_: Job, invocation
@@ -87,6 +95,170 @@ _Avoid_: Job, invocation
- Parallel foreach and native subgraphs depend on the **Scheduler Foundation**.
- Parallel foreach requires a **Foreach Policy** before public support is
enabled.
- Future parallel foreach should be bounded by default. `max_active` and
`max_outstanding` prevent one workflow from spawning unbounded MCP, HTTP,
browser, or external service calls.
- Future concurrency-specific foreach settings should live in a nested parallel
policy object rather than expanding `ForeachNode` with many top-level fields.
- Item error handling is foreach-wide, not parallel-only. Serial and parallel
foreach can both profit from `fail`, `skip`, or `collect` item failure policy.
- `collect` item error policy must declare an explicit destination for
structured item errors. Collected errors should be ordered by item index, not
async completion order.
- Item error policy handles runtime failures inside an item frame, such as
thrown handler errors, schema failures, runtime-error utility nodes, or
invalid graph execution. It does not intercept normal graph outcomes like
`error` when those outcomes are routed.
- A collected or skipped item failure should leave that item frame `FAILED`.
The foreach parent/barrier decides whether that failed child is handled and
whether the run may continue.
- `skip` item error policy writes no hidden state. Failed frame status and
trace/observability are the record; use `collect` when structured error state
is needed.
- `collect` writes structured item error records before emitting an aggregate
error outcome. Records include item index, frame id, failing node id, error
type/message, and the item value when it can be represented safely.
- `collect` destinations must be state array fields. The foreach barrier writes
the full ordered error list once, so users should not expect per-item append
writes. Authoring and MCP validation should surface this clearly.
- `completed_with_errors` is the canonical foreach aggregate outcome when all
items have finished but one or more item failures were handled by `collect` or
`skip`.
- `completed_with_errors` is barrier aggregate vocabulary, not foreach-only.
Future explicit barriers may emit it when handled branch failures occurred.
- Aggregate control-flow outcomes use result nouns/adjectives such as `done`,
`completed_with_errors`, and optionally `failed`. Policy actions use verbs
such as `fail`.
- If a foreach policy can emit `completed_with_errors`, validation should treat
it as a required routable outcome. Users may route it to the same target as
`done` explicitly.
- `collect` writes an empty list to its destination when all items succeed and
emits `done`. It emits `completed_with_errors` only when at least one item
failure was collected.
- `skip` emits `completed_with_errors` when one or more item failures were
skipped. It emits `done` only when all items succeed.
- Parallel `fail` item policy should stop scheduling new items, drain already
started jobs to a quiescent point, capture their results safely, and then fail
the run. It should not assume hard cancellation is safe.
- After `fail` trips, drained sibling results are for trace/observability and
cleanup only. They should not commit normal state progress after the failure
boundary.
- Foreach modes that continue after item failure, such as `collect` and `skip`,
should buffer item state patches until the foreach barrier completes. Future
parallel foreach should always use barrier-buffered commits.
- Serial `fail` may keep immediate commits for compatibility. Commit strategy
should be extracted into runtime helpers instead of being smeared through
foreach execution code.
- Barrier-buffered commits should store pending state patches/results, not full
state snapshots. Patch creation and commit must extract/reuse the existing
node output validation, output binding, and reducer logic rather than creating
a second write system.
- Foreach barrier commits should merge item results in item index order, not
completion order. Reducer behavior such as list appends should therefore be
deterministic.
- Successful item results may exist as pending barrier results before commit,
but they are not visible as workflow state until the barrier commits.
- Pending barrier results must live in resumable `RunState`/frame metadata, not
only in trace. Trace records history; runtime state is what resume/checkpoint
uses.
- Parallel item frames need lineage-local pending state: later nodes in the same
item lineage can read earlier pending patches from that item, while sibling
items cannot. The exact aggregate output API for committing item results to
parent state is deferred.
- Lineage-local state should be represented as patch overlays over parent-visible
state, not deep-copied full state snapshots.
- Patch overlays conceptually belong to lineage tokens, not execution frames.
A scoped foreach implementation may start by storing them in item/parent
metadata, but future Fork/Gather needs first-class lineage ownership.
- `RunState.state` remains committed parent/global state. Future frame execution
should resolve reads against a frame-specific visible state view built from
committed state plus visible lineage overlays.
- At a barrier, missing reducer means default replace only for single-writer
paths. Multiple sibling lineages writing the same path require an explicit
reducer; otherwise barrier commit raises a runtime error with writer details.
- Barrier conflict detection should use the same write-overlap rules as normal
state writes. Ancestor/descendant writes from different lineages are conflicts
unless an explicit merge strategy covers them.
- Reducers at barriers apply incrementally in deterministic lineage order by
default: item index order for foreach, declared branch token order for future
gather. Completion-order merging is a possible explicit barrier policy, not
reducer behavior.
- Collected error records follow the same barrier merge order policy. The
default is deterministic lineage order.
- Trace `state_changes` should mean committed state changes only. Do not encode
pending barrier patches there unless a future typed trace field is added.
- Foreach may keep implicit barrier/iteration state on the foreach parent frame
because it owns item spawning and refill. General branch convergence should
become an explicit **Gather Node** later. Shared barrier merge/result helpers
should prevent foreach and future barriers from duplicating patch ordering,
conflict checks, and failure aggregation.
- Future Fork/Gather needs **Lineage Tokens**, not raw edge ids. A fork produces
branch lineage tokens; a gather consumes a declared set of tokens and produces
a new merged token. This supports partial gathers such as merging branches
`a+b` before later merging with `c`.
- Explicit Fork/Gather is deferred until lineage tokens are designed. Parallel
foreach remains the nearer target because its implicit lineage tokens are item
indexes owned by one foreach activation.
- Future foreach metadata should evolve into inherited structured lineage
context. Alias lookup such as `context.document` can remain authoring sugar,
but runtime metadata should preserve nested foreach lineage without flat key
collisions.
- Nested active foreach aliases should not shadow each other. Alias collisions
in inherited context scope should be validation errors.
- Core runtime context should be structural, not alias-first. Foreach lineage
should be addressable through paths such as `context.foreach.<id>.index`;
`wf_authoring` can provide ergonomic alias helpers such as
`context_path(foreach_ref("docs").index)`.
- Python `RuntimeContext` should eventually expose typed structured foreach
context, such as `ctx.foreach["docs"].index`, while serialized frame metadata
remains JSON-compatible dictionaries.
- Structured foreach context keys should be foreach node ids. The `as_` alias is
authoring sugar for current item access, not the canonical runtime key.
- `wf_authoring.WorkflowBuilder.foreach(...)` should eventually return a richer
ref exposing context selectors such as `.item` and `.index`, so users do not
hand-write `context.foreach.<id>...` paths.
- Normal node authors should receive foreach values through mapped input.
Inspecting `RuntimeContext.foreach` is an advanced escape hatch for nodes that
genuinely need index/frame/lineage context.
- `as_` remains useful ergonomic sugar and human-readable trace/docs context,
but structured foreach refs should be preferred for non-trivial authoring.
- Future foreach policy shape should validate cross-field rules: `collect`
requires `collect_to`, non-collect actions forbid `collect_to`,
`mode="parallel"` requires a parallel policy, and `mode="serial"` forbids a
parallel policy. Deprecated top-level `on_item_error` may parse into the
nested item error policy, but canonical dumps should use the nested shape.
- `ForeachParallelPolicy` should split limits into `max_active` and
`max_outstanding`. Defaults are `max_active=4` and `max_outstanding=20`;
validation requires `max_outstanding >= max_active`. Ready or running item
frames consume active capacity; blocked item frames consume outstanding
capacity but not active capacity.
- A blocked non-interrupt item frame frees active capacity and may let foreach
start another item when `max_outstanding` also has room. A run-level interrupt
still stops scheduling.
- The foreach parent frame owns refill decisions. Scheduler wakes/schedules
frames; foreach-specific policy decides whether to start more children,
finish, or fail.
- When an item frame becomes blocked, it frees active capacity only for its
nearest foreach capacity owner. Future item metadata should identify that
owner rather than waking arbitrary ancestors.
- Foreach capacity is local correctness policy, not total process protection.
Future runtime should also have basic global run limits in `wf_core`; source-
or tool-specific limits belong in the platform layer.
- A future global `wf_core` runtime limit should count active node handler calls,
not all active frames. Control-flow frames are scheduler work; node calls are
the expensive external/user-code execution boundary.
- Global node-call limits do not replace foreach caps. Foreach caps bound local
scheduling fairness, outstanding frame count, memory, and pending results;
global node-call limits bound expensive handler execution across the run.
- Platform source/tool/account limits should be enforced at the node-handler
boundary, before invoking the external call. They layer on top of core global
node-call admission instead of replacing it.
- Node calls waiting on platform source/tool/account limits still count as
active node calls. Core global node-call admission should happen before
platform-specific semaphore acquisition.
- Waiting on a source/tool/account semaphore keeps the frame `RUNNING`; it is
backpressure inside the admitted node-call boundary, not workflow-level
`BLOCKED` state that frees foreach active capacity.
- An **Interrupt** pauses the whole **Run**, even if the interrupted frame is a
child of future parallel work.
- A **Runtime Failure** is distinct from a node returning an `error` outcome.