---
question: Which pipeline runs are recorded, and where do I see them?
kind: task
via_web: available
via_cli: na
via_python: available
via_api: available
via_config: available
ref_web: System › Jobs
ref_python: JobContext
ref_api: GET /api/admin/system/jobs/runs
ref_config: nats.jobs.retention
prereq: You need ABI 3.x and a pipeline in a NATS project, started as `pipeline.<slug>` or called by a [scheduled job](/v2/capabilities/build/pipelines/schedule-a-pipeline), and a platform super admin account for the System app and its API.
next:
  - /v2/capabilities/build/pipelines/schedule-a-pipeline
  - /v2/capabilities/build/workflows/monitor-and-cancel-a-run
  - /v2/capabilities/build/modules/module-jobs
---

> **You are reading the v2.x docs (engine modules). New projects use v3.** [Read this page in v3](/capabilities/build/pipelines/monitor-runs) · [Migrate from 2.x to 3.0](/capabilities/upgrade/migrate-to-3)

# Monitor runs

Runs are recorded by the jobs runtime of ABI 3.x, in a NATS project. ABI 2.x and 3.x without NATS have no jobs runtime: a pipeline run from a Dagster orchestration shows in the Dagster UI, and a direct call is not recorded (see [Dagster (legacy)](/v2/capabilities/build/applications/dagster)).

In a NATS project, what shows depends on how the pipeline was started:

| Started by | Recorded |
|---|---|
| A start of `pipeline.<slug>`: System › Jobs **Run now**, `/api/workflows`, `get_workflow(...).start()` or `.run()`, `ctx.call` | Yes, as a run of `pipeline.<slug>`: status, input, its one step `run`, output (the triple count and the Turtle), trace |
| A module job or a `@workflow` that calls `run` | Yes, as a run of that job or workflow: status, payload, logs, result |
| A direct `run(...)`: a script, an agent tool, `POST /pipelines/...` | No |

Run statuses are `QUEUED`, `RUNNING`, `RETRYING`, `SUCCEEDED`, `SKIPPED`, `FAILED`, `TIMED_OUT` and `CANCELLED`.

## Web UI

### Open the Jobs tab

Open the **System** app from the sidebar, then the **Jobs** tab. It lists every job of the engine and of the modules: **Job**, **Schedule**, **Recent runs**, **Last run** and **Now**. A red count on the tab is the number of failed runs; **Show failed runs** filters to them.

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

*System › Jobs, filtered to one module's jobs: schedule, recent runs, last run and Run.*

### Open the job

Click the job, or the pipeline's `pipeline.<slug>` (the **Workflows** kind filter lists the workflows only). Its page shows the **Schedule**, **Health**, **Now** and **Settings** (concurrency, attempts, timeout), then its **Runs**. **Run now** starts a run: a job takes an optional JSON payload that reaches the handler as `ctx.payload`, a pipeline a form built from its parameters model.

### Read a run

Click a run. The panel shows the timeline and the **Logs**, **Payload**, **Result** (**Output** when the run did not succeed) and **Trace** tabs. A `pipeline.<slug>` run adds **Steps** and shows **Input** instead of **Payload**. **Re-run** starts it again; **Cancel** stops a running one. Steps and cancels are on [Monitor and cancel a run](/v2/capabilities/build/workflows/monitor-and-cancel-a-run?via=web).

The System app is for platform super admins only.

## CLI

Not applicable. No `abi` command lists jobs or their runs. Use the Web UI or the REST API. `abi dev logs api -f` follows the API process's log, where the engine hosts the jobs: it has what the pipeline writes with `logger`, not the run records.

## Python

### Write to the run's log

The job handler gets a `JobContext`. What it logs and returns is what the Jobs tab shows:

```python title="src/my_module/__init__.py"
@job(triggers=(Every("1h"),))
def sync_items(self, ctx: JobContext) -> dict:
    graph = ItemsPipeline(ItemsPipelineConfiguration()).run(ItemsPipelineParameters())
    if len(graph) == 0:
        ctx.skip("no new items")
        return {}
    ctx.log(f"inserted {len(graph)} triples")
    return {"triples": len(graph)}
```

### Know what each field does

- `ctx.log(message)`: a line in the run's **Logs** tab, up to 200 lines of 2,000 characters.
- `ctx.skip(reason)`: ends the run as `SKIPPED`. Skipped runs are kept for an hour, so polls with nothing to do do not fill the history.
- The return value: the run's **Result**. An exception fails the attempt, and its error shows instead.
- `ctx.cancelled`: an `asyncio.Event`, set on a timeout or a cancel. In a long loop, test `ctx.cancelled.is_set()` (the event object itself is always truthy), or `await ctx.cancelled.wait()` in an async handler.

## REST API

### List the runs

```bash
curl -s "http://localhost:9879/api/admin/system/jobs/runs?module=my_module&job=sync_items&status=FAILED" \
  -H "Authorization: Bearer $NEXUS_ACCESS_TOKEN"
```

Filters are `module`, `job`, `status` (comma-separated), `trigger`, `before`, `limit` (1 to 500, default 50) and `include_skipped`. `module` is the module's name under `modules:`. `GET /api/admin/system/jobs` lists the jobs, and `GET /api/admin/system/jobs/failures?since=...` the failed runs.

### Read one run

```bash
curl -s "http://localhost:9879/api/admin/system/jobs/runs/my_module/$RUN_ID" \
  -H "Authorization: Bearer $NEXUS_ACCESS_TOKEN"
```

The run detail adds `payload`, `result`, `logs` and `trace_url`, and, for a `pipeline.<slug>` run, `workflow` with its steps. Scripts holding the engine's `ABI_API_KEY` read a `pipeline.<slug>` run with `GET /api/workflows/{module_id}/runs/{run_id}` instead: see [Monitor and cancel a run](/v2/capabilities/build/workflows/monitor-and-cancel-a-run?via=api).

### Trigger or cancel

`POST /api/admin/system/jobs/my_module/sync_items/trigger` with `{"payload": {}}` starts a run and returns `202` with its `run_id`. `POST /api/admin/system/jobs/runs/my_module/$RUN_ID/cancel` cancels one. Every route needs a Nexus login token of a platform super admin; see [Enable in a workspace](/v2/capabilities/build/knowledge-graph/enable-ontology?via=api) for the sign-in call.

## Config

### Keep run records longer

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

These are the defaults: finished runs are kept 7 days, at most 1,000 per job, skipped ones 60 minutes, pruned every 10 minutes. `enabled: false` keeps every record. Running and queued runs are never pruned.

### Send traces

Each job run is a span named `job <name>`, and the run record keeps its trace id. With a `telemetry:` block that is enabled (off by default) and sets `ui_url`, the run's **Trace** tab links to the trace viewer. `abi dev up --with-tracing` (ABI 3.x) adds that block. See the [Configuration Reference](/user-guide/get-started/configuration#telemetry).
