Skip to content

Pipelines — the stages work travels ​

Load when: setting up how a kind of work moves, changing a ladder, or deciding what must be true before something advances.

A pipeline is the stage sequence a task travels. An automation is recurring or triggered work that starts something. These were briefly the same word, which would have been expensive: one describes a path, the other fires → automations.

The file ​

yaml
name: design
stages: [brief, explore, draft, review, handoff]
terminal: [handoff]
gates:
  draft->review:
    check: "_ops/scripts/check-screens.py"   # mechanical — a command that must exit clean
  review->handoff:
    review_by: non-author               # a review in History from someone who did not draw them
    fields: [evidence]                  # fields that must be present on the task
default_for: [design-task]
starts: on-completion

The body says what this pipeline is for and when to run it — the same why discipline as resources and declared fields. "For visual work without code: landing pages, screens, identity. Not for research, which has its own."

Stages are linear. No branches, no conditional paths, no parallel lanes. Parallelism is children sharing a wave → decomposing. A branching pipeline is two pipelines that have not admitted it yet.

Gates sit on transitions, not on stages. "Cannot enter done without a review" is a condition on an edge. Two consequences worth having: a validator can check that at least one terminal stage exists and that protected transitions are intact; and a hook may act on a transition where a gate only blocks one.

A gate is data, and three kinds are checkable — check (a command that must exit clean), review_by: non-author (a review line in History from someone who is not the worker), fields (named fields present on the task). The prose beside a gate explains it to a person and is never what holds it — _ops/scripts/transition.py is the one door a stage change goes through: it reads this same block, refuses an illegal move with the reason, and appends the transition to History. A gate written only as prose still reads, and is honestly prose-only — the validator warns rather than blocks on it. The machine guards; it never advances — nothing transitions itself is untouched by any of this, because the door refuses and records, and starting the next step stays a person's or the dispatcher's act.

The picture of the ladder is drawn from the same block, never beside it. Where a pipeline wants showing — the docs, an owner's "show me how work moves here" — the mermaid view is generated from this yaml, stages as nodes and gates as edge labels, between markers, a view and never a source (patterns §6, diagrams). Hand-drawing a second copy of the ladder is how the picture and the door drift apart.

A hook that acts obeys the automation rule: it may create work, never move anyone else's. A post-transition hook that opens a task or a request is fine; one that advances something is the automatic transition that makes boards lie.

Type owns the destination, pipeline owns the road ​

This split matters, because collapsing it is what makes team-level overrides impossible.

