execute_effect A; execute_effect B establishes the causal order complete(A) < start(B). Async execution relaxes that so multiple effects may run concurrently, but Conseqa keeps synchronization explicit through handles and join_all / race barriers. Async is an execution mode, not a new kind of effect.
Async execution is available for
publication, request, and external effects. It is not available for outbox_write (which is atomic with its transaction commit) and not available for transaction, block, branch, or terminal steps.Async launch steps
execute_effect_async
Launches an inline effect concurrently with the surrounding program.start(A) < start(B) between async A and any subsequent step B. It does not establish complete(A) < start(B). Any result binding — even for a result-bearing effect — is deferred to a synchronization barrier.
execute_effect_intent_async
Executes a previously established effect intent in async mode.execute_effect_intent, with the same handle rules as execute_effect_async.
Handles are semantic, not values
An async handle is an operation-local synchronization artifact. It has:- no schema
- no field path
- no
ValueSourcekind
values derivations. They exist only to name a pending completion for a later join_all or race step.
Synchronization barriers
join_all
Waits for every referenced completion. Each entry may optionallybind the result of a result-bearing effect.
- Establishes
complete(H_i) < continuationfor every referenced handle. - Establishes no relative order among the joined completions.
- Empty
handlesis invalid. - No short-circuit: a failing
Erron one joined effect does not cancel the others. - Result bindings become available to steps after the barrier through
effect_result_ok/effect_result_err.
race
Waits for the first completion among two or more handles.- At least two handles required.
- If
bindis present, every candidate must be result-bearing and expose the same logical result contract (sameokschema and sameerrcontract including disposition). - No cancellation of losing candidates; they continue to completion as separate work.
- The winning completion is not replay-stable: which candidate wins may differ across attempts. A race-bound result is treated as an obstacle in idempotency and result-replay proofs (see Requirements).
Terminal does not join
An operation reachingreturn or complete does not implicitly join outstanding handles. Fire-and-forget async is a legal, deliberate modeling pattern:
join_all before the terminal.
Program reachability
Reachability in the program still walks synchronous control. An unreachable synchronous step is unreachable even if an async handle from an earlier step is still pending:Validation rules
The additional validation rules that apply to async programs:- Every referenced handle in a
join_allorracemust be established by a preceding async launch on every path reaching the barrier. - A handle may be joined or raced at most once on any given path.
- A
bindonjoin_allorracerequires the underlying effect (or every candidate, forrace) to be result-bearing. - Async launches never bind results directly.
- Race binding produces a non-replay-stable value; downstream decisions that depend on it fail the control-leg check unless separately stabilized.
When to use async
Async execution is a modeling tool, not an optimization. Reach for it when the architecture genuinely runs work in parallel, when a race between primary and fallback is the semantic story, or when the operation needs to fire off best-effort side effects that must not block the terminal. Otherwise, synchronousexecute_effect is simpler to reason about and yields stronger replay-stability facts by default.
Program Control
Synchronous step kinds, terminals, and decision steps.
Effects
Publication, request, external, and outbox write effect contracts.
Requirements
How replay-stability affects idempotency and result replay verdicts.
Value References
ValueRef kinds, including effect_result_ok and effect_result_err.