Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

0080 - Cron

  • Feature Name: Daemon-Level Cron Scheduler
  • Start Date: 2026-03-20
  • Discussion: #80
  • Crates: daemon

Summary

A daemon-level cron system that triggers skills into sessions on a schedule, replacing the previous per-agent heartbeat mechanism.

Motivation

Agents need periodic behavior — checking feeds, running maintenance, sending reminders. The original approach was a per-agent heartbeat config, but this was dead code and wrong-shaped: heartbeats are uniform intervals, while scheduled tasks need cron-style flexibility (every Monday at 9am, every 2 hours, etc.).

The session already carries the agent and sender. A cron entry only needs to know which skill to fire and which session to fire it into.

Design

A cron entry triggers a skill into a session on a schedule.

Data model

[[cron]]
id = 1
schedule = "0 */2 * * *"
skill = "check-feeds"
session = 12345
quiet_start = "23:00"
quiet_end = "07:00"
once = false
  • id — auto-incremented on create.
  • schedule — standard cron expression, validated on create and load.
  • skill — fired as /{skill} slash command into the session.
  • session — target session ID. The session determines the agent.
  • quiet_start/quiet_end — optional HH:MM window in the daemon’s local time. If fire time falls inside, skip silently. No queuing, no catch-up. Both must be set; if only one is provided, quiet hours are ignored.
  • once — fire once then delete from memory and disk.

Persistence

Memory is authoritative at runtime. Disk (crons.toml) is recovery for restarts.

  • Startup: load from disk, start timers. Invalid schedules are skipped with a warning.
  • Create/Delete: mutate memory, start/stop timer, atomic write to disk (tmp + rename).
  • Runtime reload: crons stay in memory — they survive runtime swaps.
  • Daemon restart: reload from disk.

Firing

Fire-and-forget via the daemon event channel. The cron sends a ClientMessage with content /{skill} and sender "cron". The reply channel is dropped — output goes to session history only.

Protocol

Three protocol operations on the Server trait:

  • CreateCron { schedule, skill, session, quiet_start?, quiet_end? }CronInfo
  • DeleteCron { id } → success/not found
  • ListCronsCronList

Crons are process-lifetime, not session-lifetime. They survive runtime reloads, fire via the daemon event channel, and the runtime has no notion of time-based scheduling. This is a daemon concern.

Alternatives

Per-agent heartbeat config. The original approach. Rejected because it coupled scheduling to agent definition, couldn’t express cron-style schedules, and was dead code.

Client-side polling. A client can send messages on its own timer. This works but requires the client to be running. Daemon crons fire regardless of client state.

Unresolved Questions

  • Should crons support arguments beyond the skill name?
  • Should there be a max cron count to prevent resource exhaustion?