# Delivery metrics

**A project's Metrics tab reads its delivery out of what actually happened**, not out of anyone's estimate. Every number on it is computed from cycle [stage history](/running/cycles/#stage-history) — the attributed, timestamped record of each status move — so a metric can always be traced back to specific transitions.

## Prerequisites

- Agency role with `manage` access on the project. Metrics is agency-only: stage timing and automation quality are operational data, not a client-facing deliverable view.
- At least one cycle that has moved through a stage. A project whose first cycle has not transitioned yet shows an empty state, not zeros.

## Reading it

In the dashboard, open a project and go to **Metrics** under **Delivery** in the project settings navigation. Over the API:

```bash
curl -sS "$HLIX_BASE_URL/v1/api/projects/$PROJECT_ID/cycle-metrics" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
```
**Every metric is lifetime-to-date:** There is no time window and no date filter. The response covers every cycle the project has ever had. A long-running project's medians therefore include its early cycles, which is worth knowing before you read a trend into them. All durations are milliseconds on the wire; the dashboard formats them.

## What each number means

### Throughput

Counts of cycles by where they ended up: `completed` (a pull request was created), `failed`, and `canceled`. It is a **count, not a rate** — there is no per-week denominator, because there is no window.

The dashboard shows completed as the headline figure with failed and canceled beneath it.

### Cycle time

`p50` and `p90` measured from a cycle's creation to its last recorded transition, over cycles that reached a terminal status — `pr_created`, `failed`, or `canceled`.

`sampleSize` is how many cycles that actually covers. A terminal cycle with no stage history at all is skipped rather than guessed at, so `sampleSize` can be lower than the terminal count. Both percentiles are `null` when the sample is empty, and the dashboard shows `—` rather than a zero that would read as "instant".

### Time per stage

The median time a cycle spends in each stage, over **completed** stints only. A stage segment closes when a transition leaves it; the stage a cycle is sitting in right now is not counted, because it has not finished yet.

A stage nobody has exited is **absent from the list**, not reported as zero. The chart orders stages by the lifecycle — Draft, Triage, Interviewing, Planning, Executing, End-to-end tests, Pull request created, Failed, Canceled — rather than by how often they were observed.

### Work in progress and aging work

**WIP** counts cycles currently in a non-terminal stage: `draft`, `triage`, `interviewing`, `planning`, `executing`, or `e2e`, broken down by stage.

**Aging work** lists the in-flight cycles that have been sitting in their current stage longer than that stage's own historical median for this project. The threshold is not a fixed number of days — it is the project's own typical time for that stage, so a project with slow reviews is not permanently red. A stage with no median yet flags nothing, and the list is sorted worst overrun first.

### Automation quality

This is the section worth reading carefully, because it deliberately refuses to flatter the automation.

Every stage exit — one transition leaving one stage — is attributed to a `human`, an `agent`, or a `rule`. Agent and rule both count as automated.

| Figure | What it is |
| --- | --- |
| Raw automated share | Automated exits ÷ all exits |
| Quality-adjusted share | *Clean* automated exits ÷ all exits |

An automated exit is **not clean** when the stage it left is re-entered later in the same cycle. That is rework: the automation moved the cycle on, and something moved it back.
**Rework outranks done:** A cycle that shipped can still contain automated exits that needed rework, and those count against the automation regardless. The quality-adjusted share is deliberately independent of whether the cycle eventually reached a pull request — "it worked out in the end" is not evidence the automation was right the first time.

Neither figure is colour-coded. There is no product threshold for a good automated share, and inventing one would be a claim the data does not support.

Only automated exits are checked for rework; a human decision that gets reversed is not counted anywhere.

## If a panel looks wrong

- **"No cycles yet" on a project you know has run work.** Metrics reads cycles, not tasks. A project whose work never became a cycle has nothing to measure.
- **Every stage median is missing.** Nothing has *exited* a stage yet. A cycle sitting in `executing` since it was created has produced no closed segment.
- **Cycle time is `—` but throughput shows completed cycles.** Those cycles reached a terminal status with no stage-history entries to measure between — the sample is empty, and an invented duration would be worse than a dash.
- **Aging work is empty while something is obviously stuck.** That stage has no median yet, so there is no threshold to exceed. It will start flagging once comparable cycles have passed through.
- **The Metrics entry is missing from the navigation.** You are signed in as a client collaborator. The route is agency-only and the backend refuses it too, so hiding the link is the honest behaviour rather than showing a wall.

## Where to go next

| If you want to… | Read |
| --- | --- |
| Understand the stage history these numbers are computed from | [Cycles](/running/cycles/#stage-history) |
| See why a cycle entered the stage it did | [Triage](/concepts/triage/) |
| Find the work that is blocked rather than slow | [Approvals](/running/approvals/) |
| Read the immutable record instead of the aggregate | [Review & QA gates](/concepts/review-gates/#what-gets-recorded) |

## Next steps

[Cycles](/running/cycles/)
  [Triage](/concepts/triage/)
  [The dashboard](/running/dashboard/)
  [Approvals](/running/approvals/)