# Scaled agile: teams, ARTs, PIs and portfolio

Source: https://iskue.com/docs/safe/

> Iskue's Enterprise SAFe module: delivery teams, agile release trains and solution trains, program increments with objectives, risks, milestones, burn-up and predictability, the program board, value streams, strategic themes and Rooms.

Beyond single-team Scrum and Kanban, Iskue models the structures of the Scaled Agile Framework: **teams** grouped into **agile release trains (ARTs)**, ARTs grouped into **solution trains**, **program increments (PIs)** with a generated iteration cadence, PI **objectives**, **ROAM risks**, **milestones**, a drag-and-drop **program board**, burn-up and **predictability** reporting, an Inspect & Adapt summary, and a portfolio layer of **strategic themes** and **value streams** with lean-budget guardrails. All of it is a view over ordinary tickets — a ticket simply gains `team`, `pi`, `iteration`, `theme` and WSJF fields — so nothing is duplicated into a parallel data model.

> **Note:** **Edition:** the SAFe module is an **Enterprise** feature. `/api/v1/arts/**`, `/api/v1/pis/**`, `/api/v1/portfolio`, `/api/v1/admin/teams/**` and `/api/v1/value-streams/**` answer `403 This feature requires the ENTERPRISE edition` on lower editions, and the matching navigation entries are hidden. The read-only pickers (`GET /api/v1/teams`, `GET /api/v1/themes`), the SAFe fields on tickets, the `team`/`pi`/`theme` TQL fields and per-sprint team capacity are deliberately **not** gated, so data entered under an Enterprise licence stays visible and queryable afterwards. With no licence installed nothing is gated.

## Where it lives

| Page | Route | Who | What |
| --- | --- | --- | --- |
| Teams & ARTs | `/admin/teams` | Instance admin | Solution trains, ARTs, teams and members; program increments (state, cadence, burn-up, objectives, risks); strategic themes; value streams and lean budgets |
| Program board | `/program-board` | Any user | PI planning: drag work items across (team × iteration) cells, see milestones and blocking dependencies |
| ART dashboard | `/art-dashboard` | Any user | Inspect & Adapt: predictability across PIs, the current PI at a glance, the I&A summary export |
| Portfolio | `/portfolio` | Any user | Cross-project overview of accessible projects, value-stream guardrails, filtered by strategic theme |
| Rooms | `/rooms` | Any user | Role-based home pages — **RTE room** (ART report and trains), **Team room** (team catalogue and my open work), **Portfolio room** (guardrails, goal alignment, progress). Rooms are composed from the APIs below; the last room chosen is remembered per browser. |
| Ticket detail | `/tickets/:key` | Members | Team, Program Increment, Iteration, Theme pickers and the WSJF editor |

## Teams, ARTs and solution trains

A **team** owns work items: the `team` field on tickets, `team = "Payments"` in TQL, a per-sprint capacity, and a row on the program board. An **ART** groups teams into a release train and is what a PI belongs to; a **solution train** groups ARTs for SAFe Large Solution. Names are unique at each level (duplicates return 409 `A team named '…' already exists`, likewise for ARTs and trains); deleting a solution train detaches its ARTs rather than deleting them. Membership uses a dedicated team-member table, independent of user groups.

*Team and ART management*

```bash
# Admin: an ART, a team in it, and a member
curl -s -X POST $ISKUE_URL/api/v1/admin/teams/arts -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"name":"Commerce ART"}'
curl -s -X POST $ISKUE_URL/api/v1/admin/teams -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"name":"Payments","artId":"<art-uuid>"}'
curl -s -X POST $ISKUE_URL/api/v1/admin/teams/<team-uuid>/members -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"userId":"<user-uuid>"}'

# Anyone: the pickers
curl -s $ISKUE_URL/api/v1/teams -H "Authorization: Bearer $TOKEN"
curl -s $ISKUE_URL/api/v1/arts  -H "Authorization: Bearer $TOKEN"
```

