# Runner right-sizing

Avrea measures the CPU, memory and disk every job uses on its runner, and
suggests a smaller runner when a job leaves most of its capacity idle, or a
larger one when it runs out of headroom. Suggestions cover jobs that run on
[Avrea runner labels](/runners/).

## How suggestions are made

Each job's recent successful runs are grouped by the runner size they ran on.
A group needs at least 3 runs before it gets a verdict. Avrea compares the
group's mean CPU use and its peak memory and disk use with the runner's
capacity:

| Verdict | When |
|---------|------|
| **Size up** | Peak memory reaches 75%, peak disk 90%, or mean CPU 75% of capacity |
| **Size down** | Peak memory stays under 25%, peak disk under 35% and mean CPU under 35%, and the same usage also fits a smaller runner (or the job already runs on the smallest size) |
| **Sized right** | Anything in between |
| **Not enough usage data yet** | Fewer than 3 runs |

The gap between the two thresholds keeps a job near a boundary from
switching back and forth between suggestions.

- **Size down** suggests the smallest runner the job still fits, which can
  be several sizes down (8 vCPU to 2, for example). It is only suggested when
  that runner would not itself need to size up. A job already on the
  smallest size gets **Size down** without a suggested label.
- **Size up** suggests the next larger size with more of each resource that
  ran out, which can skip sizes that add only CPU or only memory. When only
  the disk is full, it suggests the smallest size with enough disk; if no size has more disk, there
  is no suggestion.
- **Persistent disk.** A full persistent disk is not fixed by a larger
  runner, so it is flagged without a suggested label.
- **Available sizes** follow the operating system: macOS offers 8 and 16
  vCPU, ARM Linux goes up to 16, and Windows offers 2 to 16.
- Labels are suggested only for the size each job runs on now. Groups for
  sizes a job has already moved off keep their verdict but no suggestion.

## In the console

- **Job and run pages.** When a finished job should change size, its row
  shows a **Size up** or **Size down** badge, and the job shows a one-line
  explanation with the runner label to switch to. The badge opens the
  workflow's right-sizing view.
- **Workflows tab.** In a repository's **Workflows** tab, choose
  **Right-size** on a workflow to open a per-job breakdown over the last day,
  7 days or 30 days. Filter by verdict, follow a job to its recent runs, and
  copy the **Prompt for an AI assistant**: paste it into your coding
  assistant to apply the suggested labels to the workflow file.

Suggestions are refreshed at most hourly, so runs from the last hour may not
be counted yet. Changing a workflow's `runs-on` labels starts fresh
suggestions.

## API

Fetch the same suggestions from `https://api.avrea.com`. Authenticate with
an API key: `avr auth login`, then `avr auth status --show-token` prints it.
The caller must be a member of the organization and have read access to the
workflow's repository. Look up IDs with `avr org list --json organization_id`,
`avr repo list --json full_name,repository_id` and
`avr workflow list --json name,workflow_id`.

### One workflow

```bash
curl -H "Authorization: Bearer avk_your_api_key" \
  "https://api.avrea.com/orgs/{org_id}/workflows/{workflow_id}/right-sizing?window_days=7"
```

| Parameter | Description |
|-----------|-------------|
| `window_days` | Runs from the last 1 to 30 days. Default 30. |
| `created_after` | Runs created at or after this ISO 8601 time, at most 30 days ago. Use instead of `window_days`, not with it. |

At most the 1,000 most recent runs are considered. When the window held
more, the response sets `execution_limit` to that cap, and the runs used may
cover less than the window.

### Several workflows of a repository

```bash
curl -H "Authorization: Bearer avk_your_api_key" \
  "https://api.avrea.com/orgs/{org_id}/repos/{repo_id}/workflows/right-sizing?workflow_ids=wfl-...&workflow_ids=wfl-...&window_days=7"
```

Pass up to 50 `workflow_ids` and an optional `window_days` (default 30).
`data` holds one response per workflow, in the shape below. IDs that are
unknown or belong to another repository are skipped. Workflows that could not
be computed within 20 seconds are listed in `unavailable`; fetch those
individually later, or repeat the request.

### Response

```json
{
  "workflow_id": "wfl-…",
  "created_after": "2026-10-12T09:00:00Z",
  "jobs": [
    {
      "job_name": "build",
      "shape": {"cpu_count": 8, "memory_mb": 30720, "disk_gb": 300},
      "execution_count": 42,
      "summary": {"cpu_avg_ratio": 0.18, "memory_max_ratio": 0.12, "disk_max_ratio": 0.02, "truncated": false},
      "verdict": "decrease",
      "reasons": [],
      "current": true,
      "persistent_disk_full": false,
      "root_disk_full": false,
      "current_label": "avrea-ubuntu-24.04-8-vcpu",
      "suggested_label": "avrea-ubuntu-24.04-2-vcpu",
      "suggested_shape": {"cpu_count": 2, "memory_mb": 7680, "disk_gb": 14}
    }
  ],
  "workflow": []
}
```

`jobs` has one group per job name and runner size. `workflow` has one group
per runner size across all of the workflow's jobs. In each group:

| Field | Meaning |
|-------|---------|
| `shape` | Runner size the counted runs used |
| `execution_count` | Successful runs with complete usage data |
| `summary` | `cpu_avg_ratio` (mean), `memory_max_ratio` and `disk_max_ratio` (peaks), each between 0 and 1. `truncated` is `true` when some run's data covers only part of the run |
| `verdict` | `increase`, `decrease`, `keep` or `insufficient_data` |
| `reasons` | Resources over their size-up threshold: `cpu`, `memory`, `disk` |
| `current` | `false` for a size the job has moved off; such groups get no suggestion |
| `current_label` | The `runs-on` label of the latest run; omitted when unknown, when the latest run's label is for a different size, or for a workflow group whose jobs use different labels |
| `suggested_label`, `suggested_shape` | The label to switch to and its size; omitted when there is no suggestion |
| `root_disk_full`, `persistent_disk_full` | The root or persistent disk is over the size-up threshold |

### Errors

| Status | When |
|--------|------|
| 404 | Workflow not found, or no access to its repository |
| 422 | `window_days` out of range, `created_after` in the future or more than 30 days ago, both given, or more than 50 `workflow_ids` |
| 429 | Rate limit exceeded |
| 503 | One-workflow endpoint only: the workflow could not be computed in time. Retry after the `Retry-After` header, or ask for a shorter window. The repository endpoint lists such workflows in `unavailable` instead |

Results for `window_days` (or the default) are cached for up to an hour. A
request with `created_after` is always computed fresh.