Skip to content

Steps

A step is the smallest unit of work in a DagNats workflow – one task dispatched to one worker (or handled by the engine itself for infrastructure steps).

Step Types

DagNats supports 9 step types. Each has distinct execution semantics:

TypePurposeWhen to use
normalExecute a task once, complete or failStandard compute, API calls, data transforms
agent_loopIterative execution with Continue(), bounded by max iterations/durationLLM agent loops, polling, convergence tasks
agentRouted to agent SDK via metadataClaude Agent SDK integration
sub_workflowSpawn a child workflow, wait for completionReusable workflow composition, nested pipelines
mapFan-out over an array (one task per item), fan-in on completionParallel batch processing, list transforms
sleepDurable delay handled by the engineRate limiting, scheduled delays, cooldowns
wait_for_eventBlock until an external event matches a conditionWebhooks, human triggers, cross-system coordination
approvalHuman approval gate with cryptographic tokenDeploy approvals, sensitive operations
plannerGenerate DAG fragments at runtimeDynamic pipelines, AI-planned workflows

Steps that require a worker to execute: normal, agent_loop, agent, map, planner. Steps handled entirely by the engine: sleep, wait_for_event, approval, sub_workflow.

StepDef Fields

Every step is declared as a StepDef in the workflow definition:

FieldTypePurpose
IDstringUnique identifier within the workflow
TaskstringTask type that workers register for
DependsOn[]stringStep IDs that must complete first
TypeStepTypeOne of the 9 types above
Timeouttime.DurationPer-attempt timeout
RetriesintMax retry attempts (0 = no retries)
Retry*RetryPolicyFine-grained retry config (backoff, etc.)
Configjson.RawMessageType-specific configuration (AgentLoopConfig, MapConfig, etc.)
SkipIf*ParentCondConditional skip based on parent output
Metadatamap[string]stringOpaque key-value pairs (used by agent steps)
WorkerGroupstringRoute to specific worker group
OnFailurestringStep ID to run on permanent failure
CompensatestringStep ID for saga compensation
MaxTaskConcurrencyintGlobal per-task-type concurrency limit

Dependencies

Dependencies are declared via DependsOn – a list of step IDs that must reach a terminal state before this step is queued. The engine resolves dependencies iteratively: when a step completes, it checks which downstream steps have all their dependencies satisfied and enqueues them.

Skipped steps count as satisfied. If a step is skipped via SkipIf, downstream steps that depend on it proceed normally. This lets you build conditional branches without blocking the rest of the graph.

The builder API provides two ways to wire dependencies:

// String-based (backward compat)
wf.Task("process", "process")
wf.DependsOn("fetch")

// StepRef-based (compile-time safe, preferred)
fetch := wf.Task("fetch", "fetch")
process := wf.Task("process", "process").After(fetch)

After() panics immediately if you pass a StepRef from a different builder, catching miswiring at construction time rather than at validation time.

Conditional Execution

Steps can be conditionally skipped based on a parent step’s output using SkipIf. The condition references a parent step (which must be in DependsOn) and evaluates an operator against a field in that step’s output.

fetch := wf.Task("fetch", "fetch")
process := wf.Task("process", "process").
    After(fetch).
    SkipIf(dag.ParentOutput("fetch", "skip", dag.OpEquals, true))

Related pages