Scheduler

Package: timed/.

Design

The scheduler's entire integration with the gateway is one SQL statement: INSERT INTO messages. There is no scheduler-aware code in gated. Scheduled messages are indistinguishable from messages sent by a channel adapter.

The scheduler runs as a separate daemon (timed) that opens the same SQLite database as the gateway. It polls scheduled_tasks every 60 seconds.

Poll loop

For each task where status = 'active' and next_run <= now:

  1. Insert the task's prompt as a message into the messages table with sender = 'scheduler'
  2. For recurring tasks: compute next_run via robfig/cron parser and update the row
  3. For one-shot tasks: set next_run = NULL and status = 'done'

The gateway picks up the inserted message in its next poll tick, routes it to the appropriate group via the chat_jid field, and runs the agent container as normal.

Isolated task runs

Tasks flagged for isolated execution get their own container invocation, separate from any ongoing conversation context. The container name is arizuko-<folder>-task-<task_id> and the sender is scheduler-isolated:<task_id>. The gateway detects this sender prefix and skips the shared message cursor, giving the task a clean context window.

Task schema

CREATE TABLE scheduled_tasks (
  id         TEXT PRIMARY KEY,
  owner      TEXT,              -- group folder that owns this task
  chat_jid   TEXT,              -- destination JID
  prompt     TEXT,              -- message content to inject
  cron       TEXT,              -- cron expression (null = one-shot)
  next_run   DATETIME,          -- next scheduled execution time
  status     TEXT,              -- active | paused | done | error
  created_at DATETIME
);

Cron expressions

Cron expressions use the robfig/cron format with seconds support:

0 9 * * 1-5        # 9am Monday–Friday
0 0 * * *          # midnight daily
*/30 * * * *       # every 30 minutes

Database sharing

timed opens the same messages.db as gated (WAL mode allows concurrent writers). It runs its own migration via the shared migrations table keyed by service name "timed". The migration creates scheduled_tasks if it does not exist; this is idempotent with gated's own schema which also creates the table.

Agent-managed tasks

Agents can create, update, and cancel tasks via IPC tools: schedule_task, get_task, update_task, cancel_task, list_tasks. These write directly to scheduled_tasks; timed picks them up on its next poll.