Documentation Index
Fetch the complete documentation index at: https://mintlify.com/BabySid/aether/llms.txt
Use this file to discover all available pages before exploring further.
Templates are the building blocks of every Aether workflow. A 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
// Template is a pure union container. Exactly one of DAG, Task, or Loop must be set.
type Template struct {
DAG *DAG `json:"dag,omitempty"`
Task *Task `json:"task,omitempty"`
Loop *Loop `json:"loop,omitempty"`
}
The helper methods GetName(), GetInputs(), GetOutputs(), GetTimeout(), and GetExecutor() delegate to whichever sub-type is set, so the engine can inspect templates uniformly.
The validator rejects any template where zero or more than one of dag/task/loop is set. Every template must have exactly one.
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.
type DAG struct {
Name string `json:"name"`
Inputs *Inputs `json:"inputs,omitempty"`
Outputs *Outputs `json:"outputs,omitempty"`
Entrypoints any `json:"entrypoints,omitempty"` // string or []string
Tasks []Task `json:"tasks"`
ContinueOn *ContinueOn `json:"continueOn,omitempty"`
PhaseConditions *PhaseConditions `json:"phaseConditions,omitempty"`
Timeout string `json:"timeout,omitempty"`
}
Tasks within a DAG
Each entry in dag.tasks is a Task struct operating as a call site — it references a template by name (or declares an inline executor) and passes arguments:
type Task struct {
Name string `json:"name"`
Inputs *Inputs `json:"inputs,omitempty"`
Template string `json:"template,omitempty"`
Executor *Executor `json:"executor,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
Arguments *Arguments `json:"arguments,omitempty"`
When string `json:"when,omitempty"`
Timeout string `json:"timeout,omitempty"`
Retry *Retry `json:"retry,omitempty"`
ContinueOn *ContinueOn `json:"continueOn,omitempty"`
Resources *Resources `json:"resources,omitempty"`
PhaseConditions *PhaseConditions `json:"phaseConditions,omitempty"`
Hooks *Hooks `json:"hooks,omitempty"`
Outputs *Outputs `json:"outputs,omitempty"`
}
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
The dependencies 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": {
"name": "main",
"tasks": [
{"name": "fetch", "template": "fetch-data"},
{
"name": "transform",
"template": "transform-data",
"dependencies": ["fetch"],
"arguments": {
"parameters": [
{"name": "raw", "valueFrom": {"parameter": "tasks.fetch.outputs.parameters.body"}},
{"name": "count", "valueFrom": {"parameter": "tasks.fetch.outputs.parameters.count"}}
]
}
},
{
"name": "notify",
"template": "send-notify",
"dependencies": ["transform"],
"arguments": {
"parameters": [
{"name": "summary", "valueFrom": {"parameter": "tasks.transform.outputs.parameters.summary"}}
]
}
}
]
}
}
DAG outputs
A DAG template can declare its own outputs that lift individual child task outputs into the parent scope:
{
"dag": {
"name": "main",
"outputs": {
"parameters": [
{
"name": "summary",
"type": "string",
"valueFrom": {"parameter": "tasks.transform.outputs.parameters.summary"}
}
]
},
"tasks": [ ... ]
}
}
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):
{"name": "send-alert", "template": "alert", "dependencies": ["check"],
"when": "tasks.check.outputs.parameters.status == 'failed'"}
continueOn
The ContinueOn policy allows a DAG to proceed even when a task fails, errors, or times out:
type ContinueOn struct {
Failed bool `json:"failed,omitempty"`
Error bool `json:"error,omitempty"`
Timeout bool `json:"timeout,omitempty"`
}
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.
// As a standalone template (in spec.templates):
type Task struct {
Name string `json:"name"`
Inputs *Inputs `json:"inputs,omitempty"`
Outputs *Outputs `json:"outputs,omitempty"`
Executor *Executor `json:"executor,omitempty"`
Timeout string `json:"timeout,omitempty"`
Retry *Retry `json:"retry,omitempty"`
PhaseConditions *PhaseConditions `json:"phaseConditions,omitempty"`
Hooks *Hooks `json:"hooks,omitempty"`
// ... (Resources, ContinueOn also available)
}
The executor field
type Executor struct {
Type string `json:"type"` // executor type identifier, e.g. "echo", "http", "shell"
}
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
type Retry struct {
Limit int `json:"limit,omitempty"`
Expression string `json:"expression,omitempty"`
}
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
{
"task": {
"name": "fetch-data",
"inputs": {
"parameters": [
{"name": "url", "type": "string"}
]
},
"executor": {"type": "http"},
"outputs": {
"parameters": [
{"name": "body", "type": "array"},
{"name": "count", "type": "int"}
]
}
}
}
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.
type Loop struct {
Name string `json:"name"`
Inputs *Inputs `json:"inputs,omitempty"`
Outputs *Outputs `json:"outputs,omitempty"`
RepeatCondition string `json:"repeatCondition,omitempty"`
Items []any `json:"items,omitempty"`
ItemsFrom string `json:"itemsFrom,omitempty"`
Concurrency int `json:"concurrency,omitempty"`
MaxIterations int `json:"maxIterations,omitempty"`
Body string `json:"body,omitempty"` // template name
Arguments *Arguments `json:"arguments,omitempty"`
Aggregate *Aggregate `json:"aggregate,omitempty"`
PhaseConditions *PhaseConditions `json:"phaseConditions,omitempty"`
Timeout string `json:"timeout,omitempty"`
}
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>:{
"loop": {
"name": "run-loop",
"items": ["jan.csv", "feb.csv", "mar.csv"],
"concurrency": 2,
"body": "process-file",
"arguments": {
"parameters": [
{"name": "filename", "value": "{{iterator.item}}"},
{"name": "index", "value": "{{iterator.index}}"}
]
}
}
}
itemsFrom is a valueFrom-style expression string that resolves to an array at runtime — typically from a sibling task output:{
"loop": {
"name": "process-loop",
"itemsFrom": "tasks.list-files.outputs.parameters.files",
"body": "process-file"
}
}
repeatCondition is a boolean expression evaluated before each iteration. The loop continues while it evaluates to true. maxIterations is required as a safety bound when using repeatCondition:{
"loop": {
"name": "repeat-loop",
"repeatCondition": "loop_iter.index != 2",
"maxIterations": 10,
"body": "step"
}
}
concurrency is not allowed with repeatCondition. Repeat-condition loops are inherently serial because each iteration’s condition can depend on the previous iteration’s output.
Aggregate strategies
When a loop completes, its body template outputs from all iterations are merged into the loop’s own outputs using an Aggregate policy:
type Aggregate struct {
Strategy AggregateStrategy `json:"strategy,omitempty"`
Parameters []string `json:"parameters,omitempty"`
}
const (
AggregateStrategyLast AggregateStrategy = "last" // default: use last iteration's outputs
AggregateStrategyFirst AggregateStrategy = "first" // use first iteration's outputs
AggregateStrategyList AggregateStrategy = "list" // collect all into JSON array
)
Parameters narrows which output parameter names are included in the aggregate. Omit it to include all declared outputs.
{
"aggregate": {
"strategy": "list",
"parameters": ["status", "rows"]
},
"outputs": {
"parameters": [
{"name": "status", "type": "array"},
{"name": "rows", "type": "array"}
]
}
}
With 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
body can reference a DAG template, which itself can contain Loops
{
"dag": {
"name": "outer",
"tasks": [
{"name": "sub", "template": "inner-dag"},
{
"name": "final",
"template": "finalize",
"dependencies": ["sub"],
"arguments": {
"parameters": [
{"name": "result", "valueFrom": {"parameter": "tasks.sub.outputs.parameters.result"}}
]
}
}
]
}
}
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 from a nested DAG propagate upward: the inner DAG declares its own 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.