Scheduled tasks in arizuko are not a gateway feature. They are a separate daemon — timed — that polls a task table and inserts rows into messages when a cron expression fires. The gateway picks those rows up in its normal poll. The integration surface is one table write.
The agent calls the schedule_task MCP tool, which the gateway's IPC server handles by writing to the scheduled_tasks table:
schedule_task({
"group_folder": "main",
"target_jid": "telegram:-1001234567890",
"content": "produce today's summary and post it",
"cron": "0 18 * * *"
})
The tool accepts standard 5-field cron expressions. One-shot tasks use run_at instead of cron:
schedule_task({
"group_folder": "main",
"target_jid": "telegram:-1001234567890",
"content": "send the weekly report",
"run_at": "2024-12-01T09:00:00Z"
})
timed/main.go runs a poll loop (default: every 30 seconds). On each tick:
scheduled_tasks where enabled = truelast_run is not within this minute: insert a row into messageslast_run on the taskThe inserted message row looks exactly like an inbound message from a channel adapter — same columns, same format. The gateway cannot tell the difference. It processes the message normally: resolves the group from target_jid, spawns a container, runs the agent.
-- scheduled_tasks table (simplified)
id INTEGER PRIMARY KEY
group_folder TEXT
target_jid TEXT
content TEXT
cron TEXT -- "0 18 * * *" or NULL
run_at DATETIME -- one-shot or NULL
last_run DATETIME
enabled BOOLEAN DEFAULT 1
-- timed inserts this when the cron fires:
INSERT INTO messages (chat_jid, sender_jid, content, origin, created_at)
VALUES (target_jid, 'scheduler', content, 'scheduled', now())
timed adds a row to messages. That is the entire scheduler-to-gateway integration.
A group's agent is responsible for summarizing the day's activity and posting it to a Telegram channel. The agent schedules this at setup:
schedule_task({
"group_folder": "main",
"target_jid": "telegram:-1001234567890",
"content": "produce today's summary from the diary and post it to the group",
"cron": "0 18 * * *"
})
At 18:00 each day, timed inserts a message. The gateway picks it up, spawns a container for the group, and the agent runs with full access to the group's diary, facts, and memory — then sends the summary via send_message.
Because the scheduler is just a producer on the messages table, you can run multiple timed instances — one per timezone, one with finer granularity, one for a different task type. They do not interfere. Each inserts rows; the gateway processes all rows. No coordination required.
The schema is the contract. timed knows the messages schema and the scheduled_tasks schema. It knows nothing about the gateway's routing logic, container lifecycle, or MCP server. The gateway knows nothing about timed.
This is the microservice design principle at work: small processes with clear contracts. The contract is a SQLite table. Any process that can write SQLite can be a task producer — a cron job, a webhook handler, an operator dashboard button. The gateway processes them all the same way.