| Endpoint | Access | Purpose |
| --- | --- | --- |
| `GET /api/v1/teams` | Any user | Team catalogue for pickers and TQL suggestions: id, name, ART, member count, members |
| `GET/POST /api/v1/admin/teams`, `GET/PATCH/DELETE …/{id}` | Admin | Teams: `name` (≤ 120), optional `artId` (`clearArt` to detach) |
| `POST …/{id}/members`, `DELETE …/{id}/members/{userId}` | Admin | Team membership |
| `GET/POST /api/v1/admin/teams/arts`, `PATCH/DELETE …/arts/{id}` | Admin | ARTs: `name`; `PATCH` sets `solutionTrainId` or `clearTrain` |
| `GET/POST /api/v1/admin/teams/solution-trains`, `DELETE …/{id}` | Admin | Solution trains |
| `GET /api/v1/arts`, `GET /api/v1/arts/solution-trains` | Any user | Read-only catalogues (so Rooms work for non-admins) |
| `GET /api/v1/arts/{id}/report` | Any user | ART report: current PI, predictability per PI, burn-up, per-team completeness (planned/done tickets and points), open cross-team dependencies |
| `GET /api/v1/arts/solution-trains/{id}/report` | Any user | One row per ART: team count, current PI, latest predictability, open cross-team dependencies |

## Program increments

A **PI** is an ART's planning timebox. Create it with a name (unique per ART) and a start date — the end date defaults to start + 10 weeks — then **generate the cadence**: `devIterations` (1–8) iterations of `iterationDays` (7–28) days, named `It 1 … It N` with kind `DEV`, followed by one `IP` (innovation and planning) iteration; the PI's end date snaps to the end of the IP iteration, and regenerating replaces the existing iterations. A PI moves through the states `FUTURE` → `ACTIVE` → `DONE`; the ART report and Rooms treat the active PI (or, failing that, the most recently started one) as *current*. Tickets are planned into a PI and an iteration through the ordinary ticket `PATCH` (`piId`, `iterationId`, or the `clearPi`/`clearIteration` flags) — which is exactly what dragging on the program board does. Deleting a PI deletes its iterations and milestones and clears `pi` on every ticket planned into it.

*Create a PI, lay out 4×2-week dev iterations plus IP, activate it*

```bash
curl -s -X POST $ISKUE_URL/api/v1/pis -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"artId":"<art-uuid>","name":"PI 2026.4","startDate":"2026-10-05"}'
curl -s -X POST $ISKUE_URL/api/v1/pis/<pi-uuid>/generate -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"devIterations":4,"iterationDays":14}'
curl -s -X PATCH $ISKUE_URL/api/v1/pis/<pi-uuid>/state -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"state":"ACTIVE"}'
```

### Objectives and predictability

Each PI carries **objectives** per team (or at ART level when `teamId` is null): a description (≤ 500 characters), a **committed** flag (uncommitted objectives are *stretch* goals), a **planned business value** from 1 to 10 agreed at PI planning, and an **actual business value** from 0 to 10 scored at Inspect & Adapt. The **predictability measure** is `sum(actual) / sum(planned)` over **committed** objectives only — an unscored committed objective counts as 0 — reported per team and for the whole ART, as a rounded percentage (null while nothing is planned). The ART report plots it across PIs.

### Risks (ROAM), milestones and the I&A summary

