tasks

scheduled agent runs · ← concepts

A task is a saved prompt with a schedule. When the schedule says it’s time, the prompt lands in a folder as if a user had just typed it, and the agent answers like any other turn.

what it is

One row in the scheduled_tasks table = one recurring or one-shot agent run. The body is a prompt string; the trigger is either a cron expression, an interval in milliseconds, or a single future timestamp.

mechanism

The timed daemon ticks every 60 seconds. On each tick it claims every row where status='active' and next_run ≤ now, flips them to 'firing', then for each claimed row inserts the prompt into the messages table with sender timed (or timed-isolated:<id>) and the task’s chat_jid.

The gateway polls messages the same way it polls every other inbound source. It sees the new row, routes by chat_jid, and fires a turn. The agent has no idea this prompt came from a clock instead of a person — same code path.

After insert, timed writes the next next_run (parsed from the cron expression or added from the interval), or marks the row 'completed' if there is no schedule. Every fire writes one row to task_run_logs with duration and status.

triggers

The cron column carries one of three shapes:

anatomy of a task

A task carries who owns it, where it fires, what it says, when it runs, and how it sees the folder around it.

Context mode decides what session the agent runs under. With group the synthetic message hits the folder’s main session like any other turn — the task sees the folder’s prior chat history and shares state with it. With isolated the sender becomes timed-isolated:<task-id>, which the gateway treats as its own conversation. Useful for a recurring health check that shouldn’t pollute the human chat’s context.

managing tasks

Five MCP tools, exposed to agents whose tier permits scheduling:

A companion tool, inspect_tasks, joins scheduled_tasks with task_run_logs so the agent can see when each task last fired and whether it succeeded.

There is no dedicated arizuko task CLI subcommand today. Operators reach the same rows through the agent (via arizuko chat) or directly with sqlite3 on store/messages.db.

failure model

If the synthetic message insert itself fails, timed logs an error row to task_run_logs and flips the task back to active without advancing next_run. The next tick will try again.

If the insert succeeds but the agent run downstream errors out, timed never knows — from the scheduler’s point of view the fire was successful. next_run advances normally and the task fires again at its scheduled time. There is no automatic retry of the failed turn. The error sits in the gateway’s logs and the folder’s chat history; an operator (or the agent itself, on its next run) decides what to do.

The status field never auto-flips to paused based on failures. A failing recurring task keeps firing until somebody pauses it or fixes the underlying problem.

go deeper

Daemon internals and the planned /v1/tasks HTTP surface: timed/README.md. Full scheduler design: specs/4/8. How the routed JID becomes a folder: routing.