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:
- 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:
| Field | Required | Description |
|---|---|---|
name | yes | Unique identifier — becomes the reference name for outputs |
action | yes | What to do — ai, code, etc. (see Actions) |
prompt | depends | For ai steps: the prompt to send |
input | no | Data to pass to the action |
output | no | Override the default output variable name |
config | no | Step-level config overrides (model, temperature, timeout) |
on_error | no | What 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:
- 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:
- 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:
| Action | Default output field | Example reference |
|---|---|---|
ai (text response) | .text | summarize.text |
ai with output_schema | .data | extract.data.vendor |
code | custom (you declare them) | fetch.body |
Step ordering
Steps run sequentially by default — in the order they appear in the YAML file.
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:
- 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 extractionStep-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:
- 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:
- bodyThe 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.