store.Store for state persistence, a broker.TaskBroker for task dispatch, at least one executor.Plugin for execution logic, and an idgen.Generator for unique IDs. This guide walks through each one using the same in-memory implementations the playground CLI uses for integration testing, so every snippet below is taken directly from working code.
1
Install the module
Add the module to your Go project. The core engine has zero external dependencies, so this adds nothing to your transitive dependency tree beyond the Aether packages themselves.Verify the import path resolves:
2
Implement an executor plugin
An
executor.Plugin is what actually runs your task logic. It exposes a Type() string (matched against executor.type in the workflow JSON) and an Execute method that receives resolved inputs and returns outputs.The echo executor below is the same one used throughout the playground examples. It reads an outputs input parameter to discover what to return, making it useful for testing any workflow shape without real backend logic:The
executor.Plugin interface is the only place your domain logic lives. The engine never calls your business logic directly — it only dispatches task assignments to the broker, and the broker hands them to workers that call Execute.3
Implement the local broker
The The
broker.TaskBroker bridges the engine and your workers. The engine calls Dispatch when a task is ready; the broker delivers it to a worker; the worker calls StartTask and CompleteTask to report progress back.The channel-based LocalBroker below is modeled directly on the playground implementation:startHandler and handler callbacks wire directly back into the engine’s OnTaskStarted and OnTaskCompleted methods. This is the only coupling between the broker and the engine — callbacks, not direct method calls.4
Implement an ID generator
The For production use, replace this with a UUID generator or a distributed ID scheme such as Snowflake, depending on your consistency requirements.
idgen.Generator produces unique string IDs for workflow runs and task runs. The atomic counter below matches the playground’s AtomicIDGen:5
Wire the engine
With all four required dependencies ready, construct the engine using functional options. The
var eng *aether.Engine is declared before NewLocalBroker so that the callbacks can close over eng once it is assigned:6
Define a workflow document
The following JSON matches The
01-single-task.json from the playground examples. It submits a single greet task using the echo executor, passes username from workflow arguments, and declares five typed output parameters:valueFrom.parameter field uses a dot-path reference to the workflow-level argument username. At dispatch time the engine resolves this reference and injects the resolved value into the task’s inputs.7
Submit and observe the result
Parse the workflow document, submit it to the engine, then poll A completed execution from
engine.Get until the workflow reaches a terminal phase:engine.Get looks like this:What a WorkflowExecution contains
engine.Get returns a *aether.WorkflowExecution, which is the read-only projection of the workflow’s current state. All store-internal fields (tokens, raw JSON, deadline timestamps) are excluded — only the fields useful for observing and reporting are exposed:
Tasks is a TaskExecution:
Next steps
Architecture
Understand the scheduler internals, scope tree, and all ten extension interfaces
DAG workflows
Build multi-step pipelines with parallel execution and parameter passing
Executor plugin
Implement a production-grade executor with full schema validation
Store interface
Replace the in-memory store with a persistent database-backed implementation