CLI reference: runs and workers
Every command here reads the store from --store URL (default PYRULA_STORE_URL; --namespace
for Valkey, and --cluster to override the cluster detection the store does on its own). A
store a command cannot open or reach gets the same text pyrula dev --check prints.
On a Valkey store with a Postgres archive, pass --postgres-dsn (default
PYRULA_POSTGRES_DSN), the same as for pyrula worker. Without it a run whose live copy has
expired is not listed and show, retry, cancel and terminate answer “run not found”. With
it, list, show and the run-id lookup read the archive too, and retry of an archived failed
run works. The flag is refused (exit 2) on a store that has no archive.
Exit codes of pyrula runs
Section titled “Exit codes of pyrula runs”| Code | Meaning |
|---|---|
| 0 | Done. An empty list is 0. |
| 1 | No such run, or a refusal such as a run that cannot be retried. |
| 2 | Usage error: a missing --store, a malformed URL, a flag that does not apply, or a run id that exists under more than one workflow and no --workflow. |
| 3 | The store cannot be opened or reached, or the connection was lost while the command ran. |
Naming a run
Section titled “Naming a run”show, retry, cancel and terminate take the run id and look up its workflow in the
store. If the same id exists under two workflows, the command lists them and exits 2; pass
--workflow NAME to pick one. The old form, pyrula runs show NAME RUN_ID, is refused with exit 2
and a line showing the new one.
pyrula runs show RUN_ID [--workflow NAME]pyrula runs retry RUN_ID [--workflow NAME] [--dry-run]pyrula runs cancel RUN_ID [--workflow NAME] [--reason TEXT]pyrula runs terminate RUN_ID [--workflow NAME] [--reason TEXT] [--yes]pyrula runs list
Section titled “pyrula runs list”pyrula runs list [--workflow NAME] [--status STATUS[,STATUS]]... [--since DURATION] [--limit N] [--json]Runs newest first, with run id, workflow, status, start time (UTC) and duration. Without
--workflow it lists every workflow in the store.
| Flag | Meaning |
|---|---|
--workflow NAME | Only this workflow. |
--status STATUS | Only runs in this status. Repeat the flag or comma-separate for several (--status error,terminated). |
--since DURATION | Only runs started within this long: a number and one of s, m, h, d, w (30m, 1h, 7d). |
--limit N | At most N runs (default 20). |
--json | Print a JSON array instead of the table. |
A rare --status or an old --since makes the command read further back in the store before it
fills --limit. A --status filter with no --since stops after 10,000 runs, counted across all workflows
together and walked in name order (with several workflows a rare status can be missed in the later
ones: add --workflow or --since), and says so on
stderr (stopped after N runs; narrow with --since or --workflow); the exit code is still 0.
A run that completed shows its duration. A run still going shows its age so far with a trailing
… (3m12s…) so it is not read as a final figure; in JSON duration_ms stays null until the
run has ended. A run that ended without completing shows -.
With --json each run is an object with these keys. Times are ISO-8601 UTC with
milliseconds, and a value that does not exist is null.
[ { "run_id": "r-7f3a", "workflow": "process_order", "status": "complete", "started_at": "2026-10-29T12:00:00.000Z", "duration_ms": 1500 }]An empty result is an empty table (header only) or [], with exit 0.
pyrula runs show
Section titled “pyrula runs show”pyrula runs show RUN_ID [--workflow NAME] [--journal] [--json]One run. The output has the status, attempt, version, trigger, parent, start time and
duration, what the run waits on (a signal name, an interrupt, a sleep deadline or its start time),
the start time it was scheduled with, its params, and
its result or error. --journal adds the steps with their outcomes (pending, done or error).
Params, results and error text go through the dev UI’s redaction and size cap. Unlike
pyrula dev --attach, which shows tracebacks, this command never prints one: an error is its
code and a redacted, capped message. The command reads the store directly, so capture settings
(PYRULA_CLOUD_CAPTURE, @workflow(capture=...)) do not apply; whatever the run stored is
shown after redaction.
With --json the output is one object:
{ "workflow": "process_order", "run_id": "r-7f3a", "status": "signal_waiting", "attempt": 1, "version": "v3", "version_behavior": "pinned", "trigger": "manual", "parent": null, "started_at": "2026-10-29T12:00:00.000Z", "scheduled_start": null, "duration_ms": null, "pending": {"kind": "signal", "name": "approve", "wait_id": "w1", "deadline": null}, "params": {"sku": "a"}, "result": null, "error": null, "journal": null}parent is {"workflow", "run_id"} for a child run. pending is null unless the run waits; its
kind is signal, interrupt, sleep, or start for a run submitted with start_at or delay whose
wait is armed. started_at is when the run was submitted. scheduled_start is the time a delayed run was told
to start, kept after it has; it is null for every other run.
error is {"code", "detail"} for a failed run: the error code and its redacted text. journal is null without --journal, else
an array of {"step_id", "call_index", "step_kind", "outcome", "error_type", "error_message"}.
pyrula runs retry, cancel and terminate
Section titled “pyrula runs retry, cancel and terminate”These take a run id the same way as show and share the exit codes above. See Cancel and terminate and
Replay and recovery.
pyrula worker
Section titled “pyrula worker”pyrula worker --workflow app:process_orderpyrula worker --agent app:support --llm-ref app:llm--workflow serves workflows and works on an install of pyrula-workflows alone. --agent
serves agents and comes with pyrula-agents; without it --agent exits 2 with
--agent needs pyrula-agents (pip install "pyrula[agents]"). Run one process per kind: they use
different stores and claim loops, so mixing the two flags is refused (exit 2), as is a flag that
only the other kind reads (--max-concurrent-turns with --workflow, --max-concurrent-runs
with --agent).
With --workflow the flags are: module:attr refs (repeatable) or a bare module name,
--app-dir, --postgres-dsn, --worker-id, --max-concurrent-runs, --shutdown-grace,
--cancel-timeout, --orphan-scan-interval, --shard-count, --shard-lease-ttl,
--shard-lease-interval, --health-port, --health-host, --log-level. SIGTERM or SIGINT
drains and exits 0, as pyrula dev does.
python -m pyrula.workflows.worker_main is the same worker with the same flags, for a process
that cannot use the console script:
python -m pyrula.workflows.worker_main --workflow app:process_order