---
title: Stages
description: The three kinds of stage in RoutineOS. Lifecycle stages a routine moves through, phases a run writes in, and workflow stages a pack declares.
sidebar:
  order: 5
---

RoutineOS uses "stage" in three places. They nest: a routine moves through lifecycle stages; once live, each run follows its workflow stages; and the write at the end of each run lands in phases.

| Kind | Scope | Where it is declared |
|---|---|---|
| Lifecycle stage | The whole life of a routine | The RoutineOS skill |
| Workflow stage | One run's work, in order | `stages` in a routine pack (since 0.6.0), and the Steps in `prompt.md` |
| Run phase | One run's store write | The write contract |

## Lifecycle stages

Discover (optional), Design, Build, Verify, Deploy, Operate, Improve, Retire. Each has exit evidence. See [Routines](/concepts/routines#the-lifecycle).

## Workflow stages

> Since 0.6.0 (routine packs). The prompt's Steps exist today; the `stages` list is part of the pack format.

### What it is

A workflow stage is one step of a run, with a kind that says who does it.

| Kind | Who does it | Needs |
|---|---|---|
| `code` | A deterministic command | `command`: one line starting `python3 `, naming `skills/routineos/scripts/routineos.py` or a `.py` file the pack ships under `lib/` |
| `model` | The model, guided by the prompt | nothing extra |
| `skill` | The model, guided by a skill file shipped in the pack | `skill`: a name with `skills/<name>/SKILL.md` in the pack's files |
| `gate` | A person approves before the next stage | `routine.gate.enabled = true` |

A stage is `{id, kind, title, does, connectors?, reads?, writes?, command?, skill?}`. `connectors` names connector ids declared in the pack. `reads` and `writes` name store collections and may never name a people collection. Stage ids are unique slugs. The prompt's Steps follow the stage order; that is a review rule, not a parsed one.

### Why it exists

Stages make the split between code and the model visible before anything runs. A reader can see that filtering and writing are code, searching and judging are the model, and sending waits for a person.

### Worked example

Hiring Signals' prompt has seven Steps. As workflow stages they read:

| Stage | Kind | Does |
|---|---|---|
| `search` | model | Search each market for postings inside the lookback window |
| `record` | model | Record each role and its company, with a short summary |
| `inbox` | model | Pick the roles worth a look today, within `max_inbox_per_run` |
| `intro-gate` | gate | Any intro note waits for approval by email, 48 hours |
| `build-input` | model | Build the run input for the write contract |
| `learn` | code | `python3 skills/routineos/scripts/routineos.py learn ...` |
| `write` | code | `python3 skills/routineos/scripts/routineos.py write ...` |

The stage ids above illustrate the shape; they are not copied from a shipped pack. When the `jobs-watch` pack lands, its `pack.json` holds the authoritative list.

## Run phases

### What it is

Every run's writes are planned in order, as batches, each tagged with a phase. A batch is applied whole, in order.

| Phase | Writes |
|---|---|
| `seed` | One-time setup: `meta/workspace`, `meta/definition`, default settings (written by `seed`, not by runs) |
| `learning` | `state/learning`, the tallies (written by `learn`, applied before `write`) |
| `started` | `runs/<runId>` with `phase: "started"` |
| `entities` | Every record seen, with a new sighting |
| `summary` | `runs/<runId>` with counts, drops, settings used; agent views and columns created if absent |
| `inbox` | `pending/<id>` gates, then `inbox/<runId>--<n>` messages |
| `done` | `runs/<runId>` with `phase: "done"` |

Empty phases are skipped.

### Why it exists

A run can fail halfway. Because the run document records its phase, a half-applied run re-planned from a fresh snapshot finishes in the same state as a clean run, and a rerun of a run already marked done is a no-op. The Desk can also show a run that started and never finished as failed or late.

### Worked example

The Quickstart run produced five batches: `started`, `entities` (three records), `summary`, `inbox` (one message) and `done`. See [Quickstart](/introduction/quickstart#6-plan-a-runs-writes).

## Common pitfalls

- **Appending proposals after write.** The order is learn, append, write. A run already marked done writes nothing more, so proposals appended later are lost.
- **Applying batches out of order.** Apply `batch-001.json` first. The inbox phase writes gates before messages so a message never points at a missing gate.
- **Treating a stage as a security boundary.** A `gate` stage is a review point; the write contract and lint are what enforce rules.

## Related

- [The store and the write contract](/concepts/store-and-write-contract)
- [Routine kinds](/routine-kinds)
- [Pack schema reference](/reference/pack-schema)
