arizuko

arizuko › components › timed

timed

timed ticks every 60 seconds, asks routd for the due tasks and posts the prompt of each task back as a message.

timed sends the agent a message on a schedule

timed is the alarm clock. It sends the agent a message at a set time or on a repeating schedule, so work happens when nobody is there to type.

timed is the scheduler daemon: one Go binary that ticks every 60 seconds. On each tick, it asks routd for the due tasks. It posts the prompt of each task back to routd as a message, as if a user had typed it. It opens no store. Every read and write goes over the HTTP API of routd with a service:timed token.

When the message is in messages, routd reads it in the normal poll loop and routes it to the folder of the task. The agent cannot tell that the prompt came from a clock.

A separate scheduler keeps routd a pure router

Cron jobs, recurring runs and one-time reminders all need one thing: a prompt in a folder at a future time. A scheduling loop inside routd would mix two clocks, one for inbound polling and one for scheduling, and tie cron behavior to restarts of routd.

A separate daemon that drives routd over HTTP keeps routd a pure router and the only writer of its tables. Without timed, no task ever runs. The cron tools, such as schedule_task and pause_task, still write rows, but nothing claims them.

Each tick runs four steps

Every 60 seconds, timed runs these steps:

  1. It calls GET /v1/tasks/due on routd. routd claims every active task whose next_run has passed, in one guarded write, and returns them. So two timed instances cannot run the same task twice.
  2. For each task, it calls POST /v1/messages with the chat_jid of the task. The sender is timed, or timed-isolated:<id> for a task with an isolated session. The message id derives from the task and its scheduled time, so routd drops a repeated post of the same run as a duplicate.
  3. It computes the next run with core.NextRun. The cron value is a 5-field cron expression in the TZ of the daemon, a millisecond interval, or empty for a one-time task.
  4. It calls POST /v1/tasks/{id}/reschedule with the new next_run, which marks a one-time task completed. Then it calls POST /v1/tasks/runlog for the task_run_logs row.

timed reads and writes only through routd

agent (or operator)
   │  schedule_task / pause_task / …
   ▼
 scheduled_tasks  (rows with cron + next_run)
   │
   │  timed ticks every 60s: GET /v1/tasks/due (routd claims them)
   ▼
 POST /v1/messages  (one message per fire, sender='timed')
   │
   ▼
 routd  ──►  routes by chat_jid  ──►  agent run
Inputs
The due tasks that routd returns. Agents and operators create those tasks through the task tools of routd.
Outputs
One message per run and one task_run_logs row. routd writes both on request of timed.
Dependencies
A reachable routd, which owns scheduled_tasks and serves /v1/tasks, and an authd that exchanges AUTHD_SERVICE_KEY for a token. timed itself serves /health, an empty /openapi.json because it mounts no resource, and the operator page /dash/timed/.

concepts/tasks describes the task row, the context modes and the MCP tools that agents use to manage tasks.

timed runs alone against a running routd

timed is a plain Go binary and opens no database of its own. It reads and writes tasks over the HTTP API of routd, so point it at a running routd. routd owns and creates scheduled_tasks and task_run_logs.

# build
make build              # produces ./timed

# run against a running routd
ROUTER_URL=http://routd:8080 \
AUTHD_URL=http://authd:8080 \
AUTHD_SERVICE_KEY=$(grep ^AUTHD_SERVICE_KEY .env | cut -d= -f2) \
TZ=Europe/Prague ./timed

GET /health on :8080 always returns 200, because timed holds no database handle to check. A problem shows as active scheduled_tasks rows past their next_run with no new rows in task_run_logs.

Three environment variables configure timed

ROUTER_URL
Required: the address of routd, for example http://routd:8080.
AUTHD_URL, AUTHD_SERVICE_KEY
timed exchanges the key at boot for a service:timed token. Without them, routd refuses every call.
TZ
The timezone for cron expressions, UTC by default.

reference/env has the full list and the defaults.

Three sources cover the details