---
title: "Programs"
description: "Programs in Fir: plans of any size as one tree with no fixed depth (program, plan, phase, section, item by default), Today for the person signed in, the Map that shows a whole program on one screen, a readout and Do next that say what to do first, forecasts with people's waits in them, work only a person can do on every view, lists that stay compact at 1,000 and 10,000 items, insights from stored rows, milestones that count only proven merges, and many squads and models working one program."
url: https://frazer.ai/docs/source/programs
index: https://frazer.ai/docs/source/programs/llms.txt
tokens: 11502
---

> For agents: this page is indexed at https://frazer.ai/docs/source/programs/llms.txt, and every page at https://frazer.ai/llms.txt. Copy commands from this file exactly.

# Programs

A plan that outgrows one file becomes part of a **program**: one tree of nodes, as detailed as the work needs, with rollups on every node, milestones that cut across it and many squads of people and agents working different parts of it at once. Each node keeps its identity through every restructure, and every count rolls up from items whose layers merged and whose proofs passed.

## The tree

| Default level | Role | What it is | Approved |
| --- | --- | --- | --- |
| **program** | record | The outcome, its budget, repositories, risk ceiling and pre-approval rule; a plan of tier program, such as `pellwyn/plans#120` | Yes |
| **plan** | record | One workstream with its terms, owned by one squad and one accountable person (`#405`) | Yes: its terms, and its phases in waves |
| **phase** | group | A stage of a plan with an outcome, dates, an exit checklist and a detail state | With its plan, or by itself |
| **section** | group | A scope inside a phase | With its phase |
| **item** | work | The unit a layer delivers, a squad is dispatched and a session claims (`7.3.2`) | With its phase |
| **sub-item** | work | An item split further; the parent’s state rolls up from its sub-items (`7.3.2.1`) | With its item |

A **record** level is numbered, versioned and approved; a **group** has an owner, dates, an exit checklist and rollups; a **work** node gets its state from layers and proofs.

