arizuko

arizukocomponents › 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:

  1. Atomic UPDATE flips every row where status='active' AND next_run ≤ now to status='firing'. Two timed instances cannot double-fire the same task.
  2. Select the claimed rows. For each, insert one row into messages with sender='timed' (or timed-isolated:<id> when the task asked for an isolated session) and the task’s chat_jid.
  3. Compute the next fire: parse the cron column as a 5-field cron expression in the daemon’s TZ, or as a millisecond interval, or leave empty for one-shots.
  4. Write the new next_run back, flip status to active (or completed for a fired one-shot), and append one row to task_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

Full list and defaults in reference/env.

Go deeper