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.

Aether’s parameter system is built around a single principle: inputs flow through explicit declarations, not side effects. A task cannot reach sideways into a sibling’s memory or environment. It can only receive values that have been explicitly wired into its arguments by the enclosing scope. This makes data flow visible in the workflow document itself and fully testable without running the engine.

The binding model

When the engine is about to dispatch a task, it resolves the task’s inputs by merging two sources:
  1. The template’s inputs declaration — the parameter schema (name, type, default).
  2. The call-site arguments — the values or valueFrom references provided by the enclosing DAG or Loop.
Resolution priority per parameter (applied independently for each):
  1. arguments[name].value — explicit value provided at the call site (non-empty, non-null).
  2. arguments[name].valueFrom — resolved from the evaluation environment (env).
  3. decls[name].value — static value declared on the template input itself.
  4. decls[name].valueFrom — template-declared default resolution.
  5. decls[name].default — fallback default.
  6. (empty) — value is absent; the executor decides how to handle it.
Undeclared arguments (parameters present in arguments but not in the template’s inputs) are appended as-is, enabling callers to inject extra values without modifying the template.

Interpolation syntax

String fields in arguments and when expressions support {{key}} placeholder interpolation. The engine replaces each {{key}} with the corresponding value from the evaluation environment:
{{inputs.parameters.name}}
{{tasks.TASK_NAME.outputs.parameters.PARAM_NAME}}
{{workflow.parameters.NAME}}
{{loop_iter.index}}
{{loop_iter.item}}
{{loop_iter.FIELD}}
If the entire string is a single {{key}} token and the resolved value is a non-string type (e.g., an integer or array), the original Go value is preserved without string conversion. Mixed strings like "prefix-{{key}}-suffix" always produce a string via fmt.Sprint.
Keys not found in the evaluation environment are left as-is — the placeholder is not replaced, and no error is raised. Downstream validators or the executor are responsible for reporting missing required inputs.

The evaluation environment (EvalVars)

The binding engine builds a flat EvalVars map before resolving any parameter. The key space is:
Key patternSource
workflow.parameters.<name>spec.arguments.parameters
inputs.parameters.<name>Current template’s resolved inputs
tasks.<name>.phaseSibling task’s current phase string
tasks.<name>.codeSibling task’s exit code
tasks.<name>.msgSibling task’s message
tasks.<name>.outputs.parameters.<param>Sibling task’s output parameter
loop_iter.indexCurrent loop iteration index
loop_iter.itemCurrent iteration scalar item
loop_iter.<field>Current iteration object field
Scope isolation is strict. Tasks can only reference siblings in the same scope. A task inside an inner DAG cannot reference a task in the outer DAG’s scope. Cross-scope access requires the inner DAG to expose the value through its own outputs declaration, which the outer DAG then reads via tasks.<node>.outputs.parameters.<param>.

Parameter types

The type field on a Parameter is a hint for validation and executor consumption:
Type valueJSON representation
"string"JSON string
"int"JSON number (integer)
"bool"JSON boolean
"array"JSON array
"object"JSON object
Type enforcement is executor-specific; the engine itself does not reject mismatched types.

Input parameter declaration

Input parameters are declared on the template’s inputs.parameters list:
type Parameter struct {
    Name        string            `json:"name"`
    Type        string            `json:"type,omitempty"`
    Value       json.RawMessage   `json:"value,omitempty"`
    Default     json.RawMessage   `json:"default,omitempty"`
    Description string            `json:"description,omitempty"`
    Enum        []json.RawMessage `json:"enum,omitempty"`
    ValueFrom   *ValueFrom        `json:"valueFrom,omitempty"`
}
A parameter with name: "outputs" has special meaning for the echo executor used in the playground (see The outputs special parameter below).

valueFrom: dynamic value resolution

valueFrom is the mechanism for wiring dynamic values between parts of a workflow:
type ValueFrom struct {
    Path         string        `json:"path,omitempty"`
    Parameter    string        `json:"parameter,omitempty"`
    Expression   string        `json:"expression,omitempty"`
    SecretKeyRef *SecretKeyRef `json:"secretKeyRef,omitempty"`
}
References a key in the EvalVars environment by name. This is the most common form:
{"name": "raw", "valueFrom": {"parameter": "tasks.fetch.outputs.parameters.body"}}
Supported reference formats for parameter:
  • workflow.arguments.parameters.<name> — workflow-level argument (legacy alias, normalized internally)
  • workflow.parameters.<name> — canonical form of the above
  • inputs.parameters.<name> — current scope’s resolved input
  • tasks.<name>.outputs.parameters.<param> — sibling task output

Output parameter declaration

