🤖 AI-агенты для бизнеса

24. Асинхронные действия — await_event / async_action

Статус: реализовано. Два декларативных kind для флоу вида «запрос → показать юзеру → дождаться внешнего коллбэка → вернуть результат»: await_event (просто ждать) и async_action (полный флоу).

Проблема

Инструменты Agent OS синхронны: вызов → результат. Но есть класс флоу, где результат приходит не сразу, а после внешнего действия, невидимого для агента:

Раньше обобщённого примитива для этого не было:

Механизм Что умеет Чего не хватало
rest_tool / site_tool исходящий запрос (GET/POST/…, oauth2, sign) один запрос, не ждёт
oauth_connect suspend → resume по коллбэку захардкожен под OAuth-URL
request_attachment suspend, ждёт файл/фото от юзера специализирован на вложения
inbound webhook (POST /v1/inbound/:template) запускает новый ход по вебхуку не резолвит уже висящий ход; ждёт {message}

Что реализовано

Один нативный механизм suspend/resume (Kernel::request_await_event + resolve_event) и один коллбэк-роут POST /v1/events/callback. Поверх него — два YAML-инструмента:

kind Что делает Когда использовать
await_event приостановить ход и ждать событие (webhook или cron) id уже известен (агент сам сделал запрос)
async_action полный флоу: start-запрос → извлечь id/url → ждать коллбэк создать ресурс и дождаться его подтверждения
show_link показать ссылку кнопкой (окно по нажатию), опционально ждать «Готово» дать пользователю открыть внешнюю ссылку

await_event

kind: await_event
name: wait_payment
description: Дождаться подтверждения оплаты по correlation id.
timeout_secs: 600
event:
  source: webhook         # webhook (default) | cron
  id_pointer: /object/id  # где id в теле коллбэка (default /id)
  # id_query: payment_id  # либо query-параметр (взаимоисключает id_pointer)

async_action

kind: async_action
name: sbp_payment
description: Принять оплату по СБП и дождаться подтверждения.
parameters:
  type: object
  properties: { amount: { type: string } }
  required: [amount]
start:                 # поля как у rest_tool (method/url/json/headers/oauth2/sign)
  method: POST
  url: "https://api.yookassa.ru/v3/payments"
  json: { amount: { value: "{{ amount }}", currency: "RUB" }, capture: true }
  headers:
    Authorization: "Basic {{ (env_var('YOOKASSA_SHOP_ID') ~ ':' ~ env_var('YOOKASSA_SECRET_KEY')) | base64 }}"
prompt:
  message: "Оплатите по QR:"
  url_pointer: /confirmation/confirmation_url
resolve:
  id_pointer: /id
callback:
  id_pointer: /object/id
timeout_secs: 600

Три указателя:

Указатель Откуда Зачем
resolve.id_pointer ответ start correlation id, которого ждём
prompt.url_pointer ответ start URL для показа юзеру
callback.id_pointer / callback.id_query тело/query коллбэка сопоставить коллбэк с ходом

start выполняется движком site_tool (рендер шаблонов, oauth2/sign, retries); id/url извлекаются JSON-pointer'ом; дальше — тот же suspend/resume.

Коллбэк-роут POST /v1/events/callback

Общий, не привязан к интеграции:

Показ юзеру (URL)

Событие input_request получило поле url. Каналы рендерят:

Канал Сейчас (v1) Дальше (v2)
Виджет (web) кнопка «Открыть» по url клиентский JS-QR из URL
Telegram / VK / Яндекс сообщение + ссылка серверный PNG (QR) + фото
Android кнопка-ссылка Kotlin-QR

await_event/async_action ничего QR-специфичного не содержат — они отдают url, а QR рисует канал.

Не-цели (не сделано)

Ссылки