Skip to main content

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.

A DAG (directed acyclic graph) template is the primary composition primitive in Aether. Every task node in a DAG declares which other nodes it depends on, and the engine uses those edges to determine what can run concurrently and what must wait. Because edges only flow forward and no cycles are allowed, the engine can always make progress: when a task finishes, it inspects all tasks whose dependencies list is now fully satisfied and dispatches them immediately. This means you never write explicit sequencing logic — you express intent through the graph, and the scheduler does the rest.

Anatomy of a DAG Template

A DAG is declared inside a templates entry with the dag key. The required fields are name and tasks; everything else is optional.
{
  "dag": {
    "name": "main",
    "tasks": [
      { "name": "fetch",     "template": "fetch-data" },
      { "name": "transform", "template": "transform-data", "dependencies": ["fetch"] },
      { "name": "notify",    "template": "send-notify",    "dependencies": ["transform"] }
    ]
  }
}
Each entry in tasks is a call site — it references a named template and optionally supplies arguments, dependency edges, a when guard, retry policy, timeout, and hooks.

Field reference

FieldTypeDescription
namestringDAG-local node name. DNS-1123 label (max 63 chars).
tasksTask[]Ordered list of task nodes.
inputsInputsParameters this DAG accepts when called as a sub-DAG.
outputsOutputsParameters exported by this DAG (use valueFrom).
entrypointsstring | string[]Restrict execution to a subgraph rooted at these nodes.
continueOnContinueOnWhether to keep running past task failures at the DAG level.
timeoutstringDuration string for the entire DAG (e.g. "30m").

Linear Chains (A → B → C)

The simplest topology is a sequential chain where each task declares the previous one in dependencies.
{
  "apiVersion": "aether/v1",
  "kind": "Workflow",
  "metadata": { "name": "dag-linear-param-passing" },
  "spec": {
    "entrypoint": "main",
    "templates": [
      {
        "dag": {
          "name": "main",
          "outputs": {
            "parameters": [
              {
                "name": "summary",
                "type": "string",
                "valueFrom": { "parameter": "tasks.transform.outputs.parameters.summary" }
              },
              {
                "name": "sent",
                "type": "bool",
                "valueFrom": { "parameter": "tasks.notify.outputs.parameters.sent" }
              }
            ]
          },
          "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" } }
                ]
              }
            }
          ]
        }
      }
    ]
  }
}
Parameter values flow between tasks via arguments[].valueFrom. The expression tasks.<node-name>.outputs.parameters.<param> references the named output of a completed upstream task. Tasks cannot access sibling state directly — all data flow must be declared explicitly through arguments.

Parallel Fan-Out and Joins

Omitting dependencies (or listing no common predecessor) causes tasks to start immediately and run concurrently. A downstream join node lists both parallel branches in its dependencies array and waits for all of them.
{
  "dag": {
    "name": "main",
    "tasks": [
      { "name": "task-a", "template": "worker-a" },
      { "name": "task-b", "template": "worker-b" },
      {
        "name": "join",
        "template": "merge",
        "dependencies": ["task-a", "task-b"],
        "arguments": {
          "parameters": [
            { "name": "result-a", "valueFrom": { "parameter": "tasks.task-a.outputs.parameters.result" } },
            { "name": "result-b", "valueFrom": { "parameter": "tasks.task-b.outputs.parameters.result" } }
          ]
        }
      }
    ]
  }
}
task-a and task-b dispatch simultaneously. The engine holds join in PhaseCreated until both predecessors are in a terminal state, then dispatches it with the merged arguments.

Diamond Patterns

A diamond graph — one source, two branches, one sink — combines fan-out and fan-in in a single DAG:
{
  "dag": {
    "name": "main",
    "tasks": [
      { "name": "start", "template": "init-task" },
      {
        "name": "left",
        "template": "left-branch",
        "dependencies": ["start"],
        "arguments": {
          "parameters": [
            { "name": "seed", "valueFrom": { "parameter": "tasks.start.outputs.parameters.seed" } }
          ]
        }
      },
      {
        "name": "right",
        "template": "right-branch",
        "dependencies": ["start"],
        "arguments": {
          "parameters": [
            { "name": "seed", "valueFrom": { "parameter": "tasks.start.outputs.parameters.seed" } }
          ]
        }
      },
      {
        "name": "join",
        "template": "merge-task",
        "dependencies": ["left", "right"],
        "arguments": {
          "parameters": [
            { "name": "left-result",  "valueFrom": { "parameter": "tasks.left.outputs.parameters.result" } },
            { "name": "right-result", "valueFrom": { "parameter": "tasks.right.outputs.parameters.result" } }
          ]
        }
      }
    ]
  }
}
Both left and right receive start’s seed output, execute concurrently, and both must complete before join is dispatched.

