Skip to content

Gates and approvals

A gate is a leg of a pipeline that does no work. It decides whether the legs after it may run. Every gate has exactly one way of being answered:

  • by data: an expression over the pipeline’s inputs and earlier legs’ outputs, decided the moment those exist;
  • by a person: the pipeline pauses until someone approves or rejects.

A third source adds gates the pipeline author didn’t write: policies in the environment.

flowchart LR
  E[estimate] --> D{data gate}
  D -- true --> W[the leg that spends]
  D -- false --> S[pipeline stops]
  P[pull plan] --> H{person}
  H -- approve --> X[the leg that changes something]
  H -- reject --> S2[pipeline stops]

A data gate has a predicate:. When the legs it depends on finish, the predicate is evaluated. If it is true the gate passes at once, recorded with the verdict data, and nobody is asked. If it is false the pipeline stops: the gate fails closed, and the legs after it never start.

From the public serving/batch-inference pipeline, which prices a job before spending anything:

steps:
- id: estimate
task: cost/tokens
with: # abridged
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]

Because nobody approves a data gate, nobody can approve a run past it. Use one for anything a number can decide: a cost limit, a minimum count of new rows, a “no regression” check.

A person gate has no predicate:. When it becomes ready, the pipeline pauses and the gate appears in the console’s Approvals panel. From the public dataset_readiness/api-and-admit pipeline, which pulls data from an external API only after someone has approved the request:

steps:
- gate: approve-pull
show:
- input.connection
- input.base_url
- input.path
- input.max_rows
- id: pull
task: ingest/api
depends_on: [approve-pull]
  • show: lists what the approver sees next to the decision: inputs or outputs of earlier legs, written as <step>.<field>. Entries that aren’t in that form are rejected when the pipeline loads, so an approver never faces a field that can’t resolve.
  • approvers: names who is asked. A gate without one takes the pipeline’s governance.approvers.
  • The pause is durable. The gate’s state lives in the pipeline’s record, so a restart of the control plane resumes the pipeline at the same pause.
  • Approving releases the gate and records who approved, when, and any rationale. Rejecting cancels the pipeline.

A gate can’t have both predicate: and approvers:: the pipeline is refused at load with “a gate is answered by data OR a human, never both”. If you need both, write two gates, the data gate first. Then a person is only asked about runs the numbers already allow.

The decision is recorded under the identity of the person signed in, never a name typed into the request. approvers: names who is asked: it is shown on the gate and in the notification. Today it doesn’t restrict who can decide: any signed-in member of the workspace can approve or reject, and the record shows who did.

A policy can require a gate that isn’t in the pipeline file and that the author can’t remove. Policies are attached to an environment. A policy whose applies_to.pipeline_legs globs match a leg is applied when the pipeline is planned, before anything runs:

  • require_hitl inserts a person gate immediately before every matching leg;
  • deny refuses the launch outright, naming the policy.
name: cost-before-train
applies_to:
pipeline_legs: ["training/*"]
environments: [production]
rules:
- effect: require_hitl
reason: a person approves every training leg in production

The inserted gate carries the name of the policy that added it, so the approver and the audit trail see why it exists. A leg that already sits behind a person gate isn’t gated twice.

Some operators are risky enough that they ask for approval themselves: the operator’s manifest marks it as requiring approval, or the environment’s profile and policies do. Every operator call passes this check, whichever pipeline it runs in. When an approval is required, that step of the flow pauses until someone decides, and the decision lands in the same Approvals panel. One queue, wherever the gate came from.