---
question: How do I follow a workflow run's steps, and stop it?
kind: task
via_web: available
via_cli: na
via_python: available
via_api: available
via_config: available
ref_web: System › Jobs
ref_python: WorkflowRun
ref_api: GET /api/workflows/{module_id}/runs/{run_id}
ref_config: nats.jobs.retention
prereq: You need a NATS project and a started workflow run. The System app and `/api/workflows` take a platform super admin, or the engine's `ABI_API_KEY` for the REST API.
next:
  - /capabilities/build/workflows/run-a-workflow
  - /capabilities/build/workflows/write-a-durable-workflow
  - /capabilities/build/pipelines/monitor-runs
---

# Monitor and cancel a run

Every durable run, of a `@workflow` or of a published class (`workflow.<slug>`, `pipeline.<slug>`), is a job run with a record: its status, attempt, input, output, logs, trace, and the steps it stored. A cancel is cooperative: the host sets `ctx.cancelled`, the step running at that moment ends, and the run cancels the child runs it waits for.

A run's status is one of:

| Status | Meaning |
|---|---|
| `QUEUED` | Started, not taken by a host yet. |
| `RUNNING` | A replica of the module runs it. |
| `RETRYING` | An attempt failed; the next one starts after a backoff. |
| `SUCCEEDED` | It returned its output. |
| `SKIPPED` | It called `ctx.skip()`: nothing to do, no output. |
| `FAILED` | It failed with no attempt left, or with an error another attempt cannot fix (an invalid input). |
| `TIMED_OUT` | It ran past its `timeout`. |
| `CANCELLED` | Someone cancelled it, or its parent ended for good. |

## Web UI

### Open the workflow

Open **System** from the Nexus sidebar, then the **Jobs** tab. Pick **Workflows** in the kind filter to list only workflows, then click one. Its page shows the **Schedule** (with the input each trigger sends), **Health**, **Now**, **Settings**, the **Input schema** and **Output schema**, and its **Runs**.

![System app on the Jobs tab, filtered](/abi/Screenshot_System_Jobs.png)

*System › Jobs: each row is a job or a workflow, with its schedule, recent runs and last run.*

### Read a run

Click a run. Its panel has the timeline and the **Steps**, **Logs**, **Input**, **Result** (**Output** when the run did not succeed) and **Trace** tabs.

**Steps** lists each step in start order: its key, status, attempts, timing and error. A `call` or `map_call` step links to its child run: click it to open the child's own panel.

### Cancel it

While the run is going, click **Cancel**. The cancel is written to the audit log. The run ends `CANCELLED` at its next check of `ctx.cancelled`, or when its current step returns. **Re-run** starts a new run with the same input.

The System app is for platform super admins only.

## CLI

Not applicable. No `abi` command lists or cancels runs. Use the Web UI, or call the REST API from a script.

## Python

### Keep the run

`start(...)` returns a `WorkflowRun`:

```python
report = self.get_workflow("daily_report")
run = await report.start({"day": "2026-10-31"}, idempotency_key="report-2026-10-31")
print(run.run_id, run.duplicate)  # duplicate: the same key within two minutes
```

`get_workflow` is on the module (its own workflows), on `self.engine.modules[id]` (another module) and on `ABIClient`: see [Run a workflow](/capabilities/build/workflows/run-a-workflow?via=python).

### Read its status and steps

```python
record = await run.status()        # {"status": "QUEUED"} until a host takes it
steps = await run.steps()          # key, kind, status, attempt, timing, error, child
record = await run.wait(timeout=600)
output = await run.result()
```

- `status()` returns the run record.
- `steps()` returns the step summaries in start order, without their results. A `call` step carries `child`: the child's `module_id`, `workflow` and `run_id`.
- `wait(timeout=)` returns the record once the run is over.
- `result()` returns the output, or `None` for a skipped run. It raises `WorkflowRunFailed` for a run that ended any other way.

### Cancel it

```python
await run.cancel()
```

Inside the workflow, check `ctx.cancelled.is_set()` in long loops, and give I/O calls a timeout, so a cancel or a timeout takes effect soon.

## REST API

The Nexus workflow routes. Auth: `Authorization: Bearer $ABI_API_KEY` (the engine's key, which acts as the operator), or the session token of a platform super admin. These routes are not in the generated API reference.

### Read a run

The run id is what the start returned (`daily_report:12`):

```bash
curl -s "http://localhost:9879/api/workflows/acme.reports/runs/daily_report:12" \
  -H "Authorization: Bearer $ABI_API_KEY"
```

The answer has `status`, `attempt`, `max_attempts`, `fired_at`, `started_at`, `finished_at`, `duration_ms`, `error`, `trace_id`, the whole `result`, and `steps`: each with `key`, `kind`, `status`, `attempt`, timing, `error`, `child` (`module_id`, `workflow`, `run_id`, `key`) and `result_bytes`. `steps_truncated` is set when the run has more steps than one page. A run no host has a record of (pruned, mistyped, not taken yet), a plain job's run, or a malformed id answers 404.

### Cancel it

```bash
curl -s -X POST "http://localhost:9879/api/workflows/acme.reports/runs/daily_report:12/cancel" \
  -H "Authorization: Bearer $ABI_API_KEY"
```

`202 {"ok": true}` when the cancel was sent, 409 when the run is not running, 404 for a plain job's run. A cancel is written to the audit log first (with `via: api_key` for the key); if the audit log cannot be written, the answer is 503 and nothing is cancelled.

### List the workflows

`GET /api/workflows` lists every workflow with its module, input and output schemas, limits and triggers. To list runs across jobs and workflows, use the System routes on [Monitor runs](/capabilities/build/pipelines/monitor-runs?via=api).

## Config

### Keep run records longer

```yaml title="config.yaml"
nats:
  jobs:
    retention:
      max_age_days: 7
      max_runs_per_job: 1000
      skipped_max_age_minutes: 60
```

These are the defaults: finished runs are kept 7 days, at most 1,000 per workflow, skipped ones 60 minutes. A pruned run's steps are deleted with it. Running and queued runs are never pruned. The other keys are on [Monitor runs](/capabilities/build/pipelines/monitor-runs?via=config).

## Parents and children

- A parent that ends for good (cancelled, timed out, or failed with no attempt left) cancels the children it still waits for.
- A parent that the host retries leaves its children running: the next attempt waits for the same children.
- Cancelling a child alone fails the parent's step with `ChildRunFailed`, and the parent retries or fails like for any error.
