Scheduler
The scheduler daemon is the background process that claims and fires triggers from pgserve. It tracks worker liveness via the state management layer. It runs as a persistent loop, combining real-time PostgreSQL notifications with poll-based fallback for reliability.Architecture
Configuration
GENIE_MAX_CONCURRENT.
Trigger Lifecycle
Claiming
Triggers are claimed using PostgreSQL’sSELECT FOR UPDATE SKIP LOCKED, which provides lease-based atomic claiming across multiple scheduler instances:
Idempotency
Each trigger can carry anidempotency_key. A unique index on this column prevents double-fire:
State Flow
Cron Expressions
The cron parser supports standard 5-field expressions with extensions:- Wildcards:
* - Ranges:
1-5 - Steps:
*/10,1-5/2 - Lists:
1,3,5
| Format | Example | Milliseconds |
|---|---|---|
| Seconds | 30s | 30,000 |
| Minutes | 10m | 600,000 |
| Hours | 2h, 1.5h | 7,200,000 / 5,400,000 |
| Days | 1d | 86,400,000 |
Heartbeat Collection
Every 60 seconds, the scheduler collects heartbeats from all active workers:- Pane liveness — checks if tmux panes are still alive
- Agent state — reads current state from the worker registry
- Context capture — stores pane content snapshot
heartbeats table:
Machine Snapshots
Every 60 seconds (alongside heartbeats), the scheduler captures a machine-level snapshot:Orphan Reconciliation
Every 5 minutes, the scheduler scans for orphaned runs — agents that have stopped responding:- Find runs in
leasedorrunningstatus - Check if the worker has missed more than 2 consecutive heartbeats
- Mark dead runs as
failedwith a reconciliation reason - Reclaim expired leases for retry
Reboot Recovery
On startup, the scheduler performs recovery:- Reclaim expired leases — triggers where
leased_until < now()are reset topending - Reconcile orphaned runs — runs without matching live workers are marked
failed - Resume polling — normal LISTEN + poll loop begins
Structured Logging
The scheduler writes structured JSON logs to~/.genie/logs/scheduler.log: