Run lifecycle & failure semantics¶
The authoritative operator reference for what each run status means, how runs move between them, what happens when a step fails, times out, is cancelled, or pauses for a human — and which statuses retention prunes. The 0.9.0 cut made the state machine honest under failure; this page is the contract those fixes implement, and the surface the contract-stability policy binds to for run semantics.
Related deep-dives: human_in_the_loop.md (the pause/resume walkthrough), service_api.md (routes and error envelopes), troubleshooting.md.
Status reference¶
| Status | Terminal? | Meaning |
|---|---|---|
created |
no | Run row persisted; not yet executing. |
queued (queue record) |
no | Waiting for a worker (async mode). The run row itself stays created/running; queued is the queue record's status. |
running |
no | A worker (or the synchronous caller) is executing it under a lease. |
blocked |
no | Paused at a human step; a WorkflowContinuation holds the resume context. |
paused |
no | Reserved/legacy pause marker; human pauses use blocked. |
completed |
yes | Finished successfully. |
failed |
yes | A step or the runtime raised past its error policy. Eligible for /continue (sequential workflows). |
cancelled |
yes | An operator cancel landed (cooperatively honored mid-run). |
timed_out |
yes | The run exceeded its timeout_seconds deadline. |
expired |
yes | A blocked human task hit its timeout with on_timeout: fail. |
Terminal statuses are final: every write path fences on the canonical
terminal set, so a late worker result, a resume, or a cancel can never
overwrite one (409 run_state_conflict where a client asks for it).
stateDiagram-v2
[*] --> created
created --> running : execute / worker claim
created --> cancelled : cancel before start
running --> completed
running --> failed : error past on_error policy
running --> cancelled : cancel honored
running --> timed_out : deadline exceeded
running --> blocked : human step reached
blocked --> running : resume with decision /<br/>on_timeout continue
blocked --> expired : task timeout, on_timeout fail
blocked --> cancelled : cancel (continuation deleted)
blocked --> timed_out : run deadline exceeded<br/>(continuation deleted)
failed --> running : POST /runs/{id}/continue
completed --> [*]
failed --> [*]
cancelled --> [*]
timed_out --> [*]
expired --> [*]
Step failure: on_error semantics¶
Each workflow step/node declares on_error (default fail). The policy
applies after the step's retry budget (max_retries) is exhausted:
fail— the run fails; the error lands instate.errorand therun_failedcheckpoint (with the collected trace).retry— the step retries up tomax_retriesbeforefailsemantics apply.continue— the failed step is skipped: its input passes through unchanged to the next step, the failure is recorded in the step's checkpoint metadata (and astep_failedcheckpoint on graph paths), and the run proceeds. In graph workflows, edge conditions see the real outcome — astatus_is: failedcondition routes the failure branch.fallback— the referenced step/node (fallback_ref) runs in place of the failed one, under the fallback's own retry/timeout policy but never itson_errorpolicy (fallback chains cannot recurse); traversal then continues. Note: a sequentialfallback_refis another step of the same workflow, so it also runs at its own chain position.
Cancellation and human-intervention pauses are control flow, never step
failures — no on_error policy can swallow them. One unsupported
corner: a fallback_ref pointing at a human step cannot pause — keep
human steps in the main chain. /specs/validate rejects that shape
(E_STEP_FALLBACK_HUMAN), along with a fallback_ref to a disabled
graph node (E_GRAPH_NODE_FALLBACK_DISABLED).
Timeouts, cancellation, pause/resume¶
- Run timeout (
timeout_seconds): the sweep marks the runtimed_out, signals any still-executing worker to stop cooperatively, and deletes a blocked run's continuation. - Cancel (
POST /runs/{id}/cancel): cooperative mid-run; on a blocked run it also deletes the continuation (the task disappears fromGET /human-tasks). Cancelling an already-terminal run is a no-op that returns the unchanged state. - Pause/resume: a human step persists a continuation and the run goes
blocked. Resume re-enters under the continuation lock and refuses any run that is no longerblockedwith409 run_state_conflict— a cancel that raced the reviewer wins. The resumed leg runs under the run's cancellation token, and the full pre-pause trace survives onGET /runs/{id}/trace. /continue(crash/failure recovery, sequential only): re-enters afailedrun from its last checkpoint. Terminal-but-not-failed runs are refused, as arerunningruns whose worker lease is still live (double-execution guard); an expired lease admits the legitimate crash-recovery case.
Async run creation returns 202 Accepted¶
POST /v1/runs with async_mode=true returns 202 Accepted: the run is
dispatched to the worker queue and the caller polls GET /runs/{id} for
progress. Synchronous creates (async_mode=false) return 200. POST
/v1/specs/run shares the same contract as of 0.12.0 — async_mode=true
returns 202 + a queued run_id to poll, async_mode=false runs inline at
200.
0.11.0 breaking change: through 0.10.x this returned 200; the 200 → 202
move was held for the contract-stability boundary and shipped in 0.11.0 (see
contract_stability.md and
upgrade.md § 0.11.0). The response body shape is unchanged — only
the status code. (The run row progresses created → running → terminal;
queued is the queue record's status, not the run's — see the status table
above.)
What retention prunes¶
Each knob is days-based; None/unset means "never prune" (see
deployment.md for configuration):
| Knob | Prunes | Notes |
|---|---|---|
runs_days |
Terminal runs | Default statuses: completed, failed, cancelled, timed_out, expired; override via KNEO_SERV_RETENTION_RUN_STATUSES. |
checkpoints_days |
Checkpoint rows | By created_at, except checkpoints of live runs (running/blocked/created/paused) — those are retained regardless of age so an aged pause can still resume. |
queue_days |
Finished queue records | completed/failed queue statuses. |
continuations_days |
Stale continuations | Continuations of still-blocked runs are protected — age alone never bricks a pending human task. |
audit_days |
Audit events | By created_at. |
idempotency_days |
Idempotency records | By updated_at (a re-upserted key refreshes its lifetime). |
artifacts_days / logs_days |
Files under KNEO_SERV_ARTIFACT_PATH / KNEO_SERV_LOG_PATH |
By mtime. |
Error taxonomy quick reference¶
| HTTP | error |
When |
|---|---|---|
| 400 | spec_invalid / invalid_request |
Spec fails compile; malformed input. |
| 403 | environment_policy_blocked |
The environment's stored policy blocks the deployment (diagnostics included). |
| 404 | not_found |
Unknown run/continuation id. |
| 409 | run_state_conflict |
Resume/continue against a run whose status forbids it. |
| 409 | resource_locked / idempotency_key_conflict |
Concurrent operation holds the lock; same key, different payload. |
| 413 | payload too large | Body over the cap — including chunked uploads. |
| 422 | guardrail_violation |
A guardrail blocked the content (violation type included). |
| 422 | spec_path_confined |
A caller-supplied spec_path / overlay / skills[].source resolved outside the spec root (KNEO_SERV_SPEC_ROOT, or the working directory by default). Default-on as of 1.0.0. |
| 422 | (native envelope) | Request-shape validation errors. |
| 503 | queue_full |
Backpressure: retry after the queue drains (Retry-After). |
| 503 | store_unavailable |
The persistence backend is unreachable; transient (Retry-After). |
Full envelopes and examples: service_api.md.