Output parameters are declared on the template’s outputs.parameters list. For a Task template, the executor populates them at runtime. For a DAG or Loop template, valueFrom references lift values from child task outputs:
{
    "outputs": {
        "parameters": [
            {
                "name": "summary",
                "type": "string",
                "valueFrom": {"parameter": "tasks.transform.outputs.parameters.summary"}
            },
            {
                "name": "sent",
                "type": "bool",
                "valueFrom": {"parameter": "tasks.notify.outputs.parameters.sent"}
            }
        ]
    }
}

Parameter flow: a complete example

The following is from 02-dag-linear.json — a three-task linear DAG where each task receives its inputs from the previous task’s outputs:
{
    "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"}
                            }
                        ]
                    },
                    "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"}}
                                ]
                            }
                        }
                    ]
                }
            },
            {
                "task": {
                    "name": "fetch-data",
                    "executor": {"type": "echo"},
                    "outputs": {"parameters": [{"name": "body", "type": "array"}, {"name": "count", "type": "int"}]}
                }
            },
            {
                "task": {
                    "name": "transform-data",
                    "inputs": {"parameters": [{"name": "raw", "type": "array"}, {"name": "count", "type": "int"}]},
                    "executor": {"type": "echo"},
                    "outputs": {"parameters": [{"name": "summary", "type": "string"}]}
                }
            },
            {
                "task": {
                    "name": "send-notify",
                    "inputs": {"parameters": [{"name": "summary", "type": "string"}]},
                    "executor": {"type": "echo"},
                    "outputs": {"parameters": [{"name": "sent", "type": "bool"}]}
                }
            }
        ]
    }
}
Data flows like this:
  1. fetch-data runs and produces body and count as outputs.
  2. transform reads them via tasks.fetch.outputs.parameters.* in its arguments.
  3. transform-data produces summary.
  4. notify reads summary from tasks.transform.outputs.parameters.summary.
  5. The main DAG lifts summary into its own outputs for callers of this scope.

Workflow-level arguments

Workflow arguments declared in spec.arguments are available everywhere via the workflow.parameters.<name> key (or the legacy workflow.arguments.parameters.<name> alias):
{
    "spec": {
        "arguments": {
            "parameters": [
                {"name": "env",        "type": "string",  "value": "production"},
                {"name": "batch-size", "type": "int",     "value": 100},
                {"name": "dry-run",    "type": "bool",    "value": false},
                {"name": "tags",       "type": "array",   "value": ["v1", "stable"]},
                {"name": "config",     "type": "object",  "value": {"region": "cn-east-1"}}
            ]
        },
        "templates": [
            {
                "dag": {
                    "name": "main",
                    "tasks": [
                        {
                            "name": "run",
                            "template": "execute",
                            "arguments": {
                                "parameters": [
                                    {"name": "env",        "valueFrom": {"parameter": "workflow.arguments.parameters.env"}},
                                    {"name": "batch-size", "valueFrom": {"parameter": "workflow.arguments.parameters.batch-size"}},
                                    {"name": "dry-run",    "valueFrom": {"parameter": "workflow.arguments.parameters.dry-run"}},
                                    {"name": "tags",       "valueFrom": {"parameter": "workflow.arguments.parameters.tags"}},
                                    {"name": "config",     "valueFrom": {"parameter": "workflow.arguments.parameters.config"}}
                                ]
                            }
                        }
                    ]
                }
            }
        ]
    }
}

The outputs special parameter

The playground’s echo executor reads a special input parameter named "outputs" to determine what it should emit. Its value is a JSON array of output parameter descriptors:
{
    "name": "outputs",
    "value": [
        {"name": "message", "type": "string", "value": "Hello, Alice!"},
        {"name": "code",    "type": "int",    "value": 200},
        {"name": "ok",      "type": "bool",   "value": true},
        {"name": "tags",    "type": "array",  "value": ["greet", "demo"]},
        {"name": "meta",    "type": "object", "value": {"version": "1.0"}}
    ]
}
This pattern is specific to the echo executor — a testing convenience that allows integration tests to mock any task output without implementing a real executor. Production executors declare their outputs through the ExecOutputs return value, not through input parameters.
Use the echo executor in playground examples and integration tests to simulate any output shape your workflow needs, then replace it with a real executor implementation when deploying.

Scoped access summary

ReferenceAccessible from
workflow.parameters.<name>Any scope at any depth
inputs.parameters.<name>Current template only
tasks.<name>.outputs.parameters.<param>Siblings in the same DAG scope only
loop_iter.*Tasks inside a Loop body only
To pass a value from an inner scope to an outer scope, declare it in the inner template’s outputs and wire it through the parent node’s outputs.valueFrom.

Build docs developers (and LLMs) love