DAG-Level Inputs and Outputs

A DAG template can declare its own inputs and outputs, making it reusable as a sub-DAG callable from an outer DAG with arguments.
1

Declare inputs on the inner DAG

{
  "dag": {
    "name": "process-data",
    "inputs": {
      "parameters": [
        { "name": "userid",     "type": "string" },
        { "name": "multiplier", "type": "int" }
      ]
    },
    "tasks": [
      {
        "name": "fetch",
        "template": "fetch-task",
        "arguments": {
          "parameters": [
            { "name": "uid", "valueFrom": { "parameter": "inputs.parameters.userid" } }
          ]
        }
      }
    ],
    "outputs": {
      "parameters": [
        {
          "name": "report",
          "type": "object",
          "valueFrom": { "parameter": "tasks.compute.outputs.parameters.report" }
        }
      ]
    }
  }
}
2

Call it from the outer DAG with arguments

{
  "dag": {
    "name": "outer",
    "tasks": [
      {
        "name": "call-process",
        "template": "process-data",
        "arguments": {
          "parameters": [
            { "name": "userid",     "value": "u123" },
            { "name": "multiplier", "value": 3 }
          ]
        }
      },
      {
        "name": "summarize",
        "template": "summarize-task",
        "dependencies": ["call-process"],
        "arguments": {
          "parameters": [
            { "name": "report", "valueFrom": { "parameter": "tasks.call-process.outputs.parameters.report" } }
          ]
        }
      }
    ]
  }
}
Inside the inner DAG, reference its own inputs with inputs.parameters.<name>. The outputs valueFrom expression must point to a task inside that DAG — the outer DAG then references the sub-DAG’s outputs with tasks.<sub-dag-node>.outputs.parameters.<name>.

Named Entrypoints

The entrypoints field restricts execution to the subgraph reachable from the named starting nodes. Tasks not reachable from any listed entrypoint are never created.
{
  "dag": {
    "name": "main",
    "entrypoints": ["task-a"],
    "tasks": [
      { "name": "task-a", "template": "node-a" },
      { "name": "task-b", "template": "node-b", "dependencies": ["task-a"] },
      { "name": "task-c", "template": "node-c" },
      { "name": "task-d", "template": "node-d", "dependencies": ["task-c"] }
    ]
  }
}
With entrypoints: ["task-a"], only task-a and task-b execute. task-c and task-d are excluded because they are not reachable from task-a.
Entrypoints are useful for selectively running portions of a large shared DAG, such as running only the ingestion branch of a pipeline without the reporting branch.

Failure Propagation with continueOn

By default, if a task fails the DAG stops scheduling new tasks and transitions to a failed state. The continueOn field overrides this at two levels.
Apply continueOn to a specific task node so that its downstream dependents can still be dispatched even if that node fails:
{
  "dag": {
    "name": "main",
    "continueOn": { "failed": true },
    "tasks": [
      { "name": "step-a", "template": "task-ok" },
      {
        "name": "step-b",
        "template": "task-fail",
        "dependencies": ["step-a"],
        "continueOn": { "failed": true }
      },
      {
        "name": "step-c",
        "template": "task-ok",
        "dependencies": ["step-b"]
      }
    ]
  }
}
step-b fails, but because it declares continueOn: {failed: true}, step-c is still dispatched. The DAG itself also needs continueOn: {failed: true} to prevent the container from marking itself failed while children are still succeeding.
continueOn does not mask the failing task’s own phase — step-b in the example above still records PhaseFailed. It only controls whether the failure blocks downstream scheduling and whether the DAG container itself treats the failure as terminal.

Nested DAGs

A DAG task node can reference another DAG template by name. The engine creates a child scope for the inner DAG, runs it to completion, then propagates its outputs to the outer DAG.
{
  "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 inner DAG (inner-dag) runs as its own scope with its own tasks. When it completes, tasks.sub.outputs.parameters.result resolves to the inner DAG’s declared output. Nesting is bounded by spec.maxNestedDepth (default 3, max 10).

Topology Design Tips

Each task node is an independent unit of work. Tasks with no shared dependencies run concurrently. Break monolithic tasks into smaller pieces wherever possible to take advantage of the scheduler’s parallel dispatch.
Tasks cannot read sibling outputs directly. All cross-task data must pass through arguments[].valueFrom at the call site. This is intentional — it makes data flow visible in the workflow document and eliminates hidden coupling.
A multi-step process (fetch → validate → transform) can be wrapped in a named DAG template and called from multiple parent DAGs. Each call site creates its own independent scope, so runs are fully isolated.
Use entrypoints to scope execution to a subgraph, and continueOn to keep the DAG healthy when non-critical branches fail. This combination supports partial pipeline patterns where the important path must always complete regardless of auxiliary step outcomes.

Build docs developers (and LLMs) love