24. Асинхронные действия — await_event / async_action
Статус: реализовано. Два декларативных kind для флоу вида «запрос → показать юзеру → дождаться внешнего коллбэка → вернуть результат»:
await_event(просто ждать) иasync_action(полный флоу).
Проблема
Инструменты Agent OS синхронны: вызов → результат. Но есть класс флоу, где результат приходит не сразу, а после внешнего действия, невидимого для агента:
- СБП / оплата: создать платёж → показать QR → вебхук
payment.succeeded; - OAuth (частный случай, уже решён точечно через
oauth_connect); - подтверждение по коду (SMS / e-mail / push);
- «подтвердить в мобильном приложении/банке»;
- любой «запрос → пользователь что-то делает вне диалога → коллбэк».
Раньше обобщённого примитива для этого не было:
| Механизм | Что умеет | Чего не хватало |
|---|---|---|
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)
source: webhook— аргументevent_id(обязательный) задаёт id, которого ждём; по коллбэку/v1/events/callbackядро сопоставляет его черезevent.id_pointer/event.id_queryи возвращает{"event_id":"...","payload":{...}}.source: cron— аргументdelay_secs: просто приостановить ход на N секунд.
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
Общий, не привязан к интеграции:
- принимает JSON-тело (и/или query-параметры);
- ядро сканирует висящие
await_event/async_actionи резолвит тот, чей correlation id совпал (поid_pointer/id_query); - auth — bearer-токен из
events.token_env(config/server.yaml), пусто = открыт (dev); - нет совпадения — idempotent
{"status":"noop"}.
Показ юзеру (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 рисует канал.
Не-цели (не сделано)
- Отрисовка QR картинкой (сейчас ссылка/кнопка).
- Гарантии доставки/ретраи коллбэков (ответственность внешней системы).
- Очереди/приоритеты ожидающих ходов.
- Персистентность pending между рестартами процесса (pending в памяти).
- Проверка подписи коллбэка (пока только bearer-токен
events.token_env). - Источники
message/ipc/processкак отдельныеevent.source.
Ссылки
- Справочник инструментов:
05-tools-compute.md§5.28.3 (await_event), §5.28.4 (async_action), §5.28.5 (show_link). - Демо-агент:
config/agents/await-demo/(wait_payment.yaml,sleep.yaml,sbp_payment.yaml,open_link.yaml).