Skip to main content
Pylon runs background work with the same functions you already write. It needs no separate worker process, queue service, or cron daemon. There are two shapes: recurring (cron) and one-shot (deferred).

Recurring: cron(...)

Declare a cron in your manifest. It fires a function every time the schedule matches (the scheduler checks once a minute).
app.ts
The second argument is the name of a function in functions/: a normal query, mutation, or action:
functions/hourlyRollup.ts
Make cron functions internal: true. A cron function is also a regular function, so without internal: true, it is reachable at /api/fn/<name>. Marking it internal means only the scheduler (and ctx.runMutation) can invoke it.
Auth. A cron has no caller, so it runs with anonymous auth, the same as Pylon’s own built-in maintenance jobs. You usually do not need to do anything about that. A function’s own ctx.db.* calls run server-side and are not subject to entity policies (those gate client sync, not trusted server code), so a maintenance cron reads and writes its entities directly. Elevate only in two cases, and always with an audit reason:
reason is mandatory. Every elevation is logged, so an operator can audit who elevated and why. Pylon never grants admin to a cron implicitly. Schedule format. Standard 5-field cron: minute hour day-of-month month day-of-week. A schedule that does not parse, or a function name that is not registered, is logged loudly at boot and skipped. A typo fails visibly in pylon dev instead of silently never running. Durability. Cron jobs are backed by a persistent job store. A fire that is in flight survives a restart, and the schedule resumes after a redeploy. Postgres stores the queue in the application database. SQLite stores it in a local sidecar database.
Execution is at least once. A worker can finish an external side effect and stop before it records completion. Another worker can then run the job again. Make every scheduled function idempotent.
On Postgres, all replicas use one shared queue. Workers use row locks and short leases to claim jobs. Another replica can recover a job after its lease expires. One elected cron scheduler adds each matching cron to the shared queue once per tick. During a rolling release, each replica claims only functions that exist in that release. SQLite jobs stay on one machine. Do not run one SQLite database on multiple replicas.

One-shot: ctx.scheduler

To run something later (not on a repeating schedule), schedule it from inside a mutation or action:
The scheduled function runs with the auth identity of the caller that scheduled it. Pylon enforces this identity at schedule time. The args you pass are delivered verbatim. Like crons, these scheduled calls are durable across restarts. When a Postgres mutation calls runAfter or runAt, Pylon commits the scheduled job in the same database transaction as the mutation writes. A rollback removes both the writes and the job. Scheduling from an action cannot share a transaction with its external work.
Before cron(...) existed, the common pattern for recurring work was a self-perpetuating function: one that re-armed itself with ctx.scheduler.runAfter(...) at the end. That still works, but cron(...) is clearer and cannot silently stop if a re-arm is missed.