Skip to main content
A scheduler provider decides when to fire. The default polls in-process; swap it for systemd, a cloud scheduler webhook, or a cron trigger without changing what fires. A provider never decides what fires — it just calls on_due whenever a tick should occur, and the shared runner and store claim and fire due jobs.

Which Provider?

Pick in-process when one host runs all the time; pick an external provider when firing should come from elsewhere.

Quick Start

1

Default — in-process poll (nothing to configure)

2

External / serverless — event-driven, no always-on thread


How It Works

The provider owns the trigger; the runner and store own the firing. on_due is the seam: the provider calls it whenever its trigger fires, and everything downstream — claiming due jobs and firing them — happens in the runner and store.
PieceOwns
Provider (SchedulerProviderProtocol)When a tick happens
ScheduleRunner + storeWhat is due and how it fires

Provider Options

Two ways to drive firing — one always-on, one event-driven.
ProviderWhen to useAlways-on thread?
InProcessScheduleProvider (default, alias for ScheduleLoop)Single always-on processYes (daemon)
External (webhook / systemd / cron / K8s CronJob / APScheduler)Multi-host, serverless, or delegate to existing schedulerNo
Any object with start(on_due, store=...) and stop() satisfies SchedulerProviderProtocol.

User Interaction Flow

A reminder set through the agent still fires even when a cloud scheduler drives the tick.

Configuration Options

Providers are protocol-only — see the auto-generated SDK reference for full signatures.

Scheduler Protocols

SchedulerProviderProtocol, ScheduleStoreProtocol, JobConditionProtocol, GateResult

Schedule Tools

Agent-callable schedule_add / schedule_list / schedule_remove

Common Patterns

Cloud webhook

Cloud Scheduler, EventBridge, or a GitHub Actions cron posts to an HTTP handler that calls fire_due().

systemd / launchd timer

A timer unit runs a short command on schedule; the command fires one tick and exits.

Kubernetes CronJob

A short-lived pod runs fire_due() and exits — no long-running scheduler pod.

Best Practices

start() on a running loop is a no-op by design. Treat one loop as one provider — don’t try to reconfigure it mid-run.
In webhook, cron, or systemd handlers call engine.fire_due() and never call start() — you get one tick’s work with no daemon thread.
fire_due() runs its claim + fire step under a per-instance lock, so an external caller and the loop thread can’t double-fire on non-atomic stores.
When firing from multiple hosts, use a store that implements claim_due for cross-host at-most-once delivery.

Background Tasks — ScheduleLoop

The in-process poll loop and combined recipes

Schedule Tools

Let agents create schedules via tool calls

Scheduled Run Policy

Safety gate for unattended scheduled runs

Scheduler Pre-Run Gate

Skip ticks cheaply before spending tokens