Workflow Runner¶
WorkflowRunner is the runtime that executes a workflow.
It takes a WorkflowDefinition, moves through its nodes, executes each step, updates state, and decides what happens next.
Everything starts with:
var runner = services.GetRequiredService<IWorkflowRunner>();
var result = await runner.RunAsync(workflow);
IWorkflowRunner is registered automatically by AddSpectra.
What the runner does¶
At a high level, the runner repeats the same cycle until the workflow finishes or pauses:
- choose the current node
- resolve inputs from workflow state
- execute the step
- handle the step result
- continue, pause, hand off, or stop
flowchart LR
A[Pick current node] --> B[Resolve inputs and context]
B --> C[Execute step]
C --> D[Handle StepResult]
D --> E{Continue?}
E -->|Yes| A
E -->|No| F[Stop or pause]
That is the core mental model.
Basic execution flow¶
When you call RunAsync, the runner starts from the entry node or from a resume point if execution is continuing from a checkpoint.
For each node, it:
- resolves template expressions like
{{inputs.task}} - injects extra context such as agent IDs, session messages, or handoff payloads
- builds a
StepContext - calls
step.ExecuteAsync(...) - applies outputs to workflow state
- evaluates edges to find the next node
- checkpoints if checkpointing is enabled
The runner keeps doing this until the workflow:
- completes
- fails
- pauses for continuation
- pauses for user input
- pauses for an interrupt
- hands off to another agent
Input resolution¶
Before a step runs, the runner prepares its inputs.
Template values¶
StateMapper resolves expressions such as:
using the current workflow state.
Injected context¶
The runner can also inject runtime values automatically:
| Injected value | When it is used |
|---|---|
agentId |
The node is bound to an agent |
| prompt references | The node defines prompt refs and no explicit prompt overrides them |
userMessage |
A suspended session is resumed with SendMessageAsync |
| handoff payload | Execution arrives from another agent |
__subgraphId |
The node executes a subgraph |
This is what lets the same step model work across normal nodes, agents, sessions, and subgraphs.
Step result handling¶
The runner does not decide what happened by inspecting the step's internals.
Instead, it reacts to the StepResult returned by the step.
| Status | What the runner does |
|---|---|
Succeeded |
Applies outputs, evaluates edges, continues |
Failed |
Records the error, checkpoints, stops |
NeedsContinuation |
Checkpoints, stops, resumes later at the same node |
Interrupted |
Checkpoints with pending interrupt, waits for ResumeWithResponseAsync |
AwaitingInput |
Checkpoints, waits for SendMessageAsync |
Handoff |
Resolves target agent node, injects handoff context, continues there |
This table is the key to understanding runner behavior.
Handoff routing¶
When a step returns a handoff result, the runner switches execution to the target agent node.
In practice, it:
- finds the node whose
AgentIdmatches the handoff target - stores the handoff payload in workflow state
- injects the transferred context into the target node
- continues execution from that node
If the source agent requires approval for handoff, the runner pauses with an interrupt before routing.
See Handoff Pattern for the full coordination model.
Cycle detection¶
The runner tracks how many times each node is visited.
This matters for workflows with loops.
- if a node is revisited unexpectedly, the runner can detect a possible infinite loop
- if the workflow intentionally contains cycles, the runner enforces
MaxNodeIterations
This prevents workflows from running forever because of bad routing or missing exit conditions.
Checkpointing during execution¶
If checkpointing is configured, the runner saves workflow state as execution progresses.
This allows it to:
- resume after pauses
- support sessions
- recover from interrupts
- time-travel from earlier checkpoints
- fork execution from a prior point
You do not need to manage this manually during normal execution. The runner does it as part of its lifecycle.
See Checkpointing for the storage model and resume behavior.
Runner methods¶
IWorkflowRunner supports more than just RunAsync.
| Method | Purpose | Details |
|---|---|---|
RunAsync |
Start execution from the beginning | This page |
ResumeAsync |
Continue from the latest checkpoint | Checkpointing |
ResumeFromCheckpointAsync |
Continue from a specific checkpoint | Time Travel |
ResumeWithResponseAsync |
Continue after an interrupt response | Interrupts |
ForkAndRunAsync |
Branch execution from a checkpoint with modified state | Time Travel |
SendMessageAsync |
Send a message into a suspended session | Sessions |
StreamAsync |
Execute while streaming events in real time | Streaming |
A simple way to think about them:
RunAsyncstartsResume*continuesSendMessageAsynctalks to sessionsForkAndRunAsyncexplores alternative branchesStreamAsyncgives live execution output
A practical mental model¶
The runner is the piece that turns a static workflow definition into a live execution.
- the workflow defines the graph
- the steps define the work
- the runner moves through the graph and manages execution state
If a step succeeds, the runner moves forward.
If a step pauses, the runner saves state and waits.
If a step hands off, the runner routes execution.
If a step fails, the runner stops.
That is the core behavior.
What's next?¶
- Checkpointing
Learn how workflow state is saved and restored.
- Interrupts
Pause execution for approval, human input, or external callbacks.
- Streaming
Consume workflow events while execution is happening.
- Time Travel
Resume or fork from earlier checkpoints.