Owns
type (_ops/process/types/*.md)the definition of done, the default pipeline, the default cut on the description ladder (writing-work), and the declared fields
pipeline (_ops/pipelines/*.md)the stages and the gates

A bug and an article genuinely have different definitions of done, so the DoD belongs to the type. But the design team wanting everything it touches to run the design ladder is a statement about who is doing the work, not about what kind of work it is — and that is only expressible if the ladder lives somewhere the team can override.

Resolution is the cascade, minus one rung:

LevelSets
projectthe base pipeline, optionally per type
teamoverrides it for its own work
taskoverrides both, explicitly

There is no role rung — a pipeline belongs to the work, not to the worker. That is a declaration of rungs, not an exception (patterns §1). When the pick is not obvious, say which one was taken and why, in one line.

The ladders that ship ​

Ready on day one for feature · bug · content · chore · migration · tooling, and editable from day one, with the diff shown. A project that only ever does design keeps exactly one.

A type is born at first use, in the project's own words. The stock six are seeds for projects where those names do not sound absurd; where they would — a podcast has no migrations, a workshop has no bugs — the first question is what the kinds of work are called here, and what comes back may be two types, not six. One wave per type, when a task first needs it — never a questionnaire at setup, the same law as no roster before a task needs a craft.

Somebody else's checklist may become this type's bar, under three conditions. Written definitions of done exist for most crafts and are better than what a project derives from memory — security, accessibility, performance, testing, editorial. Adopt one when all three hold: every item clears checking's named-command rule · the bar is for a kind of work this project actually ships · and the copy is dated and attributed at its licence, so a later reader can tell an inherited claim from a measured one. Three things it must not become. It is not the project's evidence: passing a borrowed list is cited, never measured. It is not the sole gate for work whose failure mode its author never met. And it is not edited down to pass — a bar loosened to fit the work is the work grading itself (checking — loosening is loud). An imported number without its rationale is judgement wearing a table's clothes, which is the most common thing wrong with a checklist that looks authoritative.

Research comes before the ask, so the defaults offered are real. For an unfamiliar craft the recommended bars arrive sourced from the craft's own standards: empty options hand the research to the owner, and researched silence guesses their taste — the wave does neither. The answers — the bars, the type's cut on the description ladder (writing-work), and the type's fields — the x.* attributes the craft's own standard names (templates/TYPE-template.md) — land in _ops/process/types/<type>.md (templates/TYPE-template.md) with their provenance, and hold until the owner asks — or the bar itself accumulates the evidence and proposes its own change (checking), never silently: the bar is a locked surface.

The fields are the part everyone forgets to propose: declared at birth they are board columns from day one; discovered per task, twelve spellings of one attribute → templates/TYPE-template.md.

The default shape is build → review → accept, with a design stage in front where design precedes build, and parallel gates inside review.

And the edge into accept carries review_by: non-author — it carried nothing until 2026-09-17. A rung named review is not a review: the gate on the edge out of it is, and the stock ladder had gates: {}, so on every project that never wrote a pipeline file the worker moved its own task to accept and the door consulted nobody. Measured on a live project 2026-09-16, which had written no pipeline file and therefore ran on exactly this: four accepted tasks, two of them with no review evidence of any kind. §11 of the guard catches an author signing their own sign-off; nothing asked for a sign-off at all.

And until 2026-09-17 a type file written the way the template asked was not read at all: the inline ladder was matched against text whose → had never been normalised, so the file parsed to nothing and the task fell back to stock without a word — measured on a live project, both of its type files invisible (facts 273). Fixed — and the consequence for a project upgrading is worth acting on rather than reading past: its own ladder starts being obeyed, which may be an ungated one that was previously ignored in favour of the stock ladder. Run python3 _ops/scripts/transition.py --check-ladder --strict _ops/process/types/<type>.md on each type file: it prints the two lines to add, and exits 0 when there is nothing to do.

A ladder the project writes itself is held to the same thing, at the commit that writes it: an added _ops/pipelines/*.md or _ops/process/types/*.md whose terminal stage can be reached with no review_by on any edge into it is refused, with the line to add. An existing ladder is warned about, not frozen — by the guard on the commit that edits it, and by _ops/scripts/transition.py on every move it makes. An upgrade that stopped all work on ladders written before the rule existed would be uninstalled the same afternoon, and a project that deliberately wants an ungated terminal — a solo owner accepting their own work — says so by keeping the ladder it has.

Removing a stage asks what happens to the tasks currently in it, first. That question is the whole safety of editing a live ladder.

Six categories carry the meaning ​

The system understands six status categories; each pipeline names its own stages inside them → writing-work. That is what lets one board show work from different pipelines without either pretending they are the same or splitting into separate boards.

What starts the next unit of work ​

One field, three values, replacing what used to be two settings and an object:

startsMeans
manualthe owner starts each one. The default
on-completionfinishing one pulls the next — a continuous conveyor
schedule:<cron>a clock starts it

Keeping these as one axis prevents the state nobody documented before: a mode and a schedule both switched on, with no answer for what happens.

Two honest conditions on the non-manual values.

"Continuous" means "when something is running." There is no server: the next unit starts when a session opens, when a background session picks it up, or when a scheduled run fires. What does not exist is work proceeding while the machine is off, and a schedule that silently did not run is worse than one that says it was late.

A continuous conveyor must declare its stop conditions. A pipeline that pulls its own next item with nothing to stop it is an unbounded loop, and unbounded loops are the thing the whole gate apparatus exists to prevent.

Recurring work is a pipeline plus an automation, not a new thing ​

A content operation — a weekly publishing conveyor, a support rotation — is not a project and not an entity. It is a pipeline (the stages), an automation (what fires), a team and its resources. Nothing else is needed.

Worth saying plainly because the owner experiences it as one thing and will look for one switch: there isn't one, and the setup is a flow rather than an object. Splitting a project in two because it has both features and content is almost always wrong — that is one project with two types and two ladders. Split only when the second stream genuinely has its own team and its own cadence.