RunStateView,
computed server-side from persisted state plus liveness signals.
Surfaces never infer status from ps, event absence, or partial table
reads. They call computeRunState.
RunState
idle is gone. When a signal is missing, the state is unknown —
never succeeded, never running.
The legacy run-row status column maps to RunState like this:
_smithers_runs.status | RunState |
|---|---|
running (fresh) | running |
running (stale) | stale or orphaned |
waiting-approval | waiting-approval |
waiting-event | waiting-event |
waiting-timer | waiting-timer |
finished | succeeded |
continued | succeeded |
failed | failed |
cancelled | cancelled |
| missing / unknown text | unknown |
recovering is reserved for the supervisor takeover window; it is not
emitted today (the supervisor will set it once
ticket 0018 lands).
ReasonBlocked / ReasonUnhealthy
Every non-terminal, non-running state carries a typed reason.
RunStateView
blocked is set when state is one of the waiting-* values.
unhealthy is set when state is stale, orphaned, or recovering.
Terminal states (succeeded, failed, cancelled) carry neither.
computeRunState
computeRunState is pure over the DB plus the heartbeat / lease signals
on the run row. It does not call ps, does not probe sockets, and does
not run heuristics.
deriveRunState is the underlying pure function — useful in tests or
when you already have the rows in memory:
staleThresholdMs is 30_000 — the same threshold the
engine uses for isRunHeartbeatFresh.
Where it shows up
RunStateView is the wire format on every read surface:
smithers inspect <runId>— top-levelrunStatefield on the JSON output (and rendered in the human view).- Gateway RPC
runs.get—runStatefield on the response. - DevTools snapshot header —
runState?: RunStateViewfield. - Event stream —
RunStateChangedevent withbeforeandafter(emitted by the recovery state machine in ticket 0018).
RunState — it’s an error
(RUN_NOT_FOUND). unknown is for ambiguity, not for “doesn’t exist.”