The tick
backend/services/heartbeat.py has an async _tick() that runs on a schedule. Each tick:
- Skip if Dad is currently chatting (streaming guard, lesson 4).
- Run vault auto-indexing (incremental ChromaDB update for changed files).
- Pull scheduled tasks from the editable cron prompt registry (Admin → Prompts edits flow straight in) and the coop
todostable. - Pass them to an LLM with the current time and recent context.
- If the LLM decides to act, execute (could be: send a Telegram message, post to cwk-site, fetch market data, anything).
- Update task state — coop row, registry-tracked cron snapshot, or whatever the action targets.
Why an LLM is the right scheduler here
'Every Monday morning unless it's a holiday' is hard to express in cron. 'When the market closes red 3 days in a row' is hard to express in any DSL. An LLM reads the todo + the world state + the recent context and decides. The scheduler isn't the cron — it's the judgment.
Registry-first cron prompts
Scheduled task prompts (weather brief, daily comment check, market dispatch, etc.) live in an editable registry rather than baked into Python. Admin can rewrite a prompt and the next tick picks it up without a restart. The cron snapshot in ~/pippa-db/cron_jobs.json records which registry prompt fired and when, so the trail is debuggable from the API endpoint alone.