# Lanes and gates

How triage picks a lane, how to override it, and how your rules hold Factory work for a person at the moments that matter.

A lane is how much ceremony a piece of work gets on its way to main. A gate is a point where an item waits for a person, or for your rules to let it through. Together they decide where people sign off, and where the line runs on its own.

## The three lanes

| | Express | Standard | Assured |
|---|---|---|---|
| **For** | Trivial and low risk | Most work | Risky or important work |
| **Shape** | None | A plan a person approves | A full spec; a person approves each phase |
| **Assurance level** | L1 | L1 | L2 |
| **Build** | Sandboxed run, starts by itself | Chat in a worktree, you start it | Chat in a worktree, you start it |
| **Review** | Policy engine | A person | A person |
| **Review against the original ask** | From L2 | Off | On |
| **Merge** | When policy allows | A person | A person |
| **Triage may auto-accept** | Yes | No | No |

Assurance levels (L0 to L4) say how much proof a change needs. See [Assurance levels](assurance).

Every column can be changed per project in **Customize → Lanes**: *Shape* (None, Plan, Spec or Triage decides), *Level* (L0 to L4 or Triage decides), *Build*, whether it *starts by itself*, *Review* (A person, Policy engine or None), *Review against the original ask*, *Deliver* (Merge here or Open a pull request), *Merge* (A person or When policy allows), *Triage may auto-accept*, and *How it builds* (like the Build stage, or its own model, skill and instructions).

## How a lane is picked

Triage picks **the lightest lane that is safe**. Work touching any of these never goes to the lightest lane: authentication, permissions, migrations or schemas, payments, secrets, infrastructure or CI, public APIs, security.

With **Pick the lane by size** on (the default, in the Triage stage's settings):

- **One-file shortcut.** A short ask (up to 280 characters) that names exactly one existing file, isn't high-risk and doesn't use words that suggest risk or design work skips triage and goes straight to the lightest lane, with no shaping.
- **Confident triage.** An accept with medium or high confidence takes triage's lane with nobody asked. The item notes why, for example *by size: S -> Standard*.
- **Unsure or rejected.** Anything else waits for a person in **Needs you**.

Before an automatically picked item starts building, you get a 20-second window: *Building in Ns: change the lane to stop it*.

> [!WARNING] Small work can ship with nobody asked
> With the defaults, a confident Express item builds in a sandbox, passes the policy review and merges locally with no person involved. That is the point of Express, and your rules still check every step. If you want a person on every change, use the **Regulated** template, set Express's merge to *A person*, or turn off **Pick the lane by size**.

## Overriding the lane

- **At triage:** the item's drawer shows the lane, shape and level pickers with **Accept as ‹lane›**, **Needs info**, **Reject** and **Run triage**.
- **After acceptance:** **Change lane** works until the build starts.
- **With rules:** **Customize → Who decides** adds rules that change review, merge, auto-start or the minimum level for matching items, by size, kind, lane or path. They apply in this order: lane defaults, then triage's choice, then your rules in order, then path rules from your policy (always enforced). Levels only ever go up.

## Gates: your rules at every step

Every move the Factory makes is checked live by the policy engine, the same engine that checks your agent's commands. The checked moves are **start the build**, **review**, **merge** and **open a pull request**. Each check gets facts about the item: who is making the move (a person or the Factory), the lane, the files changed, whether migrations are touched, and the time.

| Verdict | What happens to the item |
|---|---|
| {{allow}} | It moves on. |
| {{ask}} | It is held for a person. If you pressed the button yourself, you get one confirmation naming the rule. |
| {{deny}} | It is held, with **Allow once** and **Always allow this exact action**. Company rules can't be waived. |
| Engine offline | Nothing moves. Press **Retry**. |

A held item shows a card with the **Rule**, **Why**, **To satisfy it** and **Who can override**, plus **Approve and merge** (or start the build, or open the PR) and **Send back to the agent**.

For an unattended review or merge, Warden also checks every changed file as an automated write, and the merge command itself, against your rules. A change with more than 300 changed files, or with none, is held.

### Ready-made lifecycle rules

**Customize → Lifecycle rules** has templates, all off until you turn them on:

| Rule | What it does |
|---|---|
| **Merges are a person's call** | The Factory never merges by itself, a person confirms every merge, and agents in chats ask before `git merge`, `gh pr merge` or a push to main. |
| **Migrations need a person** | A change that touches database migrations (`migrations/`, `migrate/`, `alembic/` or `*.sql`) waits for a person before merge. |
| **No unattended merge after hours or on Fridays** | The Factory merges by itself only Monday to Thursday, 09:00 to 18:00 local time. A person can still merge. |
| **Code changes need tests** | A change that touches code but no test waits for a person at review and merge. The approval is the waiver. |
| **Large changes need a person** | A change of more than 20 files waits for a person before merge. |
| **Link a ticket before merge** | An item with no linked issue or ticket (its source, or `#123`, `ABC-123` or an issue link in its text) waits before merge. |

### Write your own Factory rule

The templates are ordinary rules on the `Factory` tool, so you can write your own in your [rule file](writing-rules). Each check carries these facts:

| Fact | Values | Meaning |
|---|---|---|
| `factory_transition` | `start_build`, `review`, `merge`, `open_pr` | The move being made |
| `factory_by` | `person`, `factory` | A person's click, or the Factory on its own |
| `factory_lane` | `express`, `standard`, `assured`, or your lane's id | The item's lane |
| `factory_level` | `L1` to `L4` | The assurance level it is built to |
| `factory_shape` | `plan`, `spec`, `chat`, `pr` | How the work was shaped |
| `factory_kind` | `feature`, `bug`, `chore` | What kind of item it is |
| `factory_size` | `small`, `medium`, `large` | Up to 5 files, up to 20, more |
| `factory_files`, `factory_lines` | a number | Files changed; lines added plus deleted |
| `factory_code_touched`, `factory_tests_touched` | `"true"`, `"false"` | A non-test code file changed; a test file changed |
| `factory_migrations_touched` | `"true"`, `"false"` | `migrations/`, `migrate/`, `alembic/` or `*.sql` changed |
| `factory_ticket` | `"true"`, `"false"` | The item links an issue or ticket |
| `factory_verified` | `"true"`, `"false"` | Verify passed on the branch's current commit |
| `factory_weekday` | `mon` to `sun` | Local day |
| `factory_working_hours` | `"true"`, `"false"` | Monday to Friday, 09:00 to 18:00 local |

```yaml title="Hold unverified Express merges, block big unattended bug fixes"
rules:
  - ask: { tool: Factory }
    when: { factory_transition: merge, factory_lane: express, factory_verified: "false" }
    reason: "Express merges need Verify to pass on this commit"
  - deny: { tool: Factory }
    when: { factory_transition: start_build, factory_by: factory, factory_kind: bug, factory_size: large }
    reason: "Large bug fixes are started by a person"
```

Quote `"true"` and `"false"`: unquoted, YAML reads them as booleans.

### Paths that always need a person

**Customize → Who decides → Always needs a person** takes path patterns such as `*/migrations/*`, `*/auth/*` or `*/.github/workflows/*`. They become rules in your policy that apply only to automated runs:

```yaml title="What Warden writes for you"
rules:
  - ask: { path: "*/migrations/*" }
    when: { trigger_mode: automated }
    reason: "Software Factory: a person reviews changes here"
```

An unattended build that tries to edit one of those paths is held, and so is a merge that touches one. You aren't prompted for them when you work in a chat yourself. See [Automated runs](automated-runs) for the general version.
