Package: timed/.
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.
For each task where status = 'active' and next_run <= now:
prompt as a message into the messages table with sender = 'scheduler'next_run via robfig/cron parser and update the rownext_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.
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.
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 use the robfig/cron format with seconds support:
0 9 * * 1-5 # 9am Monday–Friday
0 0 * * * # midnight daily
*/30 * * * * # every 30 minutes
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.
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.