arizuko › components › timed
timed
What it is
In plain terms, timed is the alarm clock. It sends the agent a message at a set time or on a repeating schedule, so things happen without anyone there to type.
timed is the scheduler daemon. One Go binary that ticks every 60 seconds, claims due rows from the scheduled_tasks table, and inserts each task’s prompt into messages as if a user had just typed it.
Once the message lands in the table, routd picks it up through the normal poll loop and routes it to the task’s folder. The agent has no idea the prompt came from a clock.
Why it exists
Cron, recurring runs, and one-shot reminders all collapse to one need: drop a prompt into a folder at a future time. Keeping that loop inside routd would mix two clocks — one for inbound polling, one for scheduling — and tie cron behavior to gateway restarts.
A separate daemon writing to the same DB means routd stays a pure router. Without timed, no task ever fires. Cron tools (schedule_task, pause_task, …) still write rows, but nothing claims them.
The loop
Each 60-second tick:
- Atomic
UPDATEflips every row wherestatus='active' AND next_run ≤ nowtostatus='firing'. Two timed instances cannot double-fire the same task. - Select the claimed rows. For each, insert one row into
messageswithsender='timed'(ortimed-isolated:<id>when the task asked for an isolated session) and the task’schat_jid. - Compute the next fire: parse the
croncolumn as a 5-field cron expression in the daemon’sTZ, or as a millisecond interval, or leave empty for one-shots. - Write the new
next_runback, flipstatustoactive(orcompletedfor a fired one-shot), and append one row totask_run_logs.
How it fits
agent (or operator)
| schedule_task / pause_task / …
v
scheduled_tasks (rows with cron + next_run)
|
| timed ticks every 60s, claims due rows
v
messages (one row per fire, sender='timed')
|
v
routd ---> routes by chat_jid ---> agent run
Inputs: scheduled_tasks rows written by MCP tools through routd. Outputs: synthetic messages rows and audit rows in task_run_logs.
Hard deps: read/write access to routd’s routd.db (which holds scheduled_tasks). No HTTP API today beyond /health; the /v1/tasks surface is planned (see timed/README.md).
Concepts: concepts/tasks covers the row anatomy, context modes, and the five MCP tools agents use to manage tasks.
Standalone usage
Yes. timed is a plain Go binary that needs an SQLite file with the scheduled_tasks and task_run_logs tables already migrated — routd owns + migrates routd.db, so spin up routd first (or copy its DB).
# build
make build # produces ./timed
# run against an existing arizuko data dir
DATA_DIR=/srv/data/myinstance TZ=Europe/Prague ./timed
# or point at any sqlite DSN directly
DATABASE=/path/to/messages.db ./timed
GET /health on :8080 returns 200 when db.Ping() succeeds. Red flag: no rows appearing in task_run_logs while scheduled_tasks has active rows past their next_run.
Key env vars
DATA_DIR— resolves<DATA_DIR>/store/messages.dbwhenDATABASEis unset.DATABASE— explicit SQLite DSN; wins overDATA_DIR.TZ— timezone used to evaluate cron expressions (defaultUTC).
Full list and defaults in reference/env.
Go deeper
- concepts/tasks — row anatomy, triggers, MCP tools, failure model.
timed/README.md— tables owned, planned/v1/taskssurface, token contract.specs/4/8-scheduler-service.md— original design.- concepts/routing — how routd picks up every synthetic message.