Stdlib: Schedule
Alpha (v0.1). Breaking changes expected.
The Schedule namespace provides recurring, delayed, and one-shot scheduling. It’s a library, not a keyword — which means you can use dynamic intervals, cron expressions, and user-defined event sources without language changes.
Under the hood, Schedule sits on top of the runtime’s timer primitives (__runtime.sleep, __runtime.deadline) and the agent mailbox, and emits events into whichever agent registered the schedule.
Schedule.every — recurring execution
Schedule.every(5.minutes, () => {
check_inbox()
})
Schedule.every(1.hour, () => {
sync_data()
})
Dynamic intervals work because it’s just a function call:
interval = if load_is_high() { 10.minutes } else { 5.minutes }
Schedule.every(interval, () => { heartbeat() })
Schedule.every with calendar alignment Coming soon
Schedule.every(1.day, at: @9am, () => {
send_weekly_report()
})
Schedule.every(monday, at: @9am, () => {
start_of_week_checklist()
})
Status: the
at:alignment argument is parsed but ignored in v0.1 — intervals fire relative to when the schedule is registered, not anchored to a clock time.
Schedule.after — delayed one-shot
Schedule.after(30.minutes, () => {
follow_up(ticket)
})
Schedule.after(2.hours, () => {
Io.notify("Check on deployment")
})
Schedule.at — absolute time
Schedule.at(@2026-04-20_10am, () => {
launch_campaign()
})
Schedule.cron — full cron expressions Coming soon
Schedule.cron("0 */15 9-17 * * MON-FRI", () => {
poll_status()
})
Status:
Schedule.cronis reserved in the grammar but not registered in v0.1. Tracked in ROADMAP.
Inside an agent
A schedule typically lives in an @on_start lifecycle attribute so it’s set up once when the agent starts:
agent DailyDigest {
@role "Produce a daily digest of important emails"
@on_start {
Schedule.every(1.day, at: @8am, () => {
summary = produce_digest()
Email.send(summary, to: Env.require("DIGEST_TO"))
})
}
}
Cancelling a schedule Coming soon
Schedule.every, after, at, and cron return a handle you can cancel:
heartbeat = Schedule.every(30.seconds, () => { ping() })
# Later
heartbeat.cancel()
Status: v0.1 scheduling calls return
none— cancellation handles are not yet plumbed through. Schedules live for the lifetime of the enclosing agent.
Duration literals
Durations use the .unit suffix and are arithmetic-compatible:
5.seconds 30.minutes 2.hours 1.day 7.days
extended = 30.seconds * 2 # 60 seconds
timeout = 5.minutes + 30.seconds
Both singular and plural forms work (1.day, 2.days).
Why a library, not keywords
Schedule.every, Schedule.after, and Schedule.at are prelude functions rather than hard-coded keywords. This matters because common patterns — dynamic intervals like every N.minutes where N depends on state, cron expressions, pause/resume logic, user-defined event sources (webhooks, subscriptions) — all fight fixed keyword syntax. Keeping Schedule.* a library sidesteps every one of them. See The Prelude & Interfaces.