-   **Repeat or skip any level.** A program may hold sub-programs, phases directly, or plans of plans; a plan may hold items directly or sub-phases; an item may hold sub-items, and those their own. There is no limit on depth, children or items. A team that wants one sets it as a rule in its type (`max_depth: 6`), never as a product limit.
-   **Name your own levels.** Levels are declared in a type’s `levels:` block ([Record types](https://frazer.ai/docs/source/record-types)). The built-in `plan` type declares the defaults; a team’s layer of it can add a level, rename or drop the ones it does not use, or approve phases with their plan:

```yaml
# acme/plans .fir/types/plan.yaml: Acme's layer of the built-in plan type
format: record-type/v1
name: plan
version: 1
levels:                                   # merged by name with the built-in levels
  - {name: program, children: [program, workstream, plan, phase]}
  - {name: workstream, role: group, children: [plan, phase], numbering: dotted}
  - {name: phase, approval: inherit}      # Acme approves a plan's phases with the plan
limits: {max_depth: 7}                    # a rule Acme chose
```

-   **Type and level are separate.** A record keeps its type wherever it sits: plan `#231` can be phase 7 of plan `#405`, keep its number, terms, approvals, items (`#231 3.3`), stacks and links, and be drawn as “Phase 7 · #231 Dual-write payouts to the ledger”.

### A node keeps its identity

Every node has an ID from the server that never changes and is never reused. Inside a record, a node’s dotted key (`7.3.2`) is given at creation and never changed by a later move; a move to another record gives it a new key there and keeps the old one as an alias, so `#231 3.3` resolves wherever 3.3 went. A node’s text lives inline in its parent’s file, in a **part file** once a subtree passes 400 lines or 6,000 tokens (Fir suggests the split, a mechanical change), or as its own record.

| Operation | What it does | What it keeps |
| --- | --- | --- |
| **insert** | A new node under a parent, after a sibling | — |
| **move** | A node and its subtree to a new parent and position | ID, key inside the record, links, proofs, claims (the holder gets a notice), history |
| **split**, **join** | A subtree to its own part file, or back inline | Everything; the content digest is unchanged |
| **promote** | A node becomes a record and takes a number: an item can itself become a plan | ID, links, proofs; the old key becomes an alias |
| **demote** | A record becomes a node of another record | ID; its number stays as an alias |
| **merge** | Two nodes become one | The links and proofs of both |
| **drop** | Removed by an amendment, with a reason | History |

A move inside one plan’s approved scope is mechanical, so approvals carry; a move across plans is material for both unless the program’s pre-approval rule covers it. `fir node log <node>` lists every version of a node’s text and every move, across files and records, with the version that approved each. Two agents restructuring one phase never corrupt the tree: the server orders live tree operations and refuses a move that would create a cycle, and two pull requests that put one node under two different parents get a **tree conflict**, held as data with one key to choose.

### The tree in files

A plan file holds its phases inline or as one line each pointing at a part file; nothing else about records changes:

```markdown
---
type: plan
key: 414
title: Payouts on the ledger
level: plan
parent: pellwyn/plans#122         # the sub-program "Payouts on the new ledger"
squad: payouts
owner: people/diego@pellwyn.example
approves: [P1-P16, P21-P25]       # phases approved so far
---
## Outcome
Every payout is written to the ledger first; the old store is read-only by Jan 29.

## Phases
- P7 · plan #231 "Dual-write payouts to the ledger" · approved
- P9 {#n-9b21} Dual-write parity for 7 days · approved
  → plans/414-payouts-on-the-ledger/09-dual-write-parity.md
- P30 {#n-c04e} Retire payout batch exports · outline
  Outcome: no consumer reads batch exports. Size: about 30 items. Starts: Jan 5.
```

A program’s tree is stitched from records: each plan names its `parent`, and the index joins them into one tree, so a view, a rollup or a move never cares which file a node lives in. A node has exactly one parent, so rollups never count twice; everything else it belongs to (milestones, goals, other programs’ dependencies) is a link, and lenses show it under each. A plan stored in a code repository’s own tree can still be a child of a program in the plan repository.

## Any depth, any type, more levels

A program is a tree with no fixed depth whose levels your team names, and trees of different schemes read together. Every level of every scheme aligns to one rung of a seven-rung **ladder**: portfolio, program, plan, phase, section, item and sub-item. A level keeps its own name inside its subtree, and views that span schemes use the rung’s name. The ladder is not a limit: a level may repeat, a rung may be skipped, and nothing deeper is refused.

| Rung | Pellwyn’s default | A compliance pack’s scheme | A team’s Shape Up scheme | An imported tracker’s scheme |
| --- | --- | --- | --- | --- |
| portfolio | portfolio | — | — | — |
| program | program, sub-program | program | program, cycle | program, initiative |
| plan | plan | obligation | bet | epic |
| phase | **phase** | **control** | — | — |
| section | section (repeats) | test | **scope** | — |
| item | item | evidence | task | story |
| sub-item | sub-item | — | — | sub-task |

Bold marks each scheme’s **review level**: the level above stacks that a [node review](https://frazer.ai/docs/source/stacks#above-the-stack-the-plans-levels) covers. A level declares its rung with `aligns_to:`; when a scheme leaves it out, Fir infers it from each level’s role and order and asks the scheme’s owner only when the order cannot decide.

-   **One program mixes schemes and types.** A node may name the scheme its subtree uses (`levels: shape-up@2`), so a portfolio can hold programs with four schemes; moving a subtree maps its levels by rung and renames nothing. A level may accept several record types (a phase that is an RFC record keeps its own type, number and life cycle). Every type maps its states onto five **state categories** (proposed, in progress, resolved, completed, removed), so rollups, the Map’s fill and the readout count one progress line across plans, RFCs, reviews and risks.
-   **Your own schemes** arrive as a team’s layer of a type, as a pack, or with an import that keeps the source’s depth; Fir infers each level’s rung with at most one question, and a scheme in use changes in place by tree operations. Shape lints warn about trees that are too deep or too wide, and never refuse them.
-   **More levels.** A **portfolio** is a program of programs, at any depth; goals stay records seen through the goal lens, so a goal that many programs serve never counts twice. Below the item, **stacks** and **pull requests** appear as read-only levels of every view, because every layer names the items it delivers and every stack carries its plan node. The **Levels** view draws the whole tree as stacked bands, one per level, each node a block as wide as its items and filled by its share done, with the level counts as its legend (“portfolio 1 › program 79 › plan 878 › phase 20,628 › section 78,933 › item 996,467 › sub-item 181,327”, product UI, demo data).
-   **The Map at any depth and any size.** The tile level is chosen by an entity budget and the row’s width; rows of other schemes fall back to their own levels; aggregates never hide an exception (a ring or a ✕ always shows through). An **inline map** is a node’s Map at word size, 120 by 14 px, wherever a node appears in a list (the portfolio list, Today, the pull request list’s group headers, hover cards), so a person scanning 500 programs sees which ones need them without opening any. Each row has a **plan line**: a 1 px mark where the row’s approved baseline says it should be now, with “3 phases behind the plan line” or “on the line” on its chip. The line measures against the plan **as last approved** by default, with the first approval one key away, and every re-plan that moved a milestone’s date draws a mark on the line (“re-planned Nov 12 · +6 days”) whose hover names the milestones, the nodes, the reason and the approver. A version that moves a date needs a reason (`fir plan submit` refuses one without, with `--reason` in `next`); a re-plan moves only work still ahead, so done work keeps its baseline and actuals are never rewritten; a wrong date is fixed by a labeled correction, never drawn as a re-plan; a milestone promised outside (to a regulator, a contract or customers) moves past that date only through its owner’s milestone route, and the mark says so; and re-plan churn shows as a count (“re-planned 3 times in 90 days”), so a program cannot stay on its line by re-planning without it showing. The Map comes in four layouts, all reading the same rows: **Tiles** (the default: counting and exceptions at a glance), **Treemap** (where the remaining work or the money is), **Swimlanes** (which stage each plan is in) and **Timeline** (when, and slips against the baseline), plus the **Levels** view for the tree’s shape.
-   **Schedule measures in days**, from the same records as the plan line: behind the line in tiles and days, SV(t) and SPI(t) by Earned Schedule over approved items, the execution index (items done over items the baseline planned done by now), slip since the first approval and re-plan churn, against the plan as last approved by default and the first approval with `B`. `fir program baseline <ref> --compare v2 --measures` prints them; every approval writes the baseline Fir keeps per node (start, finish, items in scope, a scope digest), never a field of the file.
-   **Programs and pull requests reconciled.** The tree is the spine: a pull request sits under the items it delivers, a stack under its node, a campaign’s wave under its source’s nodes, and a pull request in no program under its kind (a dependency update, an exempt change, a repair). Every node keeps additive counters updated in the merge transaction (not merged, in the merge queue, merged in the last 24 hours, merged with no person, merged with a person, closed, reverted, exceptions open, waiting items, sessions open), so every level of the drill-down adds up to its parent. Pellwyn’s portfolio on Tue Nov 17 at 15:00 (product UI, demo data): “40,000 in 24 h · 35,200 merged (34,800 with no person) · 4,000 not merged · 800 closed · in #600’s programs 32,000 · in no program 8,000”.
-   **The small things every node has**, decided one by one in the type rules: timestamps for every transition, written by Fir and never typed, each a list so a regression keeps its history; stages with a `ready` and a `done` checklist per level; labels defined per project and shared with issues (`key::value` labels exclusive within their key, `area/payments/refunds` rolling up under `area/payments`); free tags that never drive a rule, a route or a dispatch; one accountable owner per node with contributors computed from sessions and pull requests; optional estimates with actuals always computed, and forecasts that never use estimates; due dates inside the parent’s; service levels per level (“items: 85 % within 6 h”); classes of service (expedite, fixed date, standard, intangible) instead of a free priority; every approved version a baseline; work-in-progress limits per level and squad; recurring items; and a node’s combined diff of every merged layer under it. Colored label pills, icons per level, several parents and closing children with their parent are left out.

## Draft everything, approve the next phases

| Detail | What exists | Dispatched |
| --- | --- | --- |
| `outline` | Title, outcome, size estimate, dates, owner | No |
| `drafted` | Sections and items, with criteria and paths | No: the items are planned and held |
| `approved` | The phase’s scope inside an approved plan version | Yes |

Drafting is cheap and approval is scarce, so agents can draft all forty phases of a plan to the item while people approve the next phases as they come. A plan approves its own terms once, then **its phases in waves**: a version names the phases it approves (`approves: [P8, P9, P10]`), and each wave is a short approval of what changed. Detailing an outlined phase is a material change to that phase, approved before its items are dispatched. Drafted phases never count as scope, progress or forecast until approved. A team that wants one approval for the whole plan sets `approval: inherit` on its phase level.

## Built for size

-   **Snapshot tokens.** Every write returns a sequence number, and every read may pass `as_of` and is served from a snapshot no older than that, so what you see after your own move is always fresh.
-   **Nothing chases pointers.** Each node stores its ancestors and its counters, so “everything under this phase” and “items done in this plan” are one read at any depth. A merge updates the counters along the changed path only.
-   **Caches that are never stale.** Each node stores the sequence of the last change anywhere below it, so a critical path or a forecast cached for a node is reused exactly until something under it changes.
-   **No ceiling to remove later.** A program index holds hundreds of thousands of items in memory; past about ten million nodes, sub-programs move to their own shards, which is the same operation as moving a subtree.

| Shape | First view, cold | Any view change | Rollup after a merge |
| --- | --- | --- | --- |
| 2,000 items | under 150 ms at p95 | under 8 ms | under 1 ms |
| 20,000 items (20 plans of 40 phases) | under 200 ms | under 16 ms | under 1 ms |
| 200,000 items | under 300 ms, windowed | under 16 ms on loaded rows | under 2 ms |
| 2,000,000 items | server filters under 100 ms at p99 | — | under 5 ms |

These are the speeds Fir is designed for. A node inherits its record’s permissions and a group may narrow them; views and rollups count what you cannot see as “3 hidden”, never with titles.

## Today

A person’s day starts on the plan repository’s **Today** tab. It is one read, about the person signed in only, newest and most blocking first, and agents read the same data with `get_today`:

| Block | What it shows |
| --- | --- |
| **Needs you** | Grouped by kind: **Decide** (decisions you decide, with decision groups), **Approve** (approvals and re-approvals, waves of phases), **Do** ([person tasks](https://frazer.ai/docs/source/person-tasks) you owe), **Confirm** (conditions and suggested rebalances), **Update** (milestone updates due), and reviews you owe and records you own that turned stale or suspect; each with its deadline and one key to act |
| **Do next** | Your actions ranked by what they change ([below](#do-next)) |
| **Waiting on others** | What you asked for and who holds it, with their SLA; for a program’s owner, **Waiting on people**: who owes what, since when, what it blocks and its act by date, decisions and approvals included, grouped by person and kind |
| **Since you last looked** | Routine events folded into counts per subtree (“#122: 14 merges · 9 items done with their proofs”) and exceptions listed one by one (failing proofs, held breaks, refuted assertions, lost leases, new person tasks, answered decisions), at most 12, then “N more” |
| **Your plans** | Each with its stage, items proven of total, its plan score and the next blocking thing |
| **Milestones** | Target against p50 and p85, the at-risk reasons and the slowest squad |
| **Agents at work** | Live sessions on your plans: the item, the step, the last event, what they wait on |
| **Insights that moved** | The five insights that changed most in your scope since last week |
| **Squads** | For a program’s owner: each squad’s load against capacity, items in flight, blocked, its next integration milestone |

You can pin any saved view to Today. Today is your share of My work for this project, from the same rows, so it never duplicates it.

## Views

| Mode | What it shows |
| --- | --- |
| **Outline** | The tree with rollups on every row and a depth control (plans, phases, sections, items, all); the critical path marked |
| **Board** | Nodes of one level as cards in columns, by state, stage, squad or any field |
| **Table** | Nodes of chosen levels as rows with chosen columns, editable in place |
| **Timeline** | Milestones from p50 to p85, phases and plans as bars, dependencies as lines |
| **Graph** | Dependencies, the critical path, cross-repository and cross-squad edges, contracts as hubs |
| **Matrix** | Traceability, or any two dimensions with counts (squad × milestone) |
| **Map** | The whole program on one screen: one row per plan, one tile per phase in phase order, with how far each phase is and where a person is needed ([below](#the-map)) |
| **Workload** | Load against capacity per squad, person or agent over the next weeks |

A **lens** re-roots the same nodes under another hierarchy without copying them: by plan (the default), by squad, by milestone, by repository, by goal, or by any field a type declares; rollups are recomputed for the lens’s groups. A mode a team adds is a view rendered by a Frazer App over the same query.

One filter syntax serves the console’s search box, URLs, `fir` and MCP, and agents may send it as JSON:

```text
is:open level:item under:#122 squad:payouts,driver-pay milestone:#146 proof:failing changed:>7d sort:critical
```

A saved view is personal, or shared as `.fir/views/<slug>.yaml` and reviewed like a record, and it can be scoped to any node. Built in: Mine, At risk, Blocked, Unproven, Changed since approval, Agents at work, Ready to take, Waiting for approval, Cross-squad blockers and Outline debt.

**Keyboard:** `J` and `K` move, `→` and `←` expand and collapse, `1` to `9` show that many levels, `0` all, `X` selects, `Space` peeks, `/` filters, `v` switches mode, `l` switches lens, `m` moves the row, `g t` opens Today, `g m` the map, `@` assigns, `A` takes the row’s allowed approval, `c` hands the item to an agent, `?` shows every key. Each action is also a command in the palette and a tool for agents.

**The plan score** ([The planning flow](https://frazer.ai/docs/source/planning-flow#the-plan-score)) shows on every node: the same formula over the items of its subtree, never a mean of its children’s percentages, with its lowest child beside it. A program reads “Score 86 · lowest #409, 61 · 16 of 20 plans ready to execute”.

## The Map

The Map puts a whole program on one screen: one row per plan, one tile per phase **in phase order** (because order is time), rows grouped by sub-program with its owner, plans, approved items done and score, and a legend that counts every state. A tile carries three channels at most:

| Channel | Says | How it reads without color |
| --- | --- | --- |
| **Hue and edge** | The phase’s state: done (solid neutral), in flight (a blue edge), proof failing (red), ready (a thin border), blocked or an ownership gap (a gray border), drafted (dashed), outline (dotted, faint) | Dash and dot patterns, and a glyph |
| **Fill** | The share of the phase’s items done, rising from the bottom, so a phase at 3 of 51 and one at 50 of 51 no longer look alike | Height |
| **Ring** | Something in the phase waits on a person: a decision, an approval, a confirmation or a [person task](https://frazer.ai/docs/source/person-tasks); amber when it is you, gray when it is someone else | The ring’s shape, and the legend’s count |

One glyph marks an exception on tiles of 11 px or more (✕ proof failing, ‖ blocked or held), a 3 px dot marks a change since you last looked, and ◆ marks the critical path. Amber means a person and nothing else; red means a failing proof and nothing else; blue is work running.

-   **A plan row** is one line: key, title, squad, score, tiles, the p85 of its next milestone (in bright text when after the target; amber is only ever a person), how many things need a person, and a 30-day sparkline of items done a day; a plan with a running [loop](https://frazer.ai/docs/source/planning-flow/loops) adds its badge (“loop · turn 3 · 2 open”).
-   **The legend is a count line**: every count is a filter, and “Needs a person 16” dims every other tile. It reads the same snapshot as the readout, so the numbers agree.
-   **Controls** on one line: Tile (plan, phase, section, item), Color by (state, health, score, squad, age, changed, model family, risk class, readiness, freshness), Size by (equal tiles by default, because equal cells can be counted; or by items, remaining estimate or spend, which turns each row into an ordered strip), Group by, Show (all, needs a person, changed, critical path, parallel now), Fold drafts, As of (a time scrubber over the event log) and density.
-   **Parallel now** draws the items that can be taken now against the items waiting on the same node, so “everyone is blocked on one item” shows at a glance, and marks each phase parallel-safe (its items write disjoint paths) or serial, which dispatch follows.

**A tile stands for whatever fits.** The Map picks the deepest level whose tiles fit the screen at a 13 px pitch; `1` to `9` step through the levels and `0` shows them all, a click zooms into a subtree, `Esc` zooms out:

| Tile size | What a tile shows |
| --- | --- |
| 11 px and more | Hue, fill, ring, glyph, change dot and ◆; a label (“P9”) from 24 px |
| 7 to 10 px | Hue, fill and ring; glyphs move to the row |
| 3 to 6 px | Hue only, with failures and “needs a person” drawn as marks on top, so they still stand out |
| Under 3 px | A cell is a unit of items, stated in the legend (“1 cell = 25 items”) |

At the shape of `pellwyn/plans#120` (20 plans, 800 phases, 20,160 items) the Map opens on phases and shows all of it; **Tile: Item** draws every item as a 3 px cell in phase order inside its plan’s block. At 200,000 items it opens on plans sized by items and zooms into a sub-program; at 2,000,000 items it opens on 2,000 plan tiles in strips. Hover or `Space` shows a phase’s card from its stored rows (“#405 P9 · Dual-write parity for 7 days · approved · on the critical path · 3 of 51 items done · PayoutParity fails 2 of 50 runs · milestone #146: p85 Dec 17 against Dec 11 · waits on: nobody”); arrows move tile to tile; `n` jumps to the next tile that needs you, `f` to the next failing one, `c` along the critical path.

**Accessible by construction.** Fill, rings, glyphs and patterns carry every state, never color alone; every mark keeps 3:1 contrast in every theme; the whole Map works from the keyboard; screen readers get a treegrid mirror (sub-program, plan, phase) with a summary at each level (“Map, 800 phases in 20 plans; 16 need a person, 11 of them you; 6 failing”); tiles under 24 px are never the only way to a phase; zoom has no animation under reduced motion, and forced colors keep the glyphs. Tiles draw on one canvas while labels, rows and the treegrid are virtualized DOM, so the Map is designed to paint 800 tiles within 50 ms of its data and 20,160 item cells within 100 ms, to recolor or filter within 16 ms, and to show a hover card within 50 ms.

## The readout and Do next

Above the Map, and on Today, two things say what to do and how it is going. Every number comes from stored rows, never a model’s guess, and its source is one click away; a model may write a cited narrative of them, never a number.

| Cell | Shows (priya on #120, demo data) | From |
| --- | --- | --- |
| **Needs you** | “14 · Decide 9 · Approve 1 · Do 2 · Confirm 1 · Update 1” | Decision rows, approvals, person tasks, program amendments and milestone updates for you |
| **Progress** | “903 of 3,760 approved items done · 24 % · +758 in 24 h” at 10:30, with a 30-day sparkline and “16,400 drafted, not counted” | Counters: items done with their proofs |
| **Forecast** | “3 of 40 milestones at risk · #146 p85 Dec 17 > Dec 11” | Forecasts; drafted phases never count in one |
| **Risks** | “critical path −3 d · payouts 112 % · 1 break held · 4 person tasks hold 70 items” | Insights |
| **Score** | “86 · lowest #409, 61 · 16 of 20 plans ready to execute” | The plan score per node |

Each cell opens its definition, its query and the rows behind it (“from stored rows · as of seq 91,207”); `get_readout` returns the same cells as data.

### Do next

For each thing you can do now (decide, approve, a person task, confirm, update), Fir reruns the program’s forecast with that wait answered now and ranks the actions by **days saved at p85 on milestones at risk**, then by **act by** (the latest day to start it), then by items released; nothing is ranked by age alone. Each row says its effect, its date and its one action. Demo (priya, Oct 17 at 15:00; the effects are forecasts):

| # | Action | Effect | Act by | Key |
| --- | --- | --- | --- | --- |
| 1 | **Decide #312** “Tax on a reversal” (it needs counsel’s opinion, a task 2 days late, or decide without it) | #149 p85 Oct 27 → Oct 20, back on track for its Oct 23 target; two other milestones 8 to 9 days earlier | now | `D` |
| 2 | **Buy two IPv4 /24 blocks** (`PT-21451`, the cart is ready) | None at p85 if bought now; the 31 items need the blocks by Nov 6 and the transfer takes up to 15 working days | Oct 16 (1 day late) | `A`, with a passkey |
| 3 | **Sign the partner bank’s sandbox agreement** (`PT-21452`) | Its 18 items are next in fx’s queue; #430 moves only if it waits more than 21 days | today | `↵` |
| 4 | **Confirm the rebalance** of #406 P12 to P15 to admin-audit | payouts 112 % → 97 %, admin-audit 43 % → 61 %, shown with the receiving squad’s own milestones | — | `C` |
| 5 | **Approve #418 P5 to P8** (the cold review passed, score 94) | Needed by Jan 2 | Jan 2 | `A` |
| 6 | **Update #146** | — | weekly | `U` |

**Forecasts with people in them.** Each run samples every squad’s daily throughput from its last 30 days, works the squads’ ready phases in dispatch order up to their work-in-progress limits, and starts a phase when its dependencies are done and its waits are answered. A milestone that needs several paths is late if any one is, so its date comes from the network, not one path. Every open wait (a decision, an approval, a person task, a contract still in draft) is a delay drawn from that kind’s history in your organization, plus the outside lead time (a registry, a bank, a vendor); without history it uses a labeled default. Doing it now removes the person’s part only, never the lead time. A counterfactual reuses each run’s random numbers, so its difference from the baseline is the action’s effect, not noise. From the same runs come a person task’s **needed by** and **act by** dates, and each wait’s **slack**: the delay at which some milestone’s p85 first moves by 2 days or more. When a milestone’s p85 moves, the readout splits the change into its causes (the squads’ throughput, waits on people, dependencies and scope). The program’s forecast and each action’s effect are cached by subtree and recomputed only for what a merge touches, designed to update within 250 ms at p95 after a merge in scope.

## Work only a person can do, on every view

A [person task](https://frazer.ai/docs/source/person-tasks) is a plan item whose assignee is a person, so it sits in the tree, counts in rollups and blocks by ordinary edges. Every program view shows it the same way:

| Where | What it shows |
| --- | --- |
| **Map** | A ring on the tile whose subtree holds an open task, amber when it is you, counted in the legend; the hover card names it (“PT-21451 · Buy two IPv4 /24 blocks · priya · blocks 31 items · act by Oct 16, 1 day late”) |
| **Today** | The **Do** group in Needs you: kind, handle, title, plan and item, what it blocks, act by, effort, and one action (`↵` opens it with the action focused; a cart is `A` with a passkey) |
| **Do next** | Person tasks ranked with decisions and approvals by their computed effect |
| **Waiting on people** | Who owes what, since when, what it blocks and act by, grouped by person and kind |
| **Outline and Table** | A **Waits on** column (“priya · buy · act by Oct 16, 1 day late”) and the filters `waits:person`, `waits:@priya` and `kind:credential` |
| **Live** | Sessions parked on a person task, as exceptions (“s-5c34 parked · waits on PT-21455 · 6 items”) |
| **Rollups** | An additive `waiting_items` counter on every node: “4 person tasks hold 70 items in 4 squads” |

Agents read the same rows with `list_waiting`.

## Lists at 1,000 and 10,000

Every program list follows the rule for lists at scale: counts first, then what needs you, then groups that roll up, then rows.

| View | At 20 plans of 40 phases (20,160 items) |
| --- | --- |
| **Outline** | A count line (“800 phases · 16 need a person (11 you) · 6 failing · 9 blocked · 97 in flight · 23 changed”); groups by sub-program that roll up, at most two levels open on screen; 800 phase rows virtualized |
| **Table** | One line per node, at most seven columns (state, key, title, plan, Next, actor, age; a picker adds squad, waits on, milestone or score), 20,160 items virtualized; the server sorts and groups past 100,000 |
| **Board** | At program scope the cards are phases, not items: 800 cards in columns by state with counts; a column of more than 50 cards shows its first 50 in dispatch order and “+2,248 ready” |
| **Today** | Needs you grouped by kind, with decision groups; Do next; Waiting on people; the readout; and Since you last looked at the program’s rate, routine events as counts and exceptions one by one |

Rows come in three densities, chosen and remembered per person and list: **Comfortable** (two lines, 48 px), **Standard** (one line, 36 px) and **Compact** (one line, 28 px). Bulk actions do only what each row already allows you: “Approve P5 to P8 of #418” approves one wave you approve, never phases of two plans at once, never R3.

## Insights

Every insight is a named query over rows Fir already writes (links, counters, check results, versions, events), with its definition, its threshold and who is told; none is a model’s estimate. Every insight runs at any node and in any lens.

| Insight | What it counts |
| --- | --- |
| Requirement coverage | Requirements with an implementing merge, and with a passing proof at a merged version |
| Item proof state | Items merged without a passing proof, and failing proofs |
| Plan coverage of work | Merges with a plan, exempt by class, or with none |
| Link coverage | Merged pull requests and lines accounted for ([Plan repositories](https://frazer.ai/docs/source/plans#link-coverage)) |
| Drift | Suspect links, failing decision checks, references that broke |
| Stale records | Records past their freshness window, or whose covered code changed |
| Risk | Open risks by impact; high-impact risks without a mitigating plan |
| Forecast | Milestones whose p85 falls after the target |
| Decision debt | Open decisions by age and by what they block; decisions in force with no check |
| Approval wait | Time plans, phases and layers wait for people, by class and approver |
| Budget burn | Spend against each plan’s budget and tolerance |
| Scope change | Items added and material versions after approval |
| Dependency health | Blocked chains and the critical path’s slack |
| Unplanned work | Merges and incidents outside any plan |
| Critical path across plans | The longest chain of remaining work across plans, with the squads it crosses |
| Cross-squad blockers | Items blocked by a node another squad owns |
| Contract health | Breaking changes held, consumers behind, deprecations past due |
| Capacity | Load against capacity per squad for the next two weeks |
| Plan score | Each node’s score, its lowest child, plans ready to execute, scores that fell this week |
| Aging work | Items in progress longer than their class’s usual cycle time |
| Outline debt | Phases still outline or drafted that start within 14 days |
| Ownership gaps | Nodes with no squad, or whose squad owns none of their paths |
| Hand-off churn | Items re-dispatched twice or more |
| Model mix | Items merged and kept by model family and agent revision, per risk class |
| Integration readiness | Integration milestones and how many of their requirements pass |

An insight changes only when a merge, a check result, a dispatch or a record version does, so Fir recomputes it in that event, never on a timer. A model may write a labeled summary of insights, billed as tokens.

## Notifications

-   **Only what needs you, now:** an approval or decision you decide, a failing proof on your plan, a milestone you own turning at risk. Everything else waits for **one digest a day**, and an empty digest is not sent.
-   **Follow what you choose:** a record, a milestone, a program, a view, an insight, or a subtree with a condition (“anything under #122 turns at risk”).
-   **Volume does not grow with size:** events about one subtree within 10 minutes arrive as one line with a count, and immediate notifications per person are capped (12 a day by default), so a person on a 20,000-item program gets as few as on a 200-item one. Agents’ steps never notify people.

An issue becomes a plan item when someone commits to doing it under a plan (`planned_in`); it keeps its thread and closes when the item is done. Intake, bug reports and questions stay issues, and a program with milestones replaces a project board where the work needs approval, proofs or several repositories.

## Milestones

A milestone is a record with a **target** (a date, a release, or both), a **scope** and a goal with its done-when. It schedules work; it never authorizes it, because approval stays on plans.

```yaml
---
title: Peak-ready checkout
target: {date: 2026-11-20, release: storefront 2026.11}
owner: people/lena@acme.example
scope:
  plans: [acme/storefront#469, acme/mobile#212, acme/infra#88]
  issues: [acme/storefront#479]
---
## Goal
Checkout holds at peak: no double charges, no false failures.
## Done when
Every plan in scope is done and storefront 2026.11 is released to production.
```

-   **Progress is written by merges, never typed.** An item counts as done when its layer merged **and** its proof passed at a merged version. The counters are stored in the merge’s transaction, so a milestone page is exact the moment a merge becomes visible.
-   **At any level.** `scope` may name programs, plans, phases, sections or items (`scope: {nodes: ["#405 P9", "#407 P6"]}`); a node in several milestones counts in each.
-   **A forecast over the phase graph, with its inputs.** Each squad’s remaining work runs against its own last 30 days of throughput, with every wait on a person drawn from that kind’s history ([Forecasts with people in them](#do-next)); the milestone’s p50 and p85 come from the runs, and the page names what sets the date: often calendar work (machine time, soaks, rehearsal windows, deliveries, outside lead times) rather than a squad. Until forecasts have held on closed milestones, they are labeled “forecast (unverified)”.
-   **At risk is computed**, with each reason listed: the p85 date falls after the target, a plan in scope is blocked or stale, or an approval it waits on is past its SLA.
-   **Updates are drafted, then posted.** An agent drafts an update in which every sentence cites its source; a person posts it, and the health they choose shows beside the computed forecast, never in its place.

An **integration milestone** is where several squads’ work must meet. Each requirement is checked like a checklist item, and the page shows “Integration: 2 of 3 met”:

```yaml
requires:
  contracts: [{contract: "pellwyn/plans#460", version: "3", consumers: ["#405", "#407"]}]
  checks: ["pellwyn/payouts:PayoutParity@main@verified", "pellwyn/driver-pay:LedgerTotals@main@verified"]
  nodes_done: ["#405 P9", "#407 P6", "#401 P8"]
```

A phase’s **exit checklist** is the condition to leave it (“phase 9 exits when its items are done and PayoutParity has passed for 7 days”); a milestone is a date or release cut across the tree. A phase may have both.

## Many squads, many models

-   **Squads own subtrees.** Every node names a squad, inherited down the tree unless a node sets its own. The squad that owns a subtree may restructure it; a move out of it needs the receiving squad’s owner too. Code ownership stays with `OWNERS`: an item goes to the squad that owns its node and most of its paths, and when the two disagree the item is an **ownership gap**, shown with the fix.
-   **Plans meet at contracts.** Plans that depend on each other meet at a `contract` record: the producing plan `provides` a version, the consuming plan `consumes` a range (`">=3 <5"`), and the dependency goes through the contract, so it is checked. A contract in `collaboration` mode needs both squads for a change; in `service` mode the provider changes it under its compatibility policy.
-   **Dependencies are checked, not only written down.** Fir finds a missing target or a cycle, a dependent that starts before its dependency’s p85, a contract version still in draft, a **missing edge** (one plan’s layers use symbols another plan’s open layers add: “add a dependency?”, one key) and an **unused edge** (suggested for removal).
-   **A break is held, never silent.** When a layer changes a stable contract, every consumer’s contract tests run against its speculative merge in the merge queue; if one fails, the layer is held with the consumers named (“contract #460 v4: 2 of 5 consumers fail”) and both squads’ accountable people get one decision with three options: stay compatible, publish a new version beside the old one, or coordinate both changes in one stack.
-   **People and models, recorded.** A squad is people with roles plus agents of several model families, with a verifier of another family than the builder. Which agent revision and model family delivered each item, and whether it was kept 30 days after merge, is stored, so the model mix is evidence.
-   **Balanced on evidence.** When one squad is above 100 % of its capacity and another below 60 %, Fir suggests moving **unstarted subtrees** whose paths the other squad owns (“Move #406 P12 to P15 to admin-audit: payouts 112 % → 97 %”), as an amendment the program’s owner confirms with one key. Never automatic, and never mid-item: a claimed item keeps its lease.

The program’s Overview is one screen: the readout, Do next, the Map, a squads strip, milestones from p50 to p85 with what sets each date, the critical path, cross-squad blockers, contract health, and the plans ready to execute with the lowest scores.

## Agent interface

| Tool | Returns or does | Toolset |
| --- | --- | --- |
| `get_program` | The concise form: levels, owners, rollups, what is at risk, the critical path, in about 1,500 tokens | Default |
| `list_ready` | Approved, unblocked, unclaimed items your squad may take now, each with its paths, criteria and boundaries | Default |
| `get_subtree`, `list_nodes` | A subtree’s nodes with keys, states and rollups; nodes by the filter syntax | `records` |
| `move_node`, `split_node`, `promote_node`, `merge_nodes` | Tree operations with `expected_version`, refused with a typed reason (`cycle`, `not_owner`, `outside_terms`) | `records` |
| `get_rollup`, `get_view`, `list_views`, `get_insights` | As the screens show them | `records` |
| `get_today` | Today for a person, a program or a subtree | `records` |
| `get_contract_status`, `get_squad_load` | Versions, consumers, the verification matrix and held breaks; load against capacity | `records` |
| `get_changes` | Tree operations and text changes since a version, with their reasons and approving versions | `records` |
| `get_milestone`, `draft_milestone_update` | Progress, the forecast with its inputs and risk reasons; an update with a citation per sentence | `records` |
| `get_readout` | The readout’s five cells for a node, with each cell’s definition and query; `fields: ["position"]` answers “where is program X” in one call: one line per program with the phase per plan, done of approved, the plan line against last and first approval, the p85 against the target, what needs a person and the last re-plan, each number with its source | Default |
| `get_baseline`, `compare_baselines`, `list_replans` | A node’s baseline at a version or a time; only what differs between two; the re-plans since a time, each with its reason, approver and the milestones it moved | `records` |
| `list_next` | Do next for a person and a scope: each action with its computed effect, act by and key | Default |
| `list_waiting` | What waits on people in a program, by person and kind, with what each blocks and act by | Default |

Watch a subtree’s resource, `records/subtree/<node>`, through `subscriptions/listen` to learn that a phase moved, a date was re-planned or a contract broke without polling. Tree operations and re-plans carry `idempotency_key` beside `expected_version`, and `dry_run: true` returns the change kind, the approvals needed, the milestones that would move and the cost, writing nothing. Agents read one plan, one phase or the program’s concise form, never the whole tree:

```text
pellwyn/plans#120 "Real-time ledger" v10 · owner priya · as of seq 91,207
levels program › sub-program › plan › phase › section › item
items 20,160 · done 903 · in flight 471 · approved, not started 2,386 (88 claimed) · waiting for approval 16,400
at risk #146 (p85 Dec 17 > Dec 11), #149 (decision #312 past due), #432
critical path #403 P11 → #401 P8 → #405 P9 → #407 P6 → #146 (slack −3 d)
blocked contract #460 v4 held (2 of 5 consumers fail) · decision #312 (14 items)
score 86 · lowest #409 (61) · 16 of 20 plans ready to execute
your scope squad payouts · #405, #406 · 112 % of capacity
```

```sh
fir program view pellwyn/plans#120 --depth 3 --lens squad
fir program ready --squad payouts
fir program where pellwyn/plans#600
fir program baseline pellwyn/plans#641 --replans
fir node move "#406 P12" --to-squad admin-audit
fir node log n-9b21
fir milestone view 50
fir milestone update 50 --draft
```

The API is `ProgramService` in `frazer.records.v1` (`GetProgram`, `GetSubtree`, `ListNodes`, `ListReady`, `ApplyTreeOperations`, `GetSquadLoad`). Events include `records.node.moved`, `records.node.promoted`, `records.tree.conflict`, `records.contract.break_held`, `records.plan.replanned`, `records.milestone.target_moved` and `records.milestone.forecast_changed`, each with its sequence number.

> **Open, no lock-in:** The tree is files: plan files and part files in Markdown with node IDs in the text, and an export of nodes, aliases and links as JSON beside them. Imports keep the depth of the hierarchies you bring from other trackers, and their baselines: a schedule file’s saved baselines, a tracker’s target-date history and dated snapshots arrive as baseline records marked as imported.

## Related

-   [Person tasks](https://frazer.ai/docs/source/person-tasks): what only a person can do, and how it shows on the Map and in Today
-   [Plan repositories](https://frazer.ai/docs/source/plans): homes, links and Why
-   [Record types](https://frazer.ai/docs/source/record-types): levels and the type language
-   [The planning flow](https://frazer.ai/docs/source/planning-flow): approvals in waves, dispatch and the plan score, and [loops](https://frazer.ai/docs/source/planning-flow/loops) and polish waves
-   [Agents and plans](https://frazer.ai/docs/for-agents/plans): claims, leases and progress at program scale
-   [Stacks and conflicts](https://frazer.ai/docs/source/stacks): contract breaks in the merge queue

## Pages in Fir

- [Overview](https://frazer.ai/docs/source.md)
- [Plan repositories](https://frazer.ai/docs/source/plans.md)
- [Record types](https://frazer.ai/docs/source/record-types.md)
- [Programs](https://frazer.ai/docs/source/programs.md) (this page)
- [Person tasks](https://frazer.ai/docs/source/person-tasks.md)
- [Decisions](https://frazer.ai/docs/source/decisions.md)
- [Decide by conversation](https://frazer.ai/docs/source/decisions/conversation.md)
- [The planning flow](https://frazer.ai/docs/source/planning-flow.md)
- [Loops and learned checks](https://frazer.ai/docs/source/planning-flow/loops.md)
- [Stacks and conflicts](https://frazer.ai/docs/source/stacks.md)
- [Auto-merge and the merge queue](https://frazer.ai/docs/source/merge-queue.md)
- [Sessions](https://frazer.ai/docs/sessions.md)
- [Issues](https://frazer.ai/docs/source/issues.md)
