Execution Semantics
This section defines the logical execution of a Core workflow independently of interpretation, compilation, storage, workers, or scheduling architecture. It constrains outcomes and values that a portable workflow can observe; Core Executor conformance does not require a persistent run history.
Recording
Section titled “Recording”When this Standard requires information to be recorded, the responsible implementation MUST retain it in logical validation, resolution, or execution state for as long as any applicable operation can observe it. Recording alone does not require persistence across interruption, retention after termination, or replayability; applicable profiles impose those additional requirements.
Run Lifecycle
Section titled “Run Lifecycle”Starting a Run
Section titled “Starting a Run”A run MUST NOT start until the Workfile and every dependency needed for static validation have been resolved, the workflow has been validated against those exact dependencies, and the target deployment has been shown to support every structurally required capability and profile. Before evaluating the first step, the executor establishes the selected format and Standard revisions, resolved dependency identities, validated inputs, trigger data if any, immutable run metadata, and workflow identity when required by an applicable capability or profile.
Validation and execution MUST use the same resolved workflow and dependency surface. Caller, deployment, and trigger values MUST be validated before they become bindings.
Failure to resolve or validate is not a failed run because no run begins. Once a run begins, its inputs, trigger selection, and trigger payload MUST NOT change. Immutability of run metadata is defined under Identity and Metadata.
Step Evaluation
Section titled “Step Evaluation”The executor MUST evaluate a step list in declaration order and MUST NOT reorder its steps.
For each step the executor:
- applies dry-run propagation, then evaluates
when, if present, and skips the step if the guard is false; - evaluates the operation’s arguments and other expression-bearing fields in the enclosing scope;
- performs the construct or invokes the connector action;
- applies retry and applicable failure policy; and
- assigns the resulting step outcome and, on success for a value- or scope-producing body, creates its binding.
No operation begins if guard or argument evaluation faults. An action MUST receive the complete evaluated argument map, including an explicitly supplied null; evaluation MUST NOT send a partial argument set.
The action result is interpreted under its resolved connector manifest and the Connector Execution Contract. A successful binding is created atomically and is visible only to later steps in that scope and to scopes nested after its creation.
The single-assignment rule under Names and Bindings applies across all attempts of a step. Attempts are execution events, not additional steps or assignments.
Constructs can refine this sequence for branches, iterations, concurrency, waiting, or child runs. Those refinements MUST preserve the enclosing list’s ordering and scope rules.
Dry Runs
Section titled “Dry Runs”The invocation’s run.dry_run binding identifies a dry run; its type and value are defined under Identity and Metadata. Action declarations are defined under Effects and Dry Runs.
When run.dry_run is false, dry-run policy does not suppress work or invoke delegated dry-run behavior. After dependency checking in a dry run, an unaffected read action executes normally; an unaffected write is suppressed unless it declares dry_run: delegate.
For an unaffected write without dry_run: delegate in a dry run, the executor performs ordinary pre-dispatch evaluation and validation. If those succeed, it dispatches no action attempt, applies no retry or action-failure policy, and gives the step outcome suppressed.
A value-producing suppressed step creates no binding, and a permitted reference to it resolves to null under the outcome-dependent reference rule. If pre-dispatch evaluation or validation faults, the step fails normally and MUST NOT be recorded as skipped or suppressed.
Workflow execution continues after a suppressed step. Successful runs evaluate outputs using the output substitution below.
An unaffected delegated action executes normally with the dry-run flag in its invocation context and can produce its declared result. Its external-effect promise is defined under Effects and Dry Runs.
Dry run does not simulate time: wf.wait, wf.repeat delays, state timers, and other timer-based behavior retain their ordinary temporal semantics. It also does not consume a live external event for wf.wait_for or a state subscription.
Before starting a dry-run invocation whose reachable definition requires such a source, the executor MUST reject it with invocation.dry_run unless an activated profile supplies a deterministic simulated event source and defines its recording semantics.
Suppression Dependencies
Section titled “Suppression Dependencies”During a dry run, the executor MUST track suppression separately from ordinary null, including absent bindings of suppressed steps. This tracking is execution state, not a workflow value or a new static type.
A reference is affected when its selected value is suppressed or contains a suppressed descendant. Literal member access and literal indexing restrict selection to that path; selecting beneath a suppressed ancestor remains affected. A computed index conservatively selects every member at that level and also depends on the index expression.
Dependency checking includes every reference in the checked expression, including conditional branches, short-circuited operands, and filter arguments such as default. It uses the statically resolved references and current suppression state without evaluating the expression. Ordinary null, branch absence, and steps skipped by when do not themselves introduce suppression.
Before a step’s guard or body starts, the executor MUST check its enclosing-scope expressions for affected references. These include arguments, projections other than wf.stop.result, immediate modifier expressions, action-source arguments, and all wf.choose arm guards. Nested bodies, handlers, reconciliation, and undo are checked only when reached; the adjacent until of wf.repeat is checked after each successful iteration. Thus an unselected branch or unused failure handler does not suppress its enclosing construct.
An affected step becomes suppressed without evaluating those expressions, starting its body, or applying retry, failure, or recovery policy. This rule applies to every step kind, including reads, delegated writes, values, calls, renders, waits, and termination constructs. Descendant steps whose containing body never starts remain not_run.
An affected later field or result projection, other than wf.stop.result, suppresses its owning active step before that evaluation, without further retry, failure, or recovery policy. This includes a selected failure handler’s result after its steps finish. Any still-active nested work follows Internal Work Cancellation.
A suppressed nested step permits its step list to continue and does not itself fail or suppress the enclosing scope-producing construct. Retained scopes and aggregates preserve suppression at each affected member; unrelated members and failure summaries keep their ordinary values. An iteration with suppressed steps but no unhandled failure counts as succeeded under the construct’s ordinary aggregation rules.
For wf.render, dependency checking covers the supplied with projection; templates read only the resulting projected bindings under their ordinary rules. Literal template content and referenced template files are not enclosing-scope expressions.
Partial Outputs and Child Results
Section titled “Partial Outputs and Child Results”For each entry of root outputs or an executed wf.stop.result, the executor MUST check the entire expression-bearing value structure before evaluating any part of that entry. An affected entry produces null with suppression recorded for that result member, without evaluating its expressions. Unaffected entries evaluate normally, and a fault in any such entry still prevents a successful workflow result. These rules apply to result production for both completed and stopped runs; an affected stop-result entry does not suppress the stop itself.
A successful wf.call preserves each child’s output-member suppression in its result binding, including across further child-call boundaries. Partial outputs alone do not suppress or fail the call; a parent reference to an affected member follows Suppression Dependencies.
Suppression does not change inferred static types; the substitution applies only during dry-run execution and output production. Implementations MUST NOT reject partial dry-run outputs against ordinary non-null output types or pass affected values to an ordinary typed consumer. Validators still check every expression and dependency, including work that a dry run suppresses.
Suppressed Control Evaluations
Section titled “Suppressed Control Evaluations”When an iteration’s until is affected, wf.repeat becomes suppressed, creates no result binding, and starts no further iteration or delay. Previously completed work retains its outcomes for recording; suppression does not undo that work.
State subscription correlation, timer deadlines, transition subjects, and selected transition into projections are checked when their defining state rules require evaluation. An affected state control evaluation initiates an implicit dry-run stop with reason Dry-run dependency suppressed., without selecting another transition or entering another state. The executor applies Internal Work Cancellation, removes the entry’s subscriptions and timer, and evaluates outputs in the root scope. For that output evaluation, references to final-entry bindings are treated as suppressed; root bindings keep their existing suppression state. This implicit stop adds no static result shape and uses the normal-completion output contract with dry-run substitution. Successful output production gives stopped; an output fault instead fails the run under Outputs. An unaffected state entry and its transitions follow ordinary state semantics.
When an agent proposes a tool invocation that dry-run policy suppresses, the executor MUST stop that agent step with outcome suppressed. The executor applies Internal Work Cancellation to its active tool work and accepts no further tool proposal or agent result for that step. It MUST NOT deliver an invented tool result or run on_exhausted because of suppression. Independent workflow steps can continue under Suppression Dependencies.
Recording Suppression
Section titled “Recording Suppression”Recording and restoring a partial value MUST preserve its suppression information for every later operation that can observe that value. Durable resumption and Replay operations apply this rule to retained scopes, child results, and workflow outputs in the applicable recovery generation. A new recovery generation determines suppression from its own execution and restored inputs; it does not inherit superseded decisions merely because step paths match.
Successful Completion
Section titled “Successful Completion”A run completes normally after its root step list finishes without an unhandled failure or termination construct. The executor then evaluates outputs in the root scope. If output evaluation succeeds, the run reaches outcome completed and the projection is its result.
A Workfile with an empty root step list completes by the same rule. Normal completion without outputs produces an empty result.
An evaluation fault in outputs prevents successful completion and fails the run with that fault code. The successful early-termination path is defined under wf.stop.
Core Cancellation
Section titled “Core Cancellation”Cancellation is a deployment or executor decision, not Workfile syntax. A cancellation request can originate from the deployment, propagation from a cancelled parent run, cancellation of an active wf.call step, or another base rule that expressly requests cancellation of a run.
A request against an already terminal run has no effect. After the executor observes a cancellation request, cancellation MUST take effect no later than its next scheduling decision. It MUST take effect before that decision starts another step, action attempt, retry, timer, or child run.
Once cancellation takes effect, the run is subject to Scheduling Cutoffs.
The executor requests cancellation of in-flight work where supported and propagates cancellation requests to child runs started by the run.
An active step becomes cancelled, and a step that has not begun becomes not_run. Workflow outputs are not evaluated for the cancelled run, as defined under Outputs. The run reaches terminal outcome cancelled; it can carry an implementation-defined reason.
Once cancellation takes effect, later results from previously dispatched work are late results. Cancellation does not roll back an external effect or assert that a dispatched action did not take effect.
Cancellation after dispatch can leave that effect unknown; the cancellation source still determines the prescribed step and run outcomes.
Cancellation of a child alone does not cancel its parent. If the calling wf.call step remains active, child cancellation supplies a flow.child_cancelled failure and ordinary policy applies.
If the calling step has already been cancelled, any later child outcome is a late result, regardless of the child’s outcome.
Internal Work Cancellation
Section titled “Internal Work Cancellation”Internal Work Cancellation is invoked only by the composite-timeout, fail-fast, wf.stop, wf.fail, and dry-run control rules that expressly require it. It does not itself assign the enclosing run outcome.
When one of those rules invokes Internal Work Cancellation, an active interrupted step other than a construct on the active containment chain of a wf.stop becomes cancelled, a step that has not begun becomes not_run, and cancellation of an active wf.call step requests cancellation of its child. An enclosing construct on the active containment chain from a wf.stop step through the root step list becomes stopped as a step.
An enclosing construct that cannot complete because wf.fail terminated the run becomes cancelled as a step. Internal Work Cancellation does not replace the outcome prescribed by its source.
The source-specific outcome remains the one defined by the invoking rule. Failure policy above a failed construct continues to apply normally.
Executor Interruption
Section titled “Executor Interruption”Loss or interruption of an executor, worker, or process is not a cancellation request. Workfile Core conformance does not require persistence or recovery and therefore does not guarantee that an interrupted run reaches any terminal outcome.
An implementation MUST NOT report cancelled merely because the executor responsible for a run was interrupted.
Reaching a Terminal Outcome
Section titled “Reaching a Terminal Outcome”If a run becomes terminal, it reaches exactly one terminal outcome: completed, stopped, failed, or cancelled. A terminal outcome MUST NOT later change.
Waiting, durable suspension, an ambiguous hold, and recovery activity are nonterminal states and do not themselves constitute outcomes; this specification does not guarantee that external work eventually lets every run become terminal.
Normal completion produces completed. wf.stop and an affected state control evaluation under Dry Runs produce stopped. An unhandled step failure or wf.fail produces failed. Core Cancellation produces cancelled.
Terminal runs are subject to Scheduling Cutoffs; later external results are late results.
When a run terminates with one or more dispatched write effects unresolved, its terminal information MUST identify every affected action attempt known at termination, including attempts made uncertain by cancellation after dispatch.
For each it MUST preserve the stable step path, state effect: unknown, and preserve the connector ambiguity code and optional details when those exist. If the executor cannot establish whether dispatch completed, it MUST identify the attempt as potentially dispatched rather than omit it.
This information does not replace or change the step and run outcomes prescribed by the cancellation or termination source.
Scheduling Cutoffs
Section titled “Scheduling Cutoffs”Once run cancellation takes effect or the run becomes terminal, the executor MUST NOT start another step, action attempt, retry, timer, or child run for it.
After a run becomes terminal, the executor MUST NOT begin workflow result evaluation for it. Workflow result evaluation that is part of reaching completed or stopped precedes the terminal transition. Cancelled runs produce no workflow result under Outputs.
Late Results
Section titled “Late Results”An external result is late if its originating attempt already has a selected result, its owning step or enclosing construct has ended that work, cancellation of its run has taken effect, its run is terminal, or its originating work belongs to superseded recovery history. A delivery or completion for a subscription or timer that is no longer pending is also late.
The executor MUST NOT use a late result to create or change a binding, select control flow, replace an attempt result, or change a step, construct, or run outcome. It MUST retain information required by the applicable ambiguity, terminal-information, and history rules even when the result is late.
A timeout or ambiguity classification selects an attempt result; a subsequent response to that attempt is late even while a retry or ambiguity disposition is active. An eligible result from a later authorized attempt is assessed separately by its originating attempt and recovery generation.
Reconciliation and operator resolution establish an outcome through their own authorized decisions, distinct from accepting a late response as a replacement attempt result. Recovery rebuilds the selected work in a new generation, and continuation follows its boundary-reuse rules. A historical outcome alone does not exclude results from work authorized by those rules.
Outcomes
Section titled “Outcomes”Run Outcomes
Section titled “Run Outcomes”| Outcome | Meaning | Workflow result |
|---|---|---|
completed |
Root evaluation and outputs finished normally. |
Evaluated outputs. |
stopped |
An explicit stop or implicit dry-run state stop ended the run successfully. | Accepted stop result, or partial root outputs for an implicit dry-run state stop. |
failed |
wf.fail, an unhandled step failure, or output fault ended the run. |
None. |
cancelled |
Core Cancellation ended the run. | None. |
The reason supplied to wf.stop, or the code and reason supplied to wf.fail, is part of the corresponding terminal result. For another failure, the terminal failure identifies the step path and code that propagated to the run.
An unresolved action ambiguity additionally carries the information required by on_unknown. Run outcomes are distinct from nonterminal execution states.
A suspended or held run can later continue to any terminal outcome permitted by the applicable profile.
Step Outcomes
Section titled “Step Outcomes”Every logical step reaches exactly one of the following outcomes if its enclosing run progresses far enough to determine that outcome:
| Outcome | Meaning |
|---|---|
succeeded |
The body completed successfully. |
skipped |
A false when guard prevented execution. |
suppressed |
Dry-run policy withheld a write or prevented work dependent on a suppressed result. |
stopped |
The step is a construct on the active containment chain of a successful wf.stop. |
failed |
The body or its evaluation failed and no retry or handler recovered it. |
cancelled |
The step had begun when Core Cancellation or Internal Work Cancellation ended it. |
not_run |
The step never began because control flow did not select or reach it. |
As established under Step Bindings, success does not imply a readable binding for a void step. Conversely, an in-flight action attempt with an ambiguous result has not yet given its step an outcome while an applicable profile keeps the run held.
Nested constructs also receive outcomes as steps. Their internal steps retain their own outcomes; a construct outcome does not rewrite them.
Binding Failed or Unexecuted Steps
Section titled “Binding Failed or Unexecuted Steps”A statically valid reference to a value-producing step whose outcome is skipped, suppressed, stopped, failed, cancelled, or not_run resolves to null. Member access and indexing can continue through that null under the expression rules.
This rule does not make an invalid path valid. Path validity for void steps, action outputs, scopes, iterations, and branches is determined by the corresponding rules in their defining sections.
A failed or unexecuted step creates no successful value assignment. Its null resolution is outcome-dependent reference behavior, not a mutable binding or an inferred action result.
Failure and Retry
Section titled “Failure and Retry”A failure has a stable code and classification. Retry applies to individual attempts before the step reaches failed; step-level and enclosing policies apply only after retry is exhausted or prohibited.
The retry exclusions for evaluation faults and author-directed wf.fail are stated under Effective Retry Policy.
Code-Pattern Matching
Section titled “Code-Pattern Matching”For manifest error classifications and catch handlers, an exact code takes precedence over any matching pattern. The longest matching prefix takes precedence over a shorter one, and a prefix pattern takes precedence over an HTTP-status pattern. Pattern grammar and matching are defined under Names, Paths, and Codes. The accepted keys and the consequence of no match are defined separately under Errors and Classification and catch.
Error Classification
Section titled “Error Classification”Connector manifests classify action error codes as retryable, fatal, or unknown:
| Classification | Declared meaning |
|---|---|
retryable |
The requested success was definitely not produced and another attempt can be made. |
fatal |
The requested success was definitely not produced and another attempt MUST NOT be made. |
unknown |
The connector cannot establish whether the operation took effect. |
Code-pattern matching and the classification of an uncovered connector code are defined under Errors and Classification.
Connector Execution Contract defines how these declarations, action effects, transport outcomes, and reserved codes determine an attempt result.
An executor MUST NOT accept a connector-produced code as a Workfile evaluation or execution fault or apply a connector-supplied reclassification to such a reserved code.
A manifest error classification is distinct from an attempt result and a step outcome. Connector details associated with a reported code are typed json and do not alter its classification.
Human-readable error text supplied by the implementation is implementation-defined; a portable workflow cannot depend on its contents.
Effective Retry Policy
Section titled “Effective Retry Policy”The retry modifier defines the effective policy, its fields, and validation. The following rules govern its execution.
For retry number n, counting the first retry as 1, the computed delay is zero for none, initial for fixed, and initial * 2^(n - 1) for exponential.
When max is present, every selected retry delay is capped at max, whether the delay is computed or supplied by a server.
Arithmetic overflow in a computed delay yields max. When honor_retry_after is true and an attempt supplies a server-requested delay, that delay replaces the computed delay before the cap is applied. Otherwise the computed delay applies.
An executor MAY shorten a computed delay by applying jitter in the closed interval from zero through that delay; it MUST NOT exceed the applicable cap. Timing and the chosen jitter do not themselves change the step’s logical attempt count.
Only a retryable failure with an unused attempt can be retried under ordinary retry. A fatal failure, evaluation fault, cancelled attempt, successful attempt, or author-directed failure MUST NOT be retried.
An ambiguous result is governed by Core Ambiguity Disposition or Ambiguous Outcomes and on_unknown.
Core Ambiguity Disposition
Section titled “Core Ambiguity Disposition”When an action attempt is ambiguous under Connector Execution Contract, the executor first reissues it within the unused effective attempt count if the action declares idempotency: inherent or idempotency: keyed. The reissue uses the ordinary retry delay and the same fixed key.
Idempotency makes that reissue safe but does not add an attempt beyond the effective policy; permitting a reissue after the first ambiguous attempt therefore requires an effective attempts value of at least 2. For idempotency: none, or exhaustion of the attempt count, the executor applies the action step’s on_unknown policy.
on_unknown
Section titled “on_unknown”on_unknown is abort or fail in Core and defaults to abort. The additional policies are defined under Ambiguous Outcomes and on_unknown; wf.agent defines how the modifier applies to agent tools.
When a run terminates with flow.action_ambiguous, its terminal result records whether the unresolved attempt applied abort or fail.
Under abort, the action step and run fail directly with flow.action_ambiguous; on_fail, catch, on_iteration_fail or on_branch_fail, and recovery MUST NOT intercept that disposition. The initiating action and concurrently cancelled writes are covered by the unresolved-effect information under Reaching a Terminal Outcome.
The action creates no binding. Under fail, the action step becomes failed with flow.action_ambiguous, creates no binding, and follows ordinary step and enclosing failure policy, including on_fail, catch, on_iteration_fail or on_branch_fail, recovery, and run-failure propagation.
The unresolved attempt remains effect: unknown in step history and in the terminal information of any run that later terminates, with its connector ambiguity code and optional details, regardless of whether a later handler supplies a replacement result. Neither abort nor fail asserts that the external effect did not happen; Durable reconcile or halt is required to establish an outcome or hold for an operator decision.
The abort disposition propagates without becoming an ordinary definite failure. If a child run terminates under the abort disposition, its calling run terminates directly with flow.action_ambiguous.
If a child run instead fails ordinarily after on_unknown: fail, the wf.call step fails ordinarily with flow.action_ambiguous; its modifiers and enclosing failure policy apply, and the child’s unknown-effect attempts are carried into the calling run’s terminal information if that run later terminates. If an agent tool reaches the abort disposition, the enclosing run terminates directly with that code.
For that abort disposition, the wf.call or wf.agent step’s modifiers and any enclosing catch, on_iteration_fail or on_branch_fail, or recovery policy MUST NOT intercept it, and the originating action identity and ambiguity information MUST be preserved across the boundary.
A validator MUST warn when supplied Static Facts establish that an effects: write action with on_unknown: abort or an omitted on_unknown declares idempotency: none and appears under on_fail: continue or an enclosing on_iteration_fail: continue or on_branch_fail: continue in the same Workfile. The warning MUST explain that unresolved ambiguity bypasses the expressed continuation policy and terminates the run. The warning MUST identify on_unknown: fail as the remedy when ordinary failure policy is intended, explaining that it passes flow.action_ambiguous into catch, on_fail, and enclosing policy. It MUST also explain that choosing fail does not establish whether the external write occurred.
A validator SHOULD also warn in that context when inherent idempotency or keyed idempotency with a fixed key makes reissue safe but the effective policy has only one attempt; that warning SHOULD distinguish lack of retry authorization from lack of safe idempotency.
catch maps exact connector, author-selected, or Standard error codes, dotted-prefix patterns, and HTTP-status patterns to handlers. Precedence is defined under Code-Pattern Matching. Matching a code does not establish handler eligibility; the failure’s origin and disposition determine eligibility.
Each handler contains required steps. When the handled operation has an output contract, omitting a handler’s required result is invalid with document.control. Every wf.call and wf.agent has an object result contract, including when that object is empty. Each handler step has path <step-path>.(catch).<id>, with ordinary nesting and indexing continuing from that path.
Each handler’s nested scope contains the reserved error binding. error.step is the handled step’s path relative to the scope containing that step.
For an action, a handler runs only after retry is exhausted for a definite classified failure or after an on_unknown: fail disposition. Under on_unknown: fail, flow.action_ambiguous is the exact code offered to this map. Executor-produced connector codes timeout and invalid_output use the same matching rules.
For wf.call, eligible failures are a failed child’s terminal code, independent child cancellation (flow.child_cancelled), and the call’s own composite timeout (flow.timeout). A child’s terminal code is eligible even when it is an author-selected wf.fail code, a connector code, or a Standard fault or failure code. The handler’s error.details preserves any connector details carried by the child’s terminal failure. Faults in the handled step’s own evaluation, including guards and runtime input checks, are not eligible for its handler. This includes a runtime mismatch against a callee’s input contract before a child run starts.
For wf.agent, eligible failures are terminal agent.* failures and the agent step’s timeout (flow.timeout). A tool error returned to an active agent does not itself invoke catch; an agent terminating because a tool cannot be completed safely supplies agent.tool_failed. After budget exhaustion, on_exhausted, when present, runs before catch receives agent.budget_exhausted; run termination from on_exhausted prevents catch from running.
Cancellation, suspension, and holding are not failures offered to this map; the abort ambiguity disposition bypasses handlers under on_unknown, including across child and agent boundaries. For a call or agent timeout, handler work starts after internal cancellation and is outside the expired operation deadline, while remaining subject to enclosing deadlines and cancellation.
If its steps and result succeed, the handled step succeeds and binds the replacement result. The replacement result MUST be statically assignable to and satisfy the handled operation’s output contract at runtime: the action’s declared output, the callee’s joined successful result type, or the agent’s returns. For a callee’s joined object shape, replacement members MUST have declared names and assignable types; every member whose type excludes null is required. A handler does not widen that contract.
If the handler fails or faults, the original failure and code remain active and ordinary failure policy continues; handler-local outcomes do not replace that code. Explicit run termination and the abort ambiguity disposition within a handler retain their ordinary run-level effects.
Failure Policy Precedence
Section titled “Failure Policy Precedence”After retry and any applicable catch handler are exhausted, the executor applies the following policy to step failures, including construct failures, evaluation faults where their defining rules permit step policy, and an on_unknown: fail disposition. The abort ambiguity disposition bypasses this ladder as specified in its defining section.
on_fail: abortfails the run directly and bypasses enclosing failure policy.on_fail: continueleaves the stepfailed, binds it asnull, and continues with the next step.- The nearest enclosing
wf.for_eachorwf.parallelapplies its iteration or branch failure policy. - An applicable profile can apply the nearest enclosing recovery policy.
- Otherwise the run fails with the step’s code.
If a policy fails an enclosing construct, that construct becomes a failed step and the same precedence is applied at its enclosing level.
A construct failed by a nested failure carries the code and step path of the innermost unhandled failure that caused it. In any retained scope, error.step identifies that same step.
The isolated nested scopes of wf.for_each and wf.parallel are iterations and branches, respectively. Their key tables declare on_iteration_fail and on_branch_fail.
When failure policy propagates a failure to an isolated scope boundary, every intervening composite step becomes failed and later siblings in every affected step list are not_run. In a retained failed scope, error.step is the innermost unhandled failure’s step path relative to that iteration or branch scope.
Under fail_fast, the first failed scope fails the construct, prevents unstarted iterations or branches from starting, and cancels those in progress through Internal Work Cancellation. Under continue, the failed scope remains failed, other iterations or branches continue, and the construct succeeds after all finish. These rules apply when the precedence ladder reaches the iteration or branch failure policy; ambiguity interception remains governed by on_unknown.
on_fail is the literal policy abort or continue. When omitted, step failure propagates to the enclosing policy.
Sources of Nondeterminism
Section titled “Sources of Nondeterminism”An executor MUST NOT make an ambient value available to workflow evaluation unless this specification or an activated profile defines that value as an input.
The following can vary between otherwise similar runs and are not fixed by the Workfile language:
- connector action results, failures, ambiguous outcomes, and external effects;
- delivered trigger or awaited-event payloads and their arrival order;
- deployment-supplied inputs, connections, policy, and explicit cancellation;
- elapsed-time events, server-supplied retry delays, and the instant at which external work completes;
- scheduling of independent branches, iterations, workflows, and trigger queues where no ordering is declared; and
- outputs or decisions from profile-defined code, agents, recovery intervention, or other expressly nondeterministic mechanisms.
Once one of these sources becomes an input value, action result, classified error, or profile-recorded decision, the subsequent base evaluation is deterministic. External side effects themselves are governed by connector contracts and are not values merely because an executor can observe them.
Permitted Scheduling Variation
Section titled “Permitted Scheduling Variation”An executor can run independent wf.parallel branches and permitted wf.for_each iterations in any order or concurrently. It can delay a timer beyond its requested instant, apply permitted retry jitter, and choose any worker or internal queue.
Independent trigger runs can overlap unless the trigger declaration constrains them. Scheduling variation MUST preserve sequential step order, iteration indexes, stable filter ordering, queue ordering where declared, and every explicit join.
The executor MUST NOT expose a sibling scope before its construct joins or make an undeclared binding visible. With fail-fast behavior, scheduling can determine whether sibling work had already begun when failure occurs.
A sibling that began and was stopped is cancelled; one that had not begun is not_run. Both are permitted where the language establishes no start order.
Results arriving from work cancelled after it began are late results.