Scheduled tasks
What a scheduled task is on Aetherfy
A scheduled task on Aetherfy is an agent that Aetherfy starts for you at fixed
times. It is not a separate resource: there is nothing to create, register, or
attach. One field in aetherfy.yaml — schedule: — turns an agent into a
scheduled task, and removing that field turns it back.
Each time a schedule fires, Aetherfy starts a run. The run reuses the agent’s
existing image, so there is no build step and no deploy latency at fire time.
Runs started this way are recorded with trigger_source=cron, which is how you
tell them apart from runs you started by hand. See
/agents/runs-and-logs.
Declaring a scheduled task in Aetherfy configuration
Add schedule: to the agent’s aetherfy.yaml and deploy. A complete
configuration for a task that runs daily at 03:00 UTC:
name: nightly-report
runtime: python3.12
type: job
entrypoint: main.py
memory_mb: 512
schedule: "0 3 * * *"Quote the expression. Unquoted, a YAML parser can read some expressions as something other than a string, and Aetherfy needs the literal text.
Deploy it exactly like any other Aetherfy agent:
afy deploytype: job is a literal configuration value and is required here. Aetherfy only
schedules agents of that type, because a long-lived service agent is already
running and so has nothing to start on a cadence.
The Aetherfy schedule expression format
An Aetherfy schedule expression is exactly 5 whitespace-separated fields, always interpreted in UTC.
┌───────────── minute
│ ┌─────────── hour
│ │ ┌───────── day of month
│ │ │ ┌─────── month
│ │ │ │ ┌───── day of week
│ │ │ │ │
* * * * *| Position | Field | Range |
|---|---|---|
| 1 | minute | 0–59 |
| 2 | hour | 0–23 |
| 3 | day of month | 1–31 |
| 4 | month | 1–12 |
| 5 | day of week | 0–6 (0 = Sunday) |
One honest caveat about how Aetherfy validates this. Only the minute field
has a hand-written grammar in Aetherfy. Fields 2–5 are handed to the underlying
parser, which accepts a broader dialect than the table above — including 7 for
Sunday and name forms for the day of week. The table is the supported core, not
the outer limit of what will parse. Write expressions that fit the table and
your schedule will behave as documented.
Schedule expressions Aetherfy rejects
Aetherfy validates the expression at deploy time, so an invalid schedule fails the deploy rather than silently never firing. Each rejection has its own message.
| Rejected input | Example | Why |
|---|---|---|
Any @-alias | @hourly | Aliases are not supported — write the 5 fields |
| 6-field syntax with seconds | 0 0 3 * * * | Exactly 5 fields are required |
| An expression that can never fire | 0 0 30 2 * | ”schedule never fires (impossible date combination)” — February has no 30th |
| A stepped wrapping minute range | 50-10/15 | ”unsupported minute-field syntax” |
| Anything firing more often than every 300 seconds | */2 * * * * | Below the Aetherfy minimum interval — see below |
The minimum interval on Aetherfy
The minimum interval between fires on Aetherfy is 5 minutes (300 seconds). An expression that would fire more often is rejected with:
schedule: fires more often than every 300 seconds (minimum interval).
For continuous or sub-interval work, use an always-on agent (keep_alive)
instead of a schedule.That message names the right alternative. If you need work done continuously or
faster than every five minutes, a schedule is the wrong instrument — deploy a
long-running agent with keep_alive: true and control the cadence inside your
own program. Always-on agents require a plan that permits them; see
/platform/limits.
Why Aetherfy schedules are UTC only
Every Aetherfy schedule expression is interpreted in UTC. There is no timezone field, and this is deliberate rather than an omission.
A local-time schedule is ambiguous twice a year at daylight-saving boundaries: in the spring the named local time does not exist, and in the autumn it happens twice. Rather than pick a silent policy for those two days, Aetherfy defines schedules in a timezone that has no such boundaries.
If you want a task to land at a fixed local time, convert that time to UTC yourself and accept that it will drift by an hour across a daylight-saving change, or schedule it at a UTC hour where the drift does not matter.
Examples of Aetherfy schedule expressions
| Expression | Fires |
|---|---|
0 * * * * | Every hour, on the hour |
*/15 * * * * | Every 15 minutes |
0 3 * * * | Daily at 03:00 UTC |
30 2 * * 1 | Mondays at 02:30 UTC |
15 2 * * 1-5 | Weekdays at 02:15 UTC |
Where Aetherfy allows a schedule
Three rules govern where a schedule is valid on Aetherfy.
| Rule | Detail |
|---|---|
Only on type: job agents | A long-lived service agent is already running, so there is nothing to start on a cadence. Aetherfy rejects a schedule on a service agent. |
| One schedule per agent | To run the same code on two cadences, deploy it as two agents with different names. |
| Not on a spawned worker | An agent that is spawned as a worker by a parent agent cannot carry a schedule. Rejected with AGENT_SCHEDULE_NOT_ALLOWED_ON_WORKER. |
The one-schedule-per-agent rule is worth planning around. Two agents pointing at
the same repository directory, differing only in name and schedule:, is the
supported way to run one program hourly and also nightly.
Changing or removing an Aetherfy schedule
Aetherfy applies aetherfy.yaml as a merge patch, which gives the schedule
field four distinct behaviours depending on what you deploy.
| What you deploy | What Aetherfy does |
|---|---|
schedule omitted | Preserves the existing schedule and does not move the next run |
schedule with the same expression | Leaves the cursor alone — the next run does not shift |
schedule with a new expression | Recomputes the next run from now |
schedule: null | Removes the schedule, removes its cursor, and clears any pause you had set |
The last row matters if you are using pause as a temporary off switch. Deploying
schedule: null does not preserve a paused state for later — it clears the
pause along with the schedule, so re-adding an expression later starts a live
schedule rather than a paused one.
To stop scheduled runs without editing configuration at all, pause instead:
afy agents schedule pause nightly-report
afy agents schedule resume nightly-reportPausing and resuming, overlap behaviour, and missed windows are covered on /agents/managing.
Building a schedule in the Aetherfy dashboard
You do not have to write the expression by hand. The Aetherfy configurator at https://app.aetherfy.com/dashboard/agents/configurator builds a valid expression from presets and previews the upcoming runs in both UTC and your local time, which is the fastest way to confirm that a UTC expression lands where you expect it to locally.
What to read next about Aetherfy scheduled tasks
| Page | What it covers |
|---|---|
| /agents/task-contract | How your code runs and exits, how it reads input, and the at-most-once guarantee |
| /agents/managing | Running on demand, pausing and resuming, overlaps, missed windows |
| /agents/runs-and-logs | Run history, run states, and log retrieval |
| /agents/aetherfy-yaml | Every configuration field and the merge-patch rules |