Коротко
- Повесьте на задачу automation условие
trigger.script: скрипт сравнивает наблюдение с прошлым и возвращает{ fire, message, state }. Ход модели запускается только приfire: true. - Если событие приходит строками из лога, держите команду открытой stream-расписанием. Если внешний сервис умеет слать webhook, отсейте шум в transform, маленьком JS-модуле, который возвращает
null, когда агента звать не надо. - Скрипт работает без присмотра с правами агента: задайте
--toolsминимумом, а в самом скрипте оставьте только чтение.
Симптом
Агенту поручили «следи за сервисом, скажи, если что-то не так», и он делает это через heartbeat (периодический ход агента) или задачу openclaw automations с --message. Каждый тик — полный ход модели («Heartbeats run full agent turns», пишет документация), и почти всегда он кончается ответом NO_REPLY. Такое решение по силам if в три строки.
Приём: сравнение «было/стало» делает код, модели остаётся решить, что делать с изменением.
Проверено по документации OpenClaw 2026.10.1, октябрь 2026; запуском конфиги не проверялись.
Схема потока
тик расписания (every/cron, не чаще раза в 30 с)
│
▼
trigger.script: exec или MCP, до 5 вызовов, до 30 с ← модели нет
│ сравнивает наблюдение с trigger.state
├── fire: false → state сохранён, ждём тик (истории запусков нет)
└── fire: true → message дописан к --message
│
▼
ход агента (isolated, лёгкая модель)
│
NO_REPLY | отчёт | AUTOMATION_FAILED
│
▼
--announce → вам в чат
Из чего собрано
| Повод | Примитив | Когда работает модель |
|---|---|---|
| Состояние надо опрашивать | trigger.script на every/cron |
только при fire: true |
| Событие пишет долгоживущая команда | --stream-command с --stream-mode match |
на пачку подходящих строк |
| Внешний сервис шлёт HTTP | hooks.mappings с transform |
transform вернул действие, а не null |
| Суждение не нужно | command payload (--command) |
никогда |
Command payload запускает процесс на хосте Gateway без модели: stdout уходит в чат, напечатанный NO_REPLY ничего не отправляет.
Как собрать: наблюдатель за health-эндпоинтом
Скрипт-условие. Вызов exec и json(...) повторяет пример из документации, || true нужен, чтобы недоступный сервер дал код 000, а не ошибку команды:
// watch-health.js
const res = await exec({
command: "curl -s -o /dev/null -w '%{http_code}' --max-time 10 https://status.example.invalid/health || true",
});
const code = String(res?.aggregated ?? "").trim();
if (!/^\d{3}$/.test(code)) throw new Error(`health check failed: ${code.slice(0, 200)}`);
const prev = trigger.state?.code;
json({
fire: prev !== undefined && code !== prev,
message: `health: ${prev} -> ${code}`,
state: { code },
});
Задача:
openclaw automations add \
--name "Health watcher" \
--every 1m \
--trigger-script ./watch-health.js \
--tools exec \
--model "<лёгкая модель>" \
--light-context \
--session isolated \
--message "Статус health-эндпоинта изменился. Ответь в три строки: что это значит и что проверить первым." \
--announce --channel telegram --to "<ваш chat id>"
trigger.state— JSON до 16 КБ, который переживает тики. Сигнал уходит только при смене кода, дублей нет.- Первый тик молчит (
prev === undefined), иначе сразу после создания придёт ложное «изменилось». throwвместо тишины. Наблюдатель, который замолкает при сбое проверки, выглядит здоровым. Исключение становится ошибкой запуска: дальше backoff и оповещение о сбоях, по умолчанию после двух ошибок подряд, если у задачи есть маршрут доставки.messageстановится всем контекстом события: пишите в нём, что изменилось.--toolsдействует и на скрипт, и на ход модели. Без него задача хранит*, то есть всю политику агента.--modelи--light-context: для проверок статуса документация советует лёгкую модель, а--light-contextне подкладывает в ход файлы рабочего пространства.
Если повод — строка в логе, опрос не нужен:
openclaw automations add \
--name "Errors in app log" \
--stream-command '["tail","-F","/var/log/app.log"]' \
--stream-mode match \
--stream-match '(ERROR|FATAL)' \
--session isolated \
--message "Разбери эти ошибки из лога."
Строки, пришедшие за время хода модели или раньше встроенных 30 секунд, склеиваются в одну пачку: на всплеск ошибок придётся один ход.
Если сервис умеет слать webhook, фильтр стоит ещё раньше. Webhooks по умолчанию выключены:
{
hooks: {
enabled: true,
token: "<long-random-hook-token>",
path: "/hooks",
allowedAgentIds: ["main"],
mappings: [
{
match: { path: "ci" },
action: "agent",
agentId: "main",
channel: "telegram",
to: "<ваш chat id>",
transform: { module: "ci-filter.js" },
},
],
},
}
// ~/.openclaw/hooks/transforms/ci-filter.js
export default ({ payload }) =>
payload?.status === "failed"
? { kind: "agent", message: `CI упал в репозитории ${String(payload.repo).slice(0, 100)}. Найди причину.` }
: null;
Без channel и to mapping отправит итог в канал "last", то есть в последний использованный. Применить конфиг: openclaw config validate, затем openclaw gateway restart.
Где человек
Человек получает итог в чат: через --announce у задачи или channel/to у mapping. Ход агента идёт без присмотра, уточнить не у кого, поэтому ответ — готовый отчёт, NO_REPLY, если сообщать нечего, или AUTOMATION_FAILED первой строкой, и тогда запуск записывается как ошибка.
Сколько стоит
Своих замеров нет, считаем по формуле.
N— тиков в сутки: раз в 5 минут — 288, раз в минуту — 1440, предел условия (30 с) — 2880.k— сколько раз в сутки есть повод.T— токенов на ход модели. Ориентир из документации есть только для heartbeat: около 2–5 тысяч в изолированной сессии против примерно 100 тысяч с полной историей.
Модель на каждый тик: N × T в сутки. Гейт: k × T (плюс короткий message). Скрипт токенов не тратит, он платит процессором и вызовами exec. Выигрыш — N / k: опрос раз в 5 минут и 4 повода в сутки дают 288 ходов против 4, в 72 раза меньше. Деньги — токены, умноженные на тариф провайдера. Когда повод есть почти на каждом тике (k близко к N), гейт ничего не экономит.
Как проверить
openclaw automations show <job-id>: задача включена, видны расписание, следующий запуск и маршрут доставки.- Пустой
openclaw automations runs <job-id>в тихие часы — норма:fire: falseистории не создаёт. - Спровоцируйте событие на тестовом эндпоинте и дождитесь записи в
openclaw automations runs <job-id> --limit 20и сообщения в чате. - Сломайте команду (
curlxвместоcurl): в истории должна появиться ошибка, а не тишина. - Webhook проверьте двумя запросами:
curl -i http://127.0.0.1:18789/hooks/ci \
-H 'Authorization: Bearer <long-random-hook-token>' \
-H 'Content-Type: application/json' \
--data '{"status":"passed","repo":"demo"}'
Ожидается 204: transform вернул null, запуска нет. С "status":"failed" — 200, это значит «запуск принят», а не «модель ответила». 401 — неверный токен, 404 — hooks выключены или путь не совпал с mapping.
Как выключить
- Одну задачу:
openclaw automations disable <job-id>илиremove <job-id>. - Все условия, script payloads и stream-источники разом:
cron.triggers.enabled: false. - Webhooks:
hooks.enabled: false.
Где ломается
- Скрипт работает без присмотра. Условия по умолчанию работают с полной политикой инструментов агента, включая
exec, stream держит вашу команду запущенной.--tools execиз примера оставляет шелл, поэтому скрипт должен только читать. - Одобрения exec не доходят до чата. Если у агента включены одобрения exec, запрос из запуска по расписанию уходит только в Control UI или приложения; когда ни одно не подключено, он отклоняется сразу. Поэтому в примере модель только объясняет событие.
- После сбоя state не сохраняется. Если упал ход модели, следующая проверка сработает снова: это встроенный повтор, но возможен дубль уведомления.
- Лимиты условия: не чаще раза в 30 секунд, 30 секунд на проверку, до 5 вызовов, state до 16 КБ. Для реакции за секунды нужен webhook от источника.
- Stream не повторяет упавшие запуски модели и держит в очереди одну пачку. Если сама stream-команда пять раз подряд завершается быстрее чем за 60 секунд (опечатка, нет файла), задача уходит в ошибку, включать её надо вручную.
- Webhook не гарантирует обработку. Ошибка после допуска возвращается как данные запуска, повторного HTTP-запроса не будет.
Idempotency-Keyзащищает от двойного запуска, но кэш повторов живёт в памяти и сбрасывается при рестарте. - Содержимое webhook — недоверенные данные. Токен даёт право войти, а не подтверждает отправителя. Подставляйте в
messageтолько короткие поля и ограничьте инструменты агента.
Когда брать другое
- Повод есть почти на каждом тике: оставьте обычное расписание.
- Каждое событие нужно обработать ровно один раз и пережить рестарт: берите очередь или durable-движок вроде Temporal.
- Чтобы понять, есть ли повод, надо читать текст («это письмо тревожное?»). Скрипт не решит, нужен ход лёгкой модели с
NO_REPLYна тишину.