Skip to Content

Step Types

A step is one unit of work in a Helix. There are exactly five step kinds — mix them to build anything from a two-step notifier to a multi-week, multi-party process.

Every step, whatever its kind, shares the same common properties:

PropertyWhat it’s for
IntentA plain-language description of what the step does — shows up in the pipeline view and in run history.
Depends onThe prior steps this one needs to complete first. Steps with no shared dependency run in parallel; a step only starts once all of its dependencies have genuinely succeeded.
Capability requiredWhat kind of work the step performs — used to route it correctly and to reason about the automation as a whole.
Action classHow consequential the step’s effect is. Read-only lookups and write/consequential actions are classed differently, which is what your autonomy and HITL settings key off.
HITLWhether the step needs a human’s sign-off before it’s allowed to run. Any step can be gated this way — see Human-in-the-loop below.

Dependencies and the DAG

Steps are connected by dependencies, not a rigid top-to-bottom line, so a Helix runs as a directed graph rather than a script:

  • Steps with no shared dependency run in parallel.
  • A step runs only once every step it depends on has succeeded.
  • If a dependency permanently fails, the step that depends on it (and anything downstream of that, in turn) is correctly skipped rather than running on incomplete data.

This is what lets a Helix fan out (check several things at once), rejoin (combine the results into one decision), and fail safely — a failed lookup doesn’t silently feed a downstream action bad or missing data.

1. Agent steps

An agent step hands a task to a model to reason about or generate — summarize a document, classify or route an item, extract structured fields, draft a reply, translate text, score or compare options. Give it a name, a model, a system prompt, and (if it needs them) the capabilities and tools it can use; the step captures a result for later steps to use.

Best practice — give it an output schema. If a later step needs to interpolate a specific field out of an agent step’s result (for example a downstream step reads ${steps.screen.data.riskLevel}), define an output schema on the agent step. Without one, the model’s answer is free-form text with no reliable field to reference; with a schema, the output is parsed into real, addressable fields every time. Get in the habit of adding one on any agent step another step will read structured data from.

Looping over a list. An agent step can repeat itself once per item in an array produced by an earlier step — for example, once per line item, once per applicant, once per record in a batch. Point the loop at the array (e.g. steps.fetchAll.data.items), give the current item a name, and the step body can address it as that variable (${item}, or whatever you called it) on each iteration. Set a maximum number of iterations so an unexpectedly large list can’t run away.

2. Transform steps

A transform step reshapes data without calling a model or a tool — combine fields, rename them, compute a derived value, build the exact payload the next step expects. Transforms are fast, deterministic, and free: reach for one whenever the next step just needs a differently-shaped version of data you already have, rather than spending an agent step on it.

3. Action steps

An action step does real work in one of your connected tools — deterministically, not “an agent probably did it.” Pick a connector, pick one of its tools, and map the inputs; the step calls that tool with exactly those inputs (with an optional timeout) and captures the response.

Naming convention: reference a tool in its connector-prefixed form — github.get_file_content, not bare get_file_content. The prefixed form is what reliably resolves to the right tool on the right connector; an unprefixed name is a common cause of a step failing to resolve its tool at run time.

Action steps only reach the tools you’ve connected. Connect the integrations you need under Connectors, then pick one when configuring the step.

4. Wait steps

A wait step pauses the run. Because Helix is durable, a wait can last seconds or weeks without holding anything open — the run picks up exactly where it left off. There are three ways to configure what it’s waiting for:

ModeWhat it waits for
DurationA fixed length of time — “pause for 3 days,” then continue.
UntilA specific point in time, given directly as a date/time — the run resumes then.
EventA named event from a source, matched against a condition you set — the run resumes as soon as a matching event arrives. Set a timeout as a fallback: if no matching event arrives in time, the wait resolves at the timeout instead of hanging forever.

This “event” is a setting on a wait step, not a top-level Helix stimulus. It pauses one already-running run until something happens. That’s a different thing from the stimulus that starts a run in the first place — worth keeping straight, since some parts of the product use “event” for both.

5. Sub-automation steps

A sub-automation step runs another Helix as a step in this one, so you can compose large processes out of smaller, reusable ones — a shared “collect an approval” or “notify stakeholders” Helix, called from several parents, for example. Point the step at the Helix to run and the input to pass it, and choose a mode:

  • Await — the parent pauses until the child finishes, and the child’s result is available to steps after this one. Use this when the parent needs the child’s outcome to continue.
  • Async — fire-and-forget. The step returns immediately with the child run’s ID, and the parent keeps going without waiting for the child to finish. Use this when the child does independent work (a notification, a background cleanup) that the parent doesn’t need to wait on.

Human-in-the-loop (HITL)

Any step — most commonly a consequential action step — can be marked HITL. When a run reaches an HITL step, it pauses and shows as awaiting approval, and a person reviews and approves or rejects it before the run continues. Approve, and the step (and the run) proceeds; reject, and the run halts there.

Use HITL to gate the specific moments that matter rather than slowing the whole automation down — for example, let a Helix draft and open a pull request automatically, but require a human to sign off first only if the proposed change falls outside your normal range. See Controls & Policy for how HITL fits into your overall autonomy settings.

Conditional execution (branching)

Any step can carry a condition — an expression checked against the outputs of the steps it depends on. If the condition evaluates false, the step (and anything that depends on it) is cleanly skipped instead of running; if true, it runs normally. This is how you build if/else branches in a Helix: route two steps off the same upstream result, and give each one the opposite condition.

Example: a step conditioned on steps.analyze.data.riskLevel == high only runs when the upstream analysis step flagged high risk; a sibling step conditioned on the opposite outcome handles everything else.

Handling failure: onError and retry

Every step has an on-error behavior, which defaults to fail:

BehaviorWhat happens when the step fails
Fail (default)The step’s failure marks the whole run as failed.
ContinueThe run’s overall outcome isn’t blocked by this one step failing — but the step itself, and anything depending on it, are still treated as failed/skipped. Use this for a step whose result is genuinely optional.
FallbackOn failure, a fallback step you designate substitutes its result for this step’s own output, and the pipeline carries on as if this step had succeeded. Use this for a step that has a reliable backup path.

Steps can also carry a retry policy — a maximum number of attempts with a backoff between them. Worth setting on any agent step: LLM calls can occasionally fail transiently, and a short retry clears most of them without any human getting involved.

Putting them together

A request-triage automation combines several kinds:

1. classify (agent) │ reads the request, extracts fields, scores urgency ├── urgency == high ──▶ 2. resolve-reviewed (action, hitl: true) │ applies the change through a connector — a person │ signs off first, since this path is flagged risky └── urgency != high ──▶ 3. resolve-direct (action) applies the change through a connector directly, no approval needed

Steps 2 and 3 both dependsOn: [classify], with opposite conditions on steps.classify.data.urgency — only one of them ever runs for a given request. The risky path is gated by hitl: true; the routine path runs straight through to the same kind of action step.

Next: Triggers · Controls & Policy · Templates

Last updated on