Подписки на события

🔵 Обзор того, как события попадают в платформу и запускают агентов. Механизмы делятся на pull (платформа сама опрашивает), push (внешняя система присылает) и внутренние (события процессов ядра).

Механизм Тип Что запускает Где настраивается
Cron pull (таймер) ход агента по расписанию/интервалу cron: в agent.yaml / config/cron.yaml
Мониторы pull (источник) ход агента на новые элементы источника config/monitors.yaml
Входящий webhook push один ход агента по POST /v1/inbound/:template server.yaml → inbound
Webhook воркфлоу push запуск воркфлоу по POST /v1/workflows/<name>/webhook triggers.webhook
Реакции внутренние события действие на событие процесса (on:) config/reactions.yaml
await_event ожидание ход агента ждёт событие (webhook или таймер) тул kind: await_event

Push vs pull. Push удобен, когда внешняя система сама знает о событии (платежи, CRM, CI). Pull — когда надо опрашивать источник; при этом монитор стреляет только на изменения, а cron — всегда по расписанию.

Внутренние события. Реакции подписываются не на внешние системы, а на события процессов ядра (ProcessEvent): старт/завершение, вызовы инструментов, содержимое ответа, бюджет и т.д. — и запускают агента, инструмент, webhook или signal (steering живого процесса).

Ожидание события внутри хода. await_event приостанавливает ход и ждёт внешний колбэк POST /v1/events/callback (по event_id) или таймер; async_action делает полный асинхронный флоу (запрос → показ URL → колбэк → результат).

Доставка результата. Итог хода/воркфлоу раздаётся в sink'и (notify): log, webhook, telegram, vk, yandex_messenger, email, messages_db, agent, workflow, storage — см. Cron.

Разница между push-точками входа (inbound webhook, каналы Telegram/email) — в Виджет и каналы.

Наружу: журнал действий агента (agent_events)

Обратное направление — внешняя система узнаёт о действиях агента. Нативный хук kind: agent_events держит события в in-memory кольцевом буфере (без диска) на выбранных точках жизненного цикла, а маршрут GET /v1/events под ACCESS_ADMIN отдаёт журнал наружу с long-poll и cursor drain (пачкой, без потери событий). Реализация — модуль agent_os_events; поля конфига — в Расширение хуков.

Хук агента (hook_files: [hooks/events.yaml]):

kind: agent_events
name: agent_events
agent: acme-agent
on: [after_tool_result, after_turn]   # по умолчанию только after_tool_result
ignore: [settings_get]                # служебные чтения не логируем
mutating: [order_create, order_cancel]
capacity: 5000                        # кольцевой буфер (в памяти)

Чтение:

GET /v1/events?agent=acme-agent&mode=wait&since=N

Контракт ответа: { ok, mode, agent, name, seq, since, count, events[], last, has_more, dropped_window }. Событие: seq, ts, agent, session, user_id, tool, kind, mutating, success, args_fp, detail (args_fp — некриптографический отпечаток аргументов; сами аргументы/результат не сохраняются).

?mode= Поведение
drain (по умолчанию) пачка событий с seq > since, сразу
wait long-poll: ждёт событие (до wait_ms), затем отдаёт пачку
events последние N событий
version только текущий seq (дешёвый счётчик)

Клиент ведёт курсор — так пачка не теряется:

let since = 0;
for (;;) {
  const r = await fetch(`/v1/events?agent=acme-agent&mode=wait&since=${since}&limit=100`,
    { headers: { Authorization: `Bearer ${adminToken}` } });
  const b = await r.json();
  for (const ev of b.events) handle(ev);            // события по возрастанию seq
  if (b.events.length) since = b.events.at(-1).seq; // сдвигаем курсор
  if (b.has_more) continue;                          // накопилась пачка — дочитываем
  if (b.dropped_window) resync();                    // часть вытеснена буфером
}

Безопасность. Маршрут GET /v1/events — только ACCESS_ADMIN. Payload минимальный (без аргументов/результатов, только args_fp); since клампится в [0, max], limit/wait_ms зажаты сверху; capacity — кольцевой буфер в памяти.