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.

Hooks give you a way to run arbitrary workflow logic in response to lifecycle events without polluting the main execution graph with observability concerns. A hook is simply a reference to a named template that the engine fires when a specific phase transition occurs — when the workflow starts, when a task succeeds, when a task is cancelled, or when anything terminates. Because hooks are declared on the workflow resource itself, they are version-controlled alongside the rest of the workflow definition and require no changes to executor code or host applications.

The Hook System

Hooks are implemented through the hook.Notifier interface. When an event fires, the engine calls Notifier.Notify() with the event type, the workflow run ID, and the task run ID (if applicable). The notifier then executes the referenced template — typically by submitting a new single-task sub-workflow or dispatching the template through the broker. Hook execution is fire-and-forget: if a hook template fails, that failure is reported to the errsink.ErrorSink but does not affect the phase of the triggering task or the overall workflow state. This ensures hooks never block the primary execution path. To enable hooks, inject a hook.Notifier implementation when creating the engine:
engine, err := aether.New(
    aether.WithStore(myStore),
    aether.WithTaskBroker(myBroker),
    aether.WithIDGenerator(myIDGen),
    aether.WithExecutor(myExecutor),
    aether.WithHookNotifier(myHookNotifier), // enables hook dispatch
)

Where Hooks Are Declared

Hooks can be attached at two levels.

Workflow-level hooks

Declared on spec.hooks. Fire once for the entire workflow run based on the workflow’s terminal phase.

Task-level hooks

Declared on individual DAG task nodes via the hooks field. Fire when that specific task transitions phases.

Workflow-Level Hooks

Workflow-level hooks are declared on spec.hooks and fire based on the overall workflow outcome:
{
  "apiVersion": "aether/v1",
  "kind": "Workflow",
  "metadata": { "name": "hooks-lifecycle" },
  "spec": {
    "entrypoint": "main",
    "hooks": {
      "onStart":   { "template": "hook-task" },
      "onSuccess": { "template": "hook-task" },
      "onExit":    { "template": "hook-task" }
    },
    "templates": []
  }
}

Workflow Hook Events

HookWhen it fires
onStartWhen the workflow transitions from PhaseCreated/PhaseReady to PhaseRunning (first task starts).
onSuccessWhen the workflow transitions to PhaseSucceeded.
onFailureWhen the workflow transitions to PhaseFailed.
onErrorWhen the workflow transitions to PhaseError.
onCancelWhen the workflow transitions to PhaseCancelled (via Engine.Cancel()).
onExitWhen the workflow reaches any terminal phase (Succeeded, Failed, Error, Timeout, Cancelled).
onExit is an always-run hook — use it for cleanup, notification, or audit logging that must happen regardless of outcome.

Task-Level Hooks

Task-level hooks are declared on DAG task nodes (call sites), not on the template definition. This means the same template can have different hooks depending on where it is called from:
{
  "dag": {
    "name": "main",
    "tasks": [
      {
        "name": "step-a",
        "template": "work-task",
        "hooks": {
          "onStart":   { "template": "hook-task" },
          "onSuccess": { "template": "hook-task" },
          "onExit":    { "template": "hook-task" }
        }
      },
      {
        "name": "step-b",
        "template": "work-task",
        "dependencies": ["step-a"],
        "hooks": {
          "onStart":   { "template": "hook-task" },
          "onSuccess": { "template": "hook-task" },
          "onExit":    { "template": "hook-task" }
        }
      }
    ]
  }
}

Task Hook Events

HookWhen it fires
onStartWhen the task transitions to PhaseRunning (executor begins).
onSuccessWhen the task transitions to PhaseSucceeded.
onFailureWhen the task transitions to PhaseFailed.
onErrorWhen the task transitions to PhaseError.
onSuspendWhen the task transitions to PhaseSuspended (executor returned ExecCodeSuspended).
onResumeWhen Engine.Resume() is called and the task is re-dispatched.
onCancelWhen the task transitions to PhaseCancelled (workflow cancelled while task was running or suspended).
onExitWhen the task reaches any terminal phase.
onSuspend and onResume are meaningful only for tasks that use the suspend/resume pattern. They fire on every suspend/resume cycle, not just the first.

Suspend/Resume Hook Lifecycle

The full hook sequence for a task that suspends once then completes:
{
  "name": "approval",
  "template": "suspend-task",
  "hooks": {
    "onStart":   { "template": "hook-task" },
    "onSuspend": { "template": "hook-task" },
    "onResume":  { "template": "hook-task" },
    "onSuccess": { "template": "hook-task" },
    "onExit":    { "template": "hook-task" }
  }
}
1

Task dispatched to executor

onStart fires.
2

Executor returns ExecCodeSuspended

Task moves to PhaseSuspended. onSuspend fires.
3

Engine.Resume() called with approval payload

Task is re-dispatched with merged inputs. onResume fires.
4

Executor returns ExecCodeSucceeded

Task moves to PhaseSucceeded. onSuccess fires.
5

Exit hook runs

onExit fires (always runs on terminal phases).

Cancellation Hooks

Cancel hooks fire when a workflow is stopped via Engine.Cancel(). Workflow-level onCancel fires once for the workflow; task-level onCancel fires for each task that was non-terminal at the time of cancellation:
{
  "spec": {
    "hooks": {
      "onStart":  { "template": "hook-task" },
      "onCancel": { "template": "hook-task" },
      "onExit":   { "template": "hook-task" }
    }
  }
}
And on the specific task node that may be cancelled:
{
  "name": "wait",
  "template": "task-suspend",
  "hooks": {
    "onCancel": { "template": "hook-task" },
    "onExit":   { "template": "hook-task" }
  }
}

Hook Reference Format

Each hook is an object with a single template field naming the template to execute:
{
  "onSuccess": { "template": "my-notification-task" }
}
The referenced template must exist in the workflow’s spec.templates list. The engine does not pass the triggering task’s inputs or outputs to the hook template automatically — design your hook templates to be self-contained or read state from an external store if needed.
Hook templates run as standalone task invocations. They execute after the phase transition is committed to the store, so the triggering task’s final phase and outputs are already readable when the hook runs.

Fire-and-Forget Semantics

Hook execution is intentionally decoupled from the primary workflow phase machine:
  • A hook template that fails is reported to the errsink.ErrorSink but does not change the triggering task’s phase.
  • Hook failures do not trigger retries or cascade to the workflow’s terminal phase.
  • If no hook.Notifier is configured (the WithHookNotifier option is omitted), hook declarations in the workflow document are silently ignored — the workflow runs normally without notifications.
This design ensures that observability hooks never become a single point of failure for production workflows.
Because hooks are fire-and-forget, avoid using them for operations that must succeed for the workflow to be correct. Use regular DAG task dependencies for that. Hooks are best suited for notifications, audit logging, and metrics emission.

Build docs developers (and LLMs) love