Specification
Steps
Steps are the execution units of a stilt. Each step resolves its fields into values, assembles a prompt, and makes one or more LLM calls.
Steps
Steps are the execution units of a stilt. Each step resolves its fields into values, assembles a prompt, and makes one or more LLM calls.
What is a Step
A step is one execution unit. When a step runs, its fields are resolved to concrete values, the prompt is assembled, and the LLM call(s) are made. The result is stored as the step's output — other steps can read it later via ingest or multi_ingest fields.
Every step has an id, a name, and a type. Everything else is optional: fields (what data goes in), systemPrompt (what instruction the LLM follows), nodes (how many calls), continueIf (output gate), timeline (visibility in Studio), and recursion (spawn child stilts).
When a step runs, the assembled prompt looks like this:
FieldName: resolved value
AnotherField: another value
[System Instruction]
Your system prompt text hereFields are rendered in order, one per line, separated by blank lines. The system prompt is appended at the end under [System Instruction].
Step Types: normal and sequential
Two step types are available. Both make LLM calls — the difference is what happens when a step has multiple nodes.
normal — all nodes run at the same time. If a step has nodes: 5, five LLM calls fire simultaneously, each with its own node number. None of them can see each other's outputs.
- id: draft
name: Draft
type: normal
fields:
- name: Context
type: text
from: input.context
systemPrompt: "Draft an answer."sequential — nodes run one after another. Node 2 can read node 1's output via nodeRef: "previous". The first node has no previous output to read, so it's typically paired with skipFirstNode: true on the ingest field — this makes node 1 return an empty string and nodes 2+ get the actual previous output.
- id: refine
name: Refine
type: sequential
nodes: 3
fields:
- name: Previous
type: ingest
from:
stepId: refine
loopRef: current
nodeRef: previous
skipFirstNode: true
systemPrompt: "Improve the previous output."The only difference between the two types is type: "normal" vs type: "sequential".
Groups
A group is not a step type — it's a container. It holds two or more sibling steps that all run at the same time. The group itself never makes LLM calls, has no fields, no nodes, no system prompt, no gates, and no recursion. It just wraps its children.
- id: debate
name: Debate
type: group
steps:
- id: pro
name: Pro
type: normal
fields:
- name: Topic
type: text
from: input.topic
systemPrompt: "Argue in favor."
- id: con
name: Con
type: normal
fields:
- name: Topic
type: text
from: input.topic
systemPrompt: "Argue against."Both pro and con run simultaneously. A downstream step can ingest both outputs using a multi_ingest field.
A group must have at least 2 children. Children inside a group can't read each other's outputs — they run at the same time, so outputs aren't ready yet. A group cannot be nested inside another group.
nodes
nodes controls how many LLM calls a step makes. Without it, the step makes exactly one call. With it, the step fans out into multiple parallel calls (for normal) or sequential calls (for sequential).
Four ways to set node count:
Omitted — defaults to 1. The step makes a single LLM call.
Hardcoded number — nodes: 5 makes exactly 5 calls.
Knob reference — nodes: "{{knobs.coverage}}" reads the knob's value at call time. If the user picks coverage=8, the step runs 8 nodes.
Dynamic from another step — derive the count from a previous step's output:
nodes:
from:
stepId: evaluate
loopRef: currentThe referenced step's output is parsed as an integer to determine the count.
pruned: true — add this to nodes.from to count only the nodes that survived a continueIf gate on the referenced step. The source step must have continueIf for pruned: true to be valid.
nodes:
from:
stepId: evaluate
loopRef: current
pruned: truecontinueIf (Gates)
A gate checks each node's output against an expected string. Only nodes that match survive — the rest are pruned and disappear from downstream steps.
On a single-node step, a mismatch causes the stilt to abort — there's only one output and it failed the gate.
On a multi-node step, non-matching nodes are pruned individually. Downstream steps using pruned: true on their nodes.from will only see the survivors. If ALL nodes are pruned, the stilt aborts.
continueIf: "1"This checks if the output is exactly "1". A common pattern is a scorer step that outputs "1" for pass and "0" for fail — only passing branches continue.
Other Properties
timeline — controls whether a step appears in Studio's timeline. Two markers: timeline: "circle" renders the step as a circle in the timeline (multi-node steps produce one circle per node), and timeline: "init" marks the step as the init node (covered in section 2.3). Steps without a timeline marker still execute normally but are invisible in the UI.
systemPrompt — the instruction appended as [System Instruction] at the end of the assembled prompt.
fields: "clone:stepId" — instead of defining fields from scratch, copy all field definitions from another step. The source must be a non-group step with explicit fields.
recursion — lets a step spawn a child stilt from its output. The child runs independently and its result bubbles back up. Covered in detail in the Recursion section.
Common Validation Failures
Putting recursion on a group — only normal and sequential steps can recurse. Nesting a group inside another group — not allowed. A non-sequential step trying to ingest from itself with loopRef: "current" — only sequential steps can self-reference, since their nodes run in order. Sibling steps inside a group trying to ingest from each other — they run simultaneously, so outputs aren't available. Cloning fields from a group step — groups don't have fields to clone. Using pruned: true on nodes.from when the source step has no continueIf gate — pruning requires a gate to prune against.