Pipelines, legs and steps
A pipeline is the unit you launch. It is a YAML file that names the legs to run, the order they run in, and the gates between them. Only a declared pipeline runs: there is no other way to start work in cairn.
A pipeline is a list of legs. Each leg has an id and exactly one kind.
| Kind | What the leg does | Has its own run |
|---|---|---|
flow: | runs a data or ML flow on a dataflow engine | yes |
template: | runs an agentic flow (a markdown flow) | yes |
task: | calls one operator directly | yes |
gate: | stops until a predicate passes or a person approves | no |
loop: | repeats a group of legs until a condition holds, with a hard limit | no |
A leg that does work (flow, template, task) becomes its own run: its own status, cost and record, launched through the same admission checks as any other run. A gate has no run; its verdict and approver are stored on the pipeline. Gates and approvals covers them in full.
Legs run in the order of depends_on. A leg with no depends_on waits for the
leg written before it. A leg with a when: condition that is false is skipped
and the pipeline carries on.
Inside a flow, the units of work are steps: the stages of a data flow, or the nodes of an agentic flow.
An example
Section titled “An example”The public serving/batch-inference pipeline prices a batch job, stops if the
price is too high, runs the job, then records what it really cost:
flowchart LR
estimate["estimate<br/>task"] --> cost{"cost<br/>gate"}
cost -->|"price ≤ cap"| infer["infer<br/>flow"]
infer --> spend["spend<br/>task"]
Abridged from cairn/packs/serving/pipelines/batch-inference.yaml:
steps: - id: estimate task: cost/tokens with: model: $input.model rows: $input.rows
- gate: cost depends_on: [estimate] predicate: steps.estimate.usd <= input.max_cost_usd
- id: infer flow: batch-infer depends_on: [cost] with: path: $input.path max_rows: $input.rows
- id: spend task: cost/tokens depends_on: [infer] with: model: $input.model measured: true rows: $steps.infer.infer_batch_rowsValues are wired with expressions. $input.<name> reads a pipeline input and
$steps.<leg>.<field> reads an earlier leg’s output. The whole value must be
the expression; there is no string interpolation. The
YAML reference lists every field.
The launch door
Section titled “The launch door”The console’s Run button, cairn pipeline run, cairn remote launch and a
schedule all start a pipeline through the same launch code. In order, it:
- resolves the reference (
<pack>/<name>, optionally prefixedpipeline:), - checks the inputs and coerces them to their declared types,
- applies policy: a policy can insert an approval gate before a leg, or refuse the launch outright,
- runs admission checks (for example, a policy can require that the dataset version a pipeline trains on passed named checks),
- only then writes the pipeline record and starts the first legs.
A launch refused in steps 1 to 4 leaves no record behind.
A pipeline record moves through queued, running, paused (something is
waiting for a person), and ends done, failed or cancelled.
Triggers belong to pipelines
Section titled “Triggers belong to pipelines”A pipeline can declare a schedule. Flows cannot: a flow only runs as part of a pipeline.
triggers: - kind: schedule cron: "0 3 * * *" inputs: path: s3://corpus/current enabled: falseA schedule is armed when the pipeline is enabled in a workspace. It starts
disabled unless you set enabled: true, so enabling a pipeline never starts
unattended spend by surprise. A fired schedule goes through the same launch
door as a person pressing Run.
- Flows: the components legs run.
- Gates and approvals: data gates, person gates and policy gates.
- Write a pipeline: build one step by step.