- **Risks** — `GET/POST /api/v1/pis/{id}/risks`, `PATCH …/risks/{riskId}` (`{"roam":…}`), `DELETE`. A risk has a description (≤ 500), an optional owner and an optional ticket key (`KEY-123` form, upper-cased), and a ROAM state `RESOLVED`, `OWNED` (default), `ACCEPTED` or `MITIGATED`.
- **Milestones** — `POST /api/v1/pis/{id}/milestones` with `name` and `date`; `DELETE …/milestones/{milestoneId}`. The program board places each milestone in the iteration whose dates contain it.
- **Burn-up** — `GET /api/v1/pis/{id}/burnup`: one row per iteration with `scope` (estimate sum of everything planned into the PI) and `accepted` (points of tickets whose last entry into a DONE status, from the changelog, happened by that iteration's end — reopening removes them).
- **I&A summary** — `GET /api/v1/pis/{id}/ia-summary` returns a Markdown document: objectives table with commitment and planned/actual BV, the predictability line, risks by ROAM state, and metrics (scope, accepted, items planned, open cross-team dependencies). The ART dashboard offers it as an export.

## The program board

`/program-board` is PI planning on one screen: rows are the ART's teams, columns are the PI's generated iterations, cards are the tickets planned into the PI (key, title, estimate, status category). Drag a card to another cell to change its team and iteration; arrows show `BLOCKS` dependencies, and a **red dashed arrow** flags a blocker scheduled *after* the item it blocks. Milestones sit in their iteration column. The board (`GET /api/v1/pis/{id}/board`) applies the normal visibility rules — cards from projects you are not a member of and restricted tickets are omitted, and a dependency edge is dropped when either end is hidden — so two users can see different boards of the same PI. A PI without a cadence shows a prompt to generate one under Teams & ARTs.

## Portfolio: themes, value streams and WSJF

- **Strategic themes** (`/api/v1/themes`, admin writes) are organisation-level investment themes tagged onto portfolio epics (and any ticket) via `themeId`; the Portfolio board filters by them and the goals page groups goals by theme (`GET /api/v1/goals/alignment`). Deleting a theme clears it from tickets and goals first.
- **Value streams** (`/api/v1/value-streams`) carry a story-point `budgetPoints` and a `warnPct` threshold (1–100); themes are assigned to a stream with `PATCH /api/v1/value-streams/themes/{themeId}`. `GET /api/v1/value-streams/guardrails` rolls up committed and done points from tickets tagged with each stream's themes and reports `OK`, `WARN` (utilisation ≥ `warnPct`) or `OVER` (committed ≥ budget).
- **WSJF** — every ticket accepts `wsjfUbv`, `wsjfTc`, `wsjfRr` and `wsjfSize` (1–20 each); the score is `(user-business value + time criticality + risk reduction) / job size`, kept to one decimal, exposed as `wsjfScore`, and sortable with `ORDER BY wsjf DESC`.
- **Portfolio board** — `GET /api/v1/portfolio` is a cross-project overview limited to the projects you can access.

## Hierarchy tiers

SAFe tiers are **catalogue configuration, not code**. Three non-system ticket types ship for them — `PORTFOLIO_EPIC` (level 3), `CAPABILITY` (level 2, spanning ARTs) and `FEATURE` (level 1, a shippable slice planned into PIs) — and a project adopts them from **Settings → Ticket types → Add existing**, typically bumping its own Epic to level 2 so the chain reads Portfolio Epic > Epic > Feature > Story. Parent links follow the usual rule (the parent's level must be higher), and progress rolls up through the existing Children section. See the screens-and-fields page for the catalogue mechanics.

## TQL and capacity

| Field | Operators | Values |
| --- | --- | --- |
| `team` | `=` `!=` `IN` `NOT IN` | Team name (case-insensitive) or `empty` for unowned |
| `pi` | `=` `!=` `IN` `NOT IN` | PI name or `empty` for unplanned; PI names are unique per ART, so one name can match PIs on several ARTs |
| `theme` | `=` `!=` `IN` `NOT IN` | Strategic theme name or `empty` for untagged |
| `ORDER BY wsjf` | — | Sort by WSJF score |

All three appear in the TQL autocomplete metadata with instance-wide name suggestions (newest PIs first, up to 50 each). There is no `iteration` or `art` field. **Capacity** is set per team per **sprint**, not per PI iteration: `PUT /api/v1/projects/{key}/sprints/{sprintId}/capacity` with `{"teamId","points"}` (0–10 000, 0 removes the row; needs `MANAGE_SPRINTS`), and `GET …/team-load` returns tickets, points and capacity per team — the load-versus-capacity bars on the backlog page and the capacity bands on plan timelines read from it.
