Executors are the primary extension point for Aether task logic. Every leaf task in a workflow is dispatched to an executor — a small Go struct that implements three methods and returns a typed result. The engine schedules and coordinates; executors do the work. This strict separation means you can add new task types without touching the scheduling core, and you can swap, version, or distribute executors independently.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.
The Plugin interface
Every executor must implementexecutor.Plugin:
Type() is the string that appears in the workflow JSON under executor.type. It must be unique across all registered plugins. Schema() is called once at registration time and cached — its result is stored in the executor registry and optionally persisted to the schema store. Execute() is called for every task dispatch.
ExecuteRequest
The engine bundles everything an executor needs into a singleExecuteRequest. Executors are self-contained — they never call back into the store.
Unique ID of this task run. Use it as a correlation key in logs and traces.
ID of the parent workflow run. Useful when an executor needs to emit metrics scoped to a run.
The task’s name within its DAG scope, as declared in the workflow document.
The name of the referenced template. Equals
TaskName for inline tasks.Resolved input parameters after interpolation and expression evaluation. May be
nil when the task declares no inputs.CPU, memory, and GPU requirements declared on the task. May be
nil.Human-readable duration string (e.g.
"30m", "2h"). Empty when the task has no deadline. Pass this to subprocesses or remote API calls so they can enforce their own deadline independently of the Go context.Number of retries already consumed.
0 means this is the first attempt; 1 means the first retry.ExecOutputs
Executors return*model.ExecOutputs:
Code must be one of the ExecCode constants. The engine maps this code to a Phase value — executors never write Phase directly.
ExecCode values
| Constant | Value | Engine maps to |
|---|---|---|
ExecCodeSucceeded | 0 | PhaseSucceeded |
ExecCodeSuspended | 1 | PhaseSuspended |
ExecCodeFailed | 2 | PhaseFailed |
ExecCodeError | 3 | PhaseError |
ExecCodeTimeout | 4 | PhaseTimeout |
PhaseSkipped and PhaseCancelled are set exclusively by the engine and are never valid return codes for an executor.Declaring the executor schema
Useexecutor.SchemaOf to derive an ExecutorSchema from Go struct types. This approach reflects struct field names and json/desc tags to populate the schema automatically — no hand-written field name strings.
C— the config struct that represents the executor’s accepted inputs.O— the output struct that represents the executor’s produced parameters. Useexecutor.DynamicOutputswhen outputs are determined at runtime.
DynamicOutputs:
Type-safe output construction with OutputFrom
executor.OutputFrom converts a flat output struct to *model.ExecOutputs by reflecting its fields. The json tag on each field becomes the Parameter.Name.
- Input must be a flat struct — no embedded (anonymous) fields, no nested structs.
- Fields tagged
json:"-"are skipped. - All field values are JSON-serialised.
Reading inputs with BindInputs
executor.BindInputs is the counterpart to OutputFrom. It maps model.Inputs.Parameters into a destination struct by matching each parameter’s Name against the json tag of the corresponding field.
The executor Registry
executor.Registry manages registered plugins, routing dispatched tasks to the correct implementation.
Register returns an error if the type is already registered. It also caches the plugin’s Schema() result. RegisterSchema registers a schema without a plugin instance — used by distributed brokers to propagate remote worker schemas to the master, enabling schema-aware validation even when executors run on remote machines.
Engine options
WithExecutor
Registers a single executor plugin. Call multiple times to register multiple types. Duplicate type names are silently ignored at option-application time; duplicates are caught at workflow validation.
WithExecutorRegistry
Accepts a pre-built
*executor.Registry. Use this when you share a registry between the engine and a broker, avoiding double-registration.The fat-assignment principle
ExecuteRequest carries every piece of information the executor needs. Workers executing tasks never call back into the store. This makes executors:
- Independently deployable — a remote worker process needs no store connection.
- Easily testable — call
Executewith a hand-craftedExecuteRequestin any unit test. - Topology-agnostic — local goroutine, separate process, or remote machine: the executor code is identical.
Triggering suspension
An executor can pause a task mid-execution and wait for an external signal by returningExecCodeSuspended:
engine.Resume(ctx, taskRunID, additionalInputs). The engine merges the new inputs on top of the original resolved inputs (last-writer-wins) and re-dispatches the task.
Complete example: EchoExecutor
The playground’sEchoExecutor is a working reference implementation. It echoes inputs as outputs and supports three control parameters:
Step-by-step: implementing a minimal executor
Define config and output structs
Create flat structs with
json and desc tags for automatic schema derivation.Implement the Plugin interface
Embed your structs into
SchemaOf and use BindInputs / OutputFrom for type-safe I/O.