Cron jobs — scheduled agents

🔵 Agent OS can run an agent on a schedule, without a user. A cron job injects a prompt into the template's session and runs one turn — just like a chat request, so budgets, hooks, tools, and the event store behave the same way. The resulting response is delivered to one or more sinks.

Where they are defined

1. On an agent — the cron: list in config/agents/<agent>/agent.yaml (template defaults to the agent itself):

# config/agents/rss/agent.yaml
cron:
  - name: morning-digest
    schedule: "0 0 9 * * *"
    prompt: "Резюмируй сегодняшние заголовки."
    notify: [{ type: log }]

2. Globally — config/cron.yaml (each job names template):

# config/cron.yaml
jobs:
  - name: morning-digest
    template: rss
    schedule: "0 0 9 * * *"
    prompt: "Резюмируй сегодняшние заголовки."

On a name collision, config/cron.yaml wins over agent.yaml. Both sources are hot-reloaded; dynamic (script-created) jobs are independent of both.

Job reference

Field Type Meaning
name string (required) unique job id
template string template to run (in agent.yaml — implicit)
schedule string cron expression: sec min hour dom month dow (UTC)
timezone string IANA timezone (e.g. Europe/Moscow) in which schedule is interpreted; defaults to UTC
interval_secs int fixed interval (alternative to schedule)
oneshot_at string RFC 3339 time — fire once, then go away
prompt string (required) user message injected into the session
persistent bool true reuses the cron:<name> session (keeps context); defaults to false
timeout_secs int per-run limit
overlap string skip (default) | queue | parallel
retry map { max, backoff_secs } (reserved)
enabled bool defaults to true
notify list where the result is delivered (sinks below)

Exactly one of schedule / interval_secs / oneshot_at must be set.

Sinks

A job can have any combination of sinks. Without notify, the result is available only through the run history (GET /v1/cron, cron.db).

type What it does Fields
log writes the response to the server log —
webhook POSTs the result (JSON) to a URL url, token_env (optional bearer from env)
telegram sends the response to a Telegram chat chat_id, token_env (opt.; defaults to the template's bot)
vk sends the response to a VKontakte chat peer_id, token_env (opt.; defaults to the template's community token)
yandex_messenger sends the response to Yandex Messenger chat_id (chat/channel id or user login), token_env (opt.; defaults to the template's bot token)
messages_db persists the response to the message store scope (defaults to cron:run:<name>)
agent feeds the response to another agent as a message template, session (opt.), run (defaults to true), prompt_wrap (opt., {result})
email sends the response via SMTP to, subject (opt.; defaults to the job name), smtp
workflow starts a workflow (ignores the agent run) name, input
storage writes the response as a file to storage storage (backend), key ({source}/{ts})

Example with multiple sinks:

notify:
  - type: log
  - type: webhook
    url: "https://hooks.example.com/ingest"
    token_env: "CRON_WEBHOOK_SECRET"
  - type: telegram
    chat_id: 123456789
  - type: vk
    peer_id: 41848219
  - type: messages_db
    scope: "cron:reports"
  - type: agent
    template: summarizer
    session: "pipeline:reports"
    run: true
    prompt_wrap: "Новый отчёт:\n{result}"
  - type: email
    to: "ops@example.com"
    subject: "Cron: reports"
    smtp:
      host: "smtp.example.com"
      username: "agent@example.com"
      password_env: "SMTP_PASSWORD"
      from: "agent@example.com"

Script API

Tool and hook scripts can schedule jobs at runtime (the contract is shared between Rhai and JS). These jobs are dynamic and persist across restarts.

const r = schedule_cron({
    name: "watch-price",
    template: "hello-agent",
    interval_secs: 3600,
    prompt: "Проверь цену и сообщи.",
});
// r = { name: "watch-price", ok: true }

const gone = cancel_cron("watch-price");   // -> bool
const jobs = list_cron();                  // -> массив статусов заданий

schedule_cron returns { name, ok, error? }; cancel_cron(name) — bool; list_cron() — an array of statuses (name, template, schedule, last run). Errors are thrown only when the cron service is not connected (e.g. agent-os hook without a server).

History and monitoring

Every run is written to logs/cron.db (table cron_runs) with a status (success / error / budget_exhausted / rate_limited / timeout), token count, cost estimate, and timings.

Notes