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 10 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/duration; see Durable AgentsLLM 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
respondSend the HTTP response for an HTTP-triggered runDurable endpoints that answer a request mid-workflow

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, respond.

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 10 types above
Timeouttime.DurationPer-attempt timeout, enforced by the engine (0 arms none)
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]stringStatic key-value pairs, delivered to the worker with the task (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
RateLimit*RateLimitAt most Limit dispatches per Period for this step’s task
KeyedRateLimit*KeyedRateLimitThe same, counted per key taken from the input
SingletonboolOnly one instance of this step runs at a time
RequiredCapabilities[]stringCapabilities the step needs, e.g. control-plane

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.SkipIfOutput(fetch, "skip", "==", true))

Related pages