tasks
A task decides when a prompt arrives, also when no person is there to send one. A task is a saved prompt with a schedule. When the schedule says it is time, the prompt lands in a folder as if a user had typed it. The agent answers as on any other turn. A scheduled run uses no special code path: it is a synthetic message on the same path as every other message.
A task is one row with a prompt and a trigger
One row in the scheduled_tasks table is one recurring or one-time agent run. The body is a prompt string. The trigger is a cron expression, an interval in milliseconds, or one future timestamp.
timed posts the prompt as a message
The timed daemon ticks every 60 seconds. It opens no database. On each tick, it calls GET /v1/tasks/due on routd. routd claims every row where status='active' and next_run ≤ now, sets each one to 'firing' and returns them. For each row, timed posts the prompt to POST /v1/messages with the chat_jid of the task. The sender is timed, or timed-isolated:<id> for an isolated task.
routd reads the new row in the same way as every other inbound message, routes it by chat_jid and starts a turn. The agent cannot tell that the prompt came from a clock and not from a person, because the code path is the same.
After the post, timed asks routd to set the next next_run, from the cron expression or the interval. If the task has no schedule, routd marks the row 'completed'. next_run always comes from the cron value, and schedule_task takes no next_run. The one exception is the run-now action of the operator, a PATCH on the task over REST. Every run writes one row to task_run_logs with the duration and the status.
The trigger has one of three shapes
The cron column holds one of three shapes:
- Cron expression
- A standard 5-field cron, for example
0 9 * * 1-5for weekday mornings.robfig/cronparses it in the timezone of the task, which is theTZof the daemon, UTC by default. - Interval
- A plain integer string of milliseconds.
1800000means every 30 minutes. After each run, the nextnext_runisnow + interval. - One-time
- An empty
cron. The row runs once atnext_run, then changes tostatus='completed'and leaves the poll. The row stays in the table for audit, and nothing deletes it.
The context mode picks the session
One column of the row decides the session of the run. With group, the default, the synthetic message goes to the main session of the folder like any other turn. The task sees the chat history of the folder and shares state with it. With isolated, the sender becomes timed-isolated:<task-id>, and routd treats it as its own conversation. Use it for a recurring health check that must not fill the context of the human chat. reference / schema has the full column list of scheduled_tasks.
Agents manage tasks, and operators use REST
An agent with the scheduling grants gets MCP tools to create, list, pause, resume and cancel tasks. schedule_task creates a task and removes duplicates on the prompt, chat and schedule, so a repeated call returns the existing ID. inspect_tasks joins task_run_logs to show when each task last ran and whether it succeeded. reference / mcp has the full signatures.
The REST endpoints live on routd at /v1/tasks, not /v1/scheduled_tasks, because timed uses that prefix too:
GET /v1/tasks- Lists tasks.
GET /v1/tasks/{id}- Reads one task.
PATCH- Takes
{status}to pause or resume, and{next_run}to run now. DELETE- Cancels a task.
No arizuko task CLI subcommand exists. An operator can use the REST resource, and the agent can use arizuko chat.
Every group starts with five tasks: /compact-memories for episodes by day, week and month, and for the diary by week and month. They run isolated, so a compaction never summarises its own work. the ant shows what they compact.
A failed turn does not stop the schedule
If the insert of the synthetic message fails, timed writes an error row to task_run_logs. It sets the task back to active without a new next_run. The next tick tries again.
If the insert succeeds but the agent run fails later, timed does not know. For the scheduler, the run succeeded. next_run advances as usual, and the task runs again at its next time. Nothing retries the failed turn automatically. The error is in the logs of routd and in the chat history of the folder. An operator, or the agent on its next run, decides what to do.
A failure never changes the status to paused. A failing recurring task keeps running until someone pauses it or fixes the problem.
timed has its own page
[timed/README.md] and components/timed describe the daemon. routing shows how the routed JID becomes a folder.