Defining a scheduled task
Create aschedule.ts file anywhere under a background-tasks directory (conventionally server/schedule/):
server/schedule/nightly-report/schedule.ts
defineSchedule is an identity function — it exists purely for type inference and discovery, the same pattern as defineAgent.
string
required
Unique task name. Defaults to the folder path if omitted and the task is loaded via
loadBackgroundTasks.string
required
A 5-field cron expression (
minute hour day-of-month month day-of-week). Supports *, comma lists (1,15), ranges (9-17), and step values (*/15).(ctx: TaskContext) => unknown | Promise<unknown>
required
The task body. Receives a
TaskContext with now, payload (unused for scheduled tasks), and trace.Running the scheduler
loadBackgroundTasks(dir) walks the directory tree, imports every schedule.ts file’s default export, and derives each task’s name from its folder path if name wasn’t set explicitly.
Scheduler API
(task: ScheduledTask) => this
Register a task. Chainable.
(now: Date) => Promise<string[]>
Run every task due at
now. Idempotent per calendar minute — calling tick twice within the same minute only fires each task once. Returns the names of the tasks that ran.(intervalMs?: number) => () => void
Poll on an interval, default
60_000ms. Returns a stop function that clears the interval.schedule <name>, tagged with the task’s cron expression. A task that throws is caught, the span closes with status error, and the failure is passed to onError (default: console.error) — one failing task never stops the scheduler or affects other tasks.
Scheduler runs in-process on a setInterval — it fires as long as your server process is alive. For multi-instance deployments, either run the scheduler on a single dedicated instance, or add your own leader-election/locking around tick() so the same task doesn’t fire once per instance.Testing a schedule without waiting
Calltick() directly with a specific Date to simulate a firing time in tests, instead of waiting for the real clock:
See also
- Triggered agents — the event-driven counterpart, fired by webhooks and queue messages instead of a clock
- Observability — every scheduled run appears in the same trace dashboard as request-driven agents
- Durability & safety — checkpoint long-running scheduled agent loops so a crash mid-run resumes cleanly
Defining a scheduled task
Create aschedule.ts file anywhere under a background-tasks directory (conventionally server/schedule/):
server/schedule/nightly-report/schedule.ts
defineSchedule is an identity function — it exists purely for type inference and discovery, the same pattern as defineAgent.
string
required
Unique task name. Defaults to the folder path if omitted and the task is loaded via
loadBackgroundTasks.string
required
A 5-field cron expression (
minute hour day-of-month month day-of-week). Supports *, comma lists (1,15), ranges (9-17), and step values (*/15).(ctx: TaskContext) => unknown | Promise<unknown>
required
The task body. Receives a
TaskContext with now, payload (unused for scheduled tasks), and trace.Running the scheduler
loadBackgroundTasks(dir) walks the directory tree, imports every schedule.ts file’s default export, and derives each task’s name from its folder path if name wasn’t set explicitly.
Scheduler API
(task: ScheduledTask) => this
Register a task. Chainable.
(now: Date) => Promise<string[]>
Run every task due at
now. Idempotent per calendar minute — calling tick twice within the same minute only fires each task once. Returns the names of the tasks that ran.(intervalMs?: number) => () => void
Poll on an interval, default
60_000ms. Returns a stop function that clears the interval.schedule <name>, tagged with the task’s cron expression. A task that throws is caught, the span closes with status error, and the failure is passed to onError (default: console.error) — one failing task never stops the scheduler or affects other tasks.
Scheduler runs in-process on a setInterval — it fires as long as your server process is alive. For multi-instance deployments, either run the scheduler on a single dedicated instance, or add your own leader-election/locking around tick() so the same task doesn’t fire once per instance.Testing a schedule without waiting
Calltick() directly with a specific Date to simulate a firing time in tests, instead of waiting for the real clock:
See also
- Triggered agents — the event-driven counterpart, fired by webhooks and queue messages instead of a clock
- Observability — every scheduled run appears in the same trace dashboard as request-driven agents
- Durability & safety — checkpoint long-running scheduled agent loops so a crash mid-run resumes cleanly