Skip to content

Steps

A step is one unit of work in a pipeline. Every step does exactly one thing: it takes some inputs, runs an action, and produces outputs. Think of each step as a single well-named function call in your workflow.

The simplest step:

yaml
- name: summarize
  action: ai
  prompt: "Summarize this: {{ input.text }}"

That's it. A name, an action, and something to do.

Step fields

Every step can have these fields:

FieldRequiredDescription
nameyesUnique identifier — becomes the reference name for outputs
actionyesWhat to do — ai, code, etc. (see Actions)
promptdependsFor ai steps: the prompt to send
inputnoData to pass to the action
outputnoOverride the default output variable name
confignoStep-level config overrides (model, temperature, timeout)
on_errornoWhat to do if this step fails

How data flows

Data flows forward through double-brace template references. A step's name becomes the namespace for its outputs.

Start with a single step:

yaml
- name: classify
  action: ai
  prompt: "Classify this support ticket: {{ input.message }}"

After this step runs, you can reference classify.text in any later step.

Now chain two steps together:

yaml
- name: classify
  action: ai
  prompt: "Classify this ticket as 'bug', 'feature', or 'question': {{ input.message }}"

- name: respond
  action: ai
  prompt: |
    The ticket was classified as: {{ classify.text }}
    Now write a helpful response to: {{ input.message }}

The respond step sees classify.text — the output of the first step. Data flows forward automatically.

Name steps after what they produce

A step named classify produces classify.text. A step named summarize produces summarize.text. Name steps after what they do and the data flow reads naturally.

Referencing step outputs

Every step produces output under its name. The exact fields depend on the action:

ActionDefault output fieldExample reference
ai (text response).textsummarize.text
ai with output_schema.dataextract.data.vendor
codecustom (you declare them)fetch.body

Step ordering

Steps run sequentially by default — in the order they appear in the YAML file.

yaml
steps:
  - name: step_one    # runs first
    action: ai
    prompt: "..."

  - name: step_two    # runs second, can reference {{ step_one.text }}
    action: ai
    prompt: "Based on: {{ step_one.text }}, ..."

The runtime determines execution order from data dependencies: if step_two references step_one's output, step_one must finish first. JigSpec handles this automatically.

Step-level config overrides Implemented

You can override pipeline defaults for individual steps:

yaml
- name: creative_write
  action: ai
  prompt: "Write a poem about {{ input.topic }}"
  config:
    model: anthropic/claude-sonnet-4-5
    temperature: 0.9   # high creativity for this step

- name: extract_facts
  action: ai
  prompt: "Extract key facts from: {{ input.text }}"
  config:
    temperature: 0.1   # low temperature for factual extraction

Step-level config overrides the pipeline-level default for that step only. Everything else inherits from pipeline.config.

Input field

Some actions — like code — take an explicit input map:

yaml
- name: process
  action: code
  input:
    url: "{{ input.url }}"
    token: "{{ secrets.API_TOKEN }}"
  run: |
    const resp = await fetch(input.url, {
      headers: { Authorization: `Bearer ${input.token}` }
    })
    return { body: await resp.text() }
  outputs:
    - body

The input map lets you explicitly define what the step receives, making steps self-documenting and easier to test.

Step names must be unique

Each step name must be unique within a pipeline. The name becomes the data reference namespace — duplicate names cause undefined behavior.

Released under the Apache 2.0 License.