How a dispatch runs
- The executor loads the run definition and frozen plan from storage.
- It checks that the supplied graph matches the stored definition.
- It folds the run’s
graph:events into graph state. - The graph returns its next command.
- The executor records the whole dispatch decision before any node starts.
- It records
node-attempt-startedimmediately before each node starts. - It runs each node through the safe node-attempt lifecycle.
- It records
node-completed,node-paused, ornode-failed, then asks the graph again.
ENGINE_UNAVAILABLE as a normal node
failure so the graph can retry, fail, or recover in its usual way.
An action-policy wait records node-paused with its reason and request. A
denied action records node-failed with DENIED. An aborted attempt records
node-failed with ABORTED. These events stay distinct when the graph folds
the stream. If a result or pause request is too large for the event stream, the
executor records a small node-failed event with RESULT_TOO_LARGE so the
dispatch does not stay in flight.
If a process stops after node-attempt-started and the start record has
retrySafe: false, resume does not run node code. It records node-paused
with this request shape:
attemptId is the attempt identity from that start record. The pause reason is
that the previous process stopped after node code started, so the outcome is
uncertain.
An empty decision is valid only when an attempt is already in flight. The
executor rejects a second dispatch record for the same position.
Resume after a process stops
A fresh executor returnswaiting when a dispatch has no result. Call
resume with one exact position from that result:
resume only after the earlier executor process stops. The executor does
not lock the run between processes.
Resume rebuilds the dispatch input from its event prefix and the graph
decision. It does not add a second dispatch event.
- Node code did not start. If no start record exists, the executor runs
the attempt at its recorded position. It does not write
node-resumed. - A retry is safe. Set
retrySafe: truein the node binding. The start record saves this rule before node code starts. Resume recordsnode-resumedfor that in-flight position, then runs the same attempt again. - The result is not known. If the saved rule is false, resume records a
typed pause with a
reconcile-attemptrequest and does not call node code. - The node is paused. Resume records
node-resumedfor the exact position and offers the same attempt again.
node-resumed is recorded for a paused node and for an in-flight attempt that
already has a start record with retrySafe: true.
The saved retrySafe value controls crash recovery. A changed value in the
live binding does not change an earlier start record.
Two nodes in flight
A graph can dispatch more than one node at a time. If the process stops with two dispatches still open, a fresh executor returnswaiting with both
positions. resume takes one position. After that attempt settles, if the
sibling is still unfinished, the result is waiting again with the sibling’s
position:
createGraphExecutor also requires a
live Memory object. The executor passes that same object into the node attempt,
and a data-only node receives it in its context. The runtime check covers
TypeScript and plain JavaScript callers.
A data-only node can call another executor and return the child result. This is
how parent and child graphs compose without adding a second execution system.
Turn-taking example
The outside example alternates a writer and critic for three rounds. Each critic prompt includes that dispatch’s position summary. The example stores the run, starts the public executor, and uses a real dead-primary path through twoMockEngine instances. The first critic attempt records the primary as dead.
The next two critic attempts go straight to the fallback. The critic permits a
crash retry.