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.
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.
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 |
|---|---|
| RUNS | It moves on. |
| ASKS YOU | It is held for a person. If you pressed the button yourself, you get one confirmation naming the rule. |
| BLOCKED | 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. 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 |
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:
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 for the general version.