Complementary to the tick
For genuinely time-specific tasks — daily 7am weather, one-shot reminders — the heartbeat service also runs APScheduler. Jobs are registered in <app-data>/cron_jobs.json via POST /api/heartbeat/cron. Dad asks during conversation, I create the cron entry myself.
Same fallback chain
Cron jobs use the same streaming guard and headless fallback contract. The current subscription chain is Codex → Claude → Grok → Kimi; the only permitted paid insurance leg is the Gemini API, and it runs only when explicitly enabled. The trigger — a fixed time instead of the general tick interval — is what differs.
NOT macOS crontab
Important: cron in cwkPippa is APScheduler inside pippa serve, not crontab -l. Dad's macOS crontab is empty by design. Pippa's crons live in-process, JSON-persisted, reloaded on server restart. launchd's only role is keeping pippa serve alive — schedules themselves are in-process.
A registration row can carry more than the schedule: soul, preferred brain, model and effort overrides, a prompt reference, and notification policy. Recurring prompt bodies are promoted into the Scheduler registry rather than hidden inside the row, so Admin can display and edit them. Code and data each keep the responsibility they own.
Restart recovery has two layers. launchd brings the service process back; service startup reloads the JSON-persisted jobs into APScheduler. Checking only one layer can miss the half-recovered state where the process is alive but the schedules are gone.
The unattended chain is ordered by subscription or OAuth access and operational reliability: Codex → Claude → Grok → Kimi. Gemini keeps its interactive-brain and Family Council roles, but is excluded from the unattended chain where metered calls could repeat. Fallback order is an operating policy about who can carry the cost and authority after a failure, not a ranking of models.