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:
- It calls
GET /v1/tasks/dueon routd. routd claims every active task whosenext_runhas passed, in one guarded write, and returns them. So two timed instances cannot run the same task twice. - For each task, it calls
POST /v1/messageswith thechat_jidof the task. The sender istimed, ortimed-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. - It computes the next run with
core.NextRun. Thecronvalue is a 5-field cron expression in theTZof the daemon, a millisecond interval, or empty for a one-time task. - It calls
POST /v1/tasks/{id}/reschedulewith the newnext_run, which marks a one-time task completed. Then it callsPOST /v1/tasks/runlogfor thetask_run_logsrow.
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_logsrow. routd writes both on request of timed. - Dependencies
- A reachable routd, which owns
scheduled_tasksand serves/v1/tasks, and an authd that exchangesAUTHD_SERVICE_KEYfor a token. timed itself serves/health, an empty/openapi.jsonbecause 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:timedtoken. Without them, routd refuses every call. TZ- The timezone for cron expressions,
UTCby default.
reference/env has the full list and the defaults.
Three sources cover the details
- concepts/tasks: the task row, triggers, MCP tools and the failure model.
- [timed/README.md]: the run loop, the rule of one id per run and the token contract.
- concepts/routing: how routd reads every synthetic message.