# Plans and the build loop

> How a Switchboard plan is proposed, revised in conversation, approved step by step, signed, checked and built.

Source: https://dev.ody.co/switchboard/plans

> **Private preview:**
In-app only, for allowlisted workspaces. Nothing on this page is reachable with an API key.

## Anatomy of a plan

| Part | Written by | What it is |
|---|---|---|
| Outcome | Model | One sentence: what will be true after the build |
| Assumptions ("I assumed") | Model | What it inferred, so the admin can correct it |
| Questions | Model, then Ody | Blocking questions. Ody adds follow-ups when a required detail is missing |
| Steps | Model proposes, **code validates** | Each step has an `action` from the [catalog](https://dev.ody.co/switchboard/actions), its params, a `why`, the part of the request it comes from (`traces_to`), and its dependencies |
| Risk, reversibility, cost, plan gate | **Code only** | Looked up from the catalog, so the model can't play down a purchase |
| Before → after | Code | A line diff of the affected account settings |
| Signature | Code, at approval | HMAC over the frozen steps. Never returned to the browser |

A step whose `traces_to` doesn't quote the request (or start with `platform rule:`), or whose action isn't in the catalog, is dropped before the admin sees it. Plans are capped at 25 write steps and 10 numbers.

## Talking it through

Every message after the first is a **revision**. The model re-plans with the whole conversation and the current plan:

- *"Make that 30 seconds"* changes one step. Steps whose action and params are byte-for-byte identical keep their approved or skipped decision, so the admin re-approves only what changed.
- *"When I said Luis I meant Jordan"* replaces that detail and nothing else.
- Answering a blocking question works the same way, with the answer attached to the question.

Every revision drops the signature and bumps the plan's internal version.

## Approving

- **Per step:** approve, skip or reset. Skipping a step also skips the steps that depend on it ("Skipped with step 2"). Approving it again brings them back.
- **Approve all:** approves every undecided step, then the plan.
- **New-number steps** show a number picker, and the admin chooses which number to buy from the ones the plan shows.
- If a step needs a higher plan, Switchboard adds an `upgrade_plan` step itself. The model never proposes plan changes on its own.

Approving the plan signs it: HMAC-SHA256 over the plan ID, workspace ID, version, a hash of the before-snapshot, the approver's user ID, and every step's ID, action, params, decision and dependencies.

## Checking

**Check plan** runs every approved step's catalog check in order, with each check seeing the steps before it, and writes nothing. The result is either "Checked · ready to build" or "The check found a problem. Nothing was changed."

## Building

**Build** runs only what was signed, and only for the admin who approved it:

1. The plan row is atomically moved to `building`. A second click gets "already being built" and writes nothing.
2. Before each step, the objects it touches are compared with the before-snapshot. If a teammate changed one in the meantime, the build **pauses** and names it rather than overwriting.
3. Each step calls the same function the Settings screen calls, as the approving admin.
4. After each write, every number's routing is read back. A number left forwarding nowhere, pointing at an unpublished flow, or routed to a missing agent **stops** the build.

A step interrupted mid-write isn't retried automatically, because retrying a purchase could buy twice. The admin is asked to check Settings and start a new plan.

## Plan statuses

| Status | Meaning |
|---|---|
| `proposed` | Drafted, not approved |
| `approved` | Signed |
| `dry_run_ok` / `dry_run_failed` | Checked |
| `building` | Running |
| `paused` | Stopped on a concurrency conflict. Can resume |
| `built` / `failed` / `cancelled` | Finished |

Every step of the loop is written to an append-only audit trail that the app's own database role can't edit or delete.
