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.

Many workflows need to run on a schedule — nightly data exports, hourly health checks, daily report generation. Aether models scheduled execution through a first-class CronWorkflow resource that pairs a standard cron expression with a WorkflowSpec. Each time the schedule fires, the engine submits a fresh workflow run on your behalf, tracks it alongside all previous runs from the same cron definition, and applies configurable concurrency policies to prevent overlapping runs from accumulating. The scheduler is crash-safe: when the engine restarts, it reloads all active CronWorkflow records from the store and re-registers their schedules automatically.

CronWorkflow Resource

A CronWorkflow is a top-level Aether resource with kind: CronWorkflow. Its spec wraps a standard WorkflowSpec alongside scheduling configuration:
{
  "apiVersion": "aether/v1",
  "kind": "CronWorkflow",
  "metadata": {
    "name": "cron-basic"
  },
  "spec": {
    "schedule": "0 * * * *",
    "workflowSpec": {
      "entrypoint": "main",
      "templates": [
        {
          "task": {
            "name": "main",
            "executor": { "type": "echo" },
            "outputs": {
              "parameters": [{ "name": "result", "type": "string" }]
            }
          }
        }
      ]
    }
  }
}
This CronWorkflow fires at the top of every hour and runs a single-task workflow.

CronWorkflowSpec Fields

FieldTypeDescription
schedulestringStandard five-field cron expression ("0 * * * *"). Required.
timezonestringIANA timezone name (e.g. "America/New_York"). Defaults to UTC.
startAtstringRFC3339 timestamp. Triggers before this time are suppressed.
endAtstringRFC3339 timestamp. Triggers after this time are suppressed.
concurrencyPolicystring"Allow", "Forbid", or "Replace". Defaults to "Allow".
startingDeadlineSeconds*intHow many seconds late a trigger may fire. nil = no deadline.
successfulJobsHistoryLimitintHow many successful run records to retain.
failedJobsHistoryLimitintHow many failed run records to retain.
suspendboolWhen true, scheduling is paused without deleting the resource.
workflowSpecWorkflowSpecThe workflow definition run on each trigger. Immutable after submit.

Concurrency Policies

The concurrencyPolicy field controls what happens when a new trigger fires while a previous run is still active.
Multiple concurrent runs are permitted. Each trigger creates a new workflow run regardless of whether previous runs have finished. This is the default.
{
  "spec": {
    "schedule": "0 * * * *",
    "concurrencyPolicy": "Allow",
    "workflowSpec": { "entrypoint": "main", "templates": [] }
  }
}
Use Allow when runs are truly independent and overlapping execution is acceptable.

Bounded Scheduling Windows

startAt and endAt

Use RFC3339 timestamps to restrict when the cron schedule is active:
{
  "spec": {
    "schedule": "0 9 * * 1-5",
    "timezone": "America/New_York",
    "startAt": "2026-01-01T00:00:00Z",
    "endAt":   "2026-12-31T23:59:59Z"
  }
}
Triggers that would fire before startAt or after endAt are suppressed. This is useful for seasonal or project-scoped workflows.

startingDeadlineSeconds

Controls how late a missed trigger may fire. If the engine is down when a trigger was scheduled, and the engine restarts within startingDeadlineSeconds, it fires the missed trigger. If the engine restarts later than the deadline, the trigger is dropped.
{
  "spec": {
    "schedule": "0 * * * *",
    "startingDeadlineSeconds": 300
  }
}
Setting this to 300 means the engine will fire a missed hourly trigger up to 5 minutes late. If startingDeadlineSeconds is null, there is no deadline and missed triggers are always fired on engine restart.

Engine API for CronWorkflows

Submitting

cronID, err := engine.SubmitCronWorkflow(ctx, &model.CronWorkflow{
    APIVersion: "aether/v1",
    Kind:       "CronWorkflow",
    Metadata:   model.Metadata{Name: "my-cron"},
    Spec: model.CronWorkflowSpec{
        Schedule:          "0 * * * *",
        ConcurrencyPolicy: model.ConcurrencyForbid,
        WorkflowSpec:      myWorkflowSpec,
    },
})
SubmitCronWorkflow returns a system-generated cronID. The WorkflowSpec is immutable after submit — to change it you must delete and re-submit.

Reading Status

exec, err := engine.GetCronWorkflow(ctx, cronID)
// exec.Runs contains []WorkflowExecution for all triggered runs

Updating

Only mutable fields (schedule, timezone, startAt, endAt, concurrencyPolicy, startingDeadlineSeconds, suspend) can be updated. Attempting to change WorkflowSpec returns ErrValidation.
err := engine.UpdateCronWorkflow(ctx, cronID, &model.CronWorkflow{
    Spec: model.CronWorkflowSpec{
        Schedule: "30 * * * *", // changed from "0 * * * *"
        Suspend:  false,
        WorkflowSpec: existingSpec, // must match exactly
    },
})

Pausing and Deleting

// Pause: schedule stops firing, resource is preserved
err := engine.UpdateCronWorkflow(ctx, cronID, &model.CronWorkflow{
    Spec: model.CronWorkflowSpec{Suspend: true, WorkflowSpec: existingSpec},
})

// Delete: stops scheduling, removes the resource (existing runs are NOT deleted)
err := engine.DeleteCronWorkflow(ctx, cronID)

Enabling CronWorkflow Support

CronWorkflow support is opt-in. You must provide a cron.Scheduler implementation via the WithCronScheduler option when creating the engine:
engine, err := aether.New(
    aether.WithStore(myStore),
    aether.WithTaskBroker(myBroker),
    aether.WithIDGenerator(myIDGen),
    aether.WithExecutor(myExecutor),
    aether.WithCronScheduler(myCronScheduler), // required for CronWorkflow
)
Without WithCronScheduler, calls to SubmitCronWorkflow, GetCronWorkflow, UpdateCronWorkflow, and DeleteCronWorkflow all return ErrNotSupported.

Crash Recovery

On Engine.Start(), the engine reloads all active (non-suspended) CronWorkflow records from the store and re-registers their schedules with the cron.Scheduler. This means the scheduler survives engine restarts without data loss, subject to startingDeadlineSeconds for triggers that were missed during the downtime window.
err := engine.Start(ctx)
// Engine now polls for timeouts and fires registered cron schedules
// Crash recovery: active CronWorkflows are re-registered automatically
defer engine.Stop()
WorkflowSpec is stored as an immutable snapshot at submit time. The engine uses the stored snapshot for every triggered run, so updating the CronWorkflow does not retroactively change what runs were launched with a previous spec.

Build docs developers (and LLMs) love