11. Cron-задания — агенты по расписанию
🔵 Agent OS умеет запускать агента по расписанию, без пользователя. Cron-задание впрыскивает промпт в сессию шаблона и запускает один ход — ровно как чат-запрос, поэтому бюджеты, хуки, инструменты и событийное хранилище ведут себя одинаково. Итоговый ответ доставляется в один или несколько sink'ов.
- Конфиг-задания лежат в YAML и hot-reload'ятся.
- Динамические задания создаются в рантайме из скриптов (
schedule_cron) и персистятся вlogs/cron.db, переживая рестарт.
Где задаются
1. На агента — список cron: в config/agents/<agent>/agent.yaml
(template по умолчанию = сам агент):
# config/agents/rss/agent.yaml
cron:
- name: morning-digest
schedule: "0 0 9 * * *"
prompt: "Резюмируй сегодняшние заголовки."
notify: [{ type: log }]
2. Глобально — config/cron.yaml (каждое задание называет template):
# config/cron.yaml
jobs:
- name: morning-digest
template: rss
schedule: "0 0 9 * * *"
prompt: "Резюмируй сегодняшние заголовки."
При коллизии имён config/cron.yaml выигрывает у agent.yaml. Оба источника
hot-reload'ятся; динамические (скриптовые) задания независимы от обоих.
Справочник задания
| Поле | Тип | Смысл |
|---|---|---|
name |
string (обязат.) | уникальный id задания |
template |
string | шаблон для запуска (в agent.yaml — неявный) |
schedule |
string | cron-выражение: sec min hour dom month dow (UTC) |
timezone |
string | IANA-таймзона (напр. Europe/Moscow), в которой интерпретируется schedule; по умолчанию UTC |
interval_secs |
int | фиксированный интервал (альтернатива schedule) |
oneshot_at |
string | RFC 3339 время — сработать разово, затем уйти |
prompt |
string (обязат.) | сообщение пользователя, впрыскиваемое в сессию |
persistent |
bool | true переиспользует сессию cron:<name> (хранит контекст); по умолчанию false |
timeout_secs |
int | лимит одного запуска |
overlap |
string | skip (по умолч.) | queue | parallel |
retry |
map | { max, backoff_secs } (зарезервировано) |
enabled |
bool | по умолчанию true |
notify |
list | куда доставляется результат (sink'и ниже) |
Ровно одно из schedule / interval_secs / oneshot_at должно быть задано.
Sink'и
У задания может быть любая комбинация sink'ов. Без notify результат доступен
только через историю запусков (GET /v1/cron, cron.db).
type |
Что делает | Поля |
|---|---|---|
log |
пишет ответ в лог сервера | — |
webhook |
POST результата (JSON) на URL | url, token_env (опциональный bearer из env) |
telegram |
отправляет ответ в чат Telegram | chat_id, token_env (опц.; по умолчанию — бот шаблона) |
messages_db |
персистит ответ в message store | scope (по умолчанию cron:run:<name>) |
agent |
подаёт ответ другому агенту как сообщение | template, session (опц.), run (по умолч. true), prompt_wrap (опц., {result}) |
email |
шлёт ответ по SMTP | to, subject (опц.; по умолчанию имя задания), smtp |
workflow |
запускает воркфлоу (игнорирует запуск агента) | name, input |
Пример с несколькими sink'ами:
notify:
- type: log
- type: webhook
url: "https://hooks.example.com/ingest"
token_env: "CRON_WEBHOOK_SECRET"
- type: telegram
chat_id: 123456789
- 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"
Скриптовый API
Скрипты инструментов и хуков могут планировать задания в рантайме (контракт
общий для Rhai и JS). Эти задания — dynamic, персистятся через рестарты.
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 возвращает { name, ok, error? }; cancel_cron(name) — bool;
list_cron() — массив статусов (name, template, schedule, last run). Ошибки
бросают только когда cron-сервис не подключён (например hook_tool без сервера).
История и мониторинг
Каждый запуск пишется в logs/cron.db (таблица cron_runs) со статусом
(success / error / budget_exhausted / rate_limited / timeout),
числом токенов, оценкой стоимости и таймингами.
GET /v1/cron(админ) — список заданий с расписанием и сводкой последнего запуска.POST /v1/cron/:name/run(админ) — ручной запуск задания «прямо сейчас», не дожидаясь расписания (проходит тот же путь: overlap, запись запуска, sink'и).- Сырая история — SQL-запросы к
logs/cron.db→cron_runs.
Примечания
- Бюджеты: задание по расписанию тянет из бюджетной группы шаблона как
обычная сессия. Частое задание на общей группе может «голодать» живых
пользователей — настрой
budget/budget_group. - Время — UTC по умолчанию; задать локальное время можно полем
timezone(IANA-имя, напр.Europe/Moscow).interval_secsиoneshot_atот таймзоны не зависят. - Нет догона: пропущенные тики (сервер лежал, overlap) пропускаются, не переигрываются.
- Sink'и best-effort: упавший sink пишет предупреждение и не валит запуск.
agent-sink'и компонуются: цепочкиA → B → Cработают, потому что каждый хоп — обычный ход сессии.