spec.templates array holds all the templates that a workflow can reference, and each template is a union type: exactly one of dag, task, or loop must be set. This design keeps templates composable — a DAG can reference a Loop template as one of its tasks, which in turn can run an inner DAG as its body, up to the configured nesting depth.
Template as a union type
GetName(), GetInputs(), GetOutputs(), GetTimeout(), and GetExecutor() delegate to whichever sub-type is set, so the engine can inspect templates uniformly.
DAG template
A DAG template ("dag") defines a directed acyclic graph of tasks. It is the primary composition mechanism — the entrypoint of a workflow is almost always a DAG.
Tasks within a DAG
Each entry indag.tasks is a Task struct operating as a call site — it references a template by name (or declares an inline executor) and passes arguments:
Each DAG task must have either a
template reference or an inline executor. Both at the same time, or neither, is a validation error.How dependencies create the DAG graph
Thedependencies field on each task lists the names of other tasks in the same DAG that must reach a terminal phase before this task is eligible to run. A task with no dependencies is a root node and starts immediately.
DAG outputs
A DAG template can declare its own outputs that lift individual child task outputs into the parent scope:when conditions
The when field on a DAG task is a boolean expression evaluated just before the task would be dispatched. If it evaluates to false, the task transitions to PhaseSkipped and any downstream tasks that depend on it are unblocked (the dependency is treated as satisfied):
continueOn
The ContinueOn policy allows a DAG to proceed even when a task fails, errors, or times out:
entrypoints
By default the engine starts all tasks that have no dependencies. entrypoints (a single name or a list) restricts the initial dispatch to a named subset, useful when a DAG has multiple independent roots but you want to control which ones start first.
Task template
A Task template ("task") is a leaf node — it delegates execution to a named executor plugin and cannot contain other templates.
The executor field
executor.type is a string that the engine’s broker uses to look up the registered executor.Plugin implementation. The engine itself never executes task logic — that is the sole responsibility of the matched plugin.
Retry policy
limit sets the maximum retry count. When expression is omitted, only Error and Timeout phases trigger a retry. With a custom expression, the retry fires only when it evaluates to true; the expression can reference tasks.<name>.phase, tasks.<name>.code, tasks.<name>.msg, and output parameters.
Only leaf Task templates support retry. DAG and Loop containers do not.
Task template example
Loop template
A Loop template ("loop") iterates over a set of items, running a body template for each one. It supports three mutually exclusive iteration modes and parallel execution control.
body is required and must reference an existing template by name — this is the template executed for each iteration.
Three iteration modes
- Static items
- itemsFrom
- repeatCondition
items is a static JSON array. Each element is one iteration’s item value. Scalar elements are available as loop_iter.item; object elements are expanded into loop_iter.<field>:Aggregate strategies
When a loop completes, its body template outputs from all iterations are merged into the loop’s own outputs using anAggregate policy:
Parameters narrows which output parameter names are included in the aggregate. Omit it to include all declared outputs.
strategy: "list", each parameter’s value across all iterations is collected into a JSON array ordered by iteration index.
Template composability
Templates compose by name reference. A DAG task sets"template": "<name>" to invoke any template in the flat spec.templates list. This means:
- A DAG can contain tasks that run other DAGs (nested DAGs)
- A DAG can contain tasks that run Loop templates
- A Loop’s
bodycan reference a DAG template, which itself can contain Loops
The maximum static nesting depth is controlled by
spec.maxNestedDepth (default 3, absolute ceiling 10). The validator walks the entire template reference tree from the entrypoint and rejects workflows that exceed this limit before they are submitted.outputs.parameters using valueFrom references to its child task outputs. The parent scope then reads those via tasks.<node-name>.outputs.parameters.<param>, exactly as it would for a leaf task. See Parameter Binding for the complete data-flow model.