A task was claimed by an instance and is about to run.
A task finished successfully.
A task failed. ErrorMessage says why.
A task was blocked because a prerequisite failed or became unreachable.
A task was NOT TAKEN because another branch of an exclusive fan-out won.
Distinct from TaskBlocked on purpose: blocked means something went wrong upstream and the
viewer should look for a cause; skipped means the workflow chose a different route and there
is nothing to investigate. Rendering them the same would send people hunting for bugs that do
not exist.
A human task became actionable and is waiting on its assignee.
Every node has reached a terminal state; Status is the graph's rolled-up outcome.
A gating edge's verdict changed — satisfied, not taken, or held.
Emitted on CHANGE, not on every poll (the dispatcher re-resolves edges each pass, and an
unqualified emission would repeat every few seconds for as long as the graph is live, exactly
the flood logUnevaluableConditionOnce exists to prevent in the log). This frame is what lets
a viewer answer "why did this branch run?" — and what makes a permanent hold visible the
moment it happens instead of after a forensic query.
A task's claim changed hands or state — claimed, heartbeat lost, reclaimed.
The R2-1 defect class ("a crashed worker's task wedges the graph forever with zero diagnostics") becomes a red badge instead of an invisible fact precisely because this frame exists.
One dispatch pass finished for this graph — the engine's heartbeat.
Carries the pass's counts (eligible / held / claimed / in flight). A run that is stuck is a strip of these ticking with nothing moving, which is the honest visual of a stall.
The graph was paused — by a person or by a breakpoint. Nothing new will be claimed.
The graph was resumed; claiming continues normally.
An eligible task carried a breakpoint, so the graph paused BEFORE the task was claimed.
TaskID names the task the run is now parked in front of.
A step allowance could not release anything on this pass — the named work is already running,
or this host has no runner for it. The allowance is NOT spent, and Reason says so.
Exists because the alternative is the worst shape a control can have: the button reports nothing, the allowance is gone, and the run does not move.
A runner reported progress inside one task — message and optional percentage.
The bridge from node-level execution (an agent run's own progress stream) up to graph-level observers, rate-limited by the dispatcher so a chatty runner cannot flood the topic.
What happened to a task or a graph, as a closed set a consumer can branch on.