Asynchronous actions — await_event / async_action

Status: implemented. Two declarative kinds for the flow of the form "request → show to user → wait for an external callback → return the result": await_event (just wait) and async_action (full flow).

Problem

Agent OS tools are synchronous: call → result. But there is a class of flows where the result arrives not immediately, but after an external action invisible to the agent:

Previously there was no generalized primitive for this:

Mechanism What it can do What was missing
rest_tool / site_tool outgoing request (GET/POST/…, oauth2, sign) a single request, does not wait
oauth_connect suspend → resume via callback hardcoded for OAuth URLs
request_attachment suspend, waits for a file/photo from the user specialized for attachments
inbound webhook (POST /v1/inbound/:template) starts a new turn via webhook does not resolve an already pending turn; waits for {message}

What is implemented

One native suspend/resume mechanism (Kernel::request_await_event + resolve_event) and one callback route POST /v1/events/callback. On top of it — two YAML tools:

kind What it does When to use
await_event suspend the turn and wait for an event (webhook or cron) the id is already known (the agent made the request itself)
async_action full flow: start request → extract id/url → wait for callback create a resource and wait for its confirmation
show_link show a link as a button (window on click), optionally wait for "Done" let the user open an external 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

Three pointers:

Pointer From Why
resolve.id_pointer response start correlation id we are waiting for
prompt.url_pointer response start URL to show to the user
callback.id_pointer / callback.id_query body/query of the callback match the callback to the turn

start is executed by the site_tool engine (template rendering, oauth2/sign, retries); id/url are extracted with a JSON pointer; then — the same suspend/resume.

Callback route POST /v1/events/callback

Shared, not tied to an integration:

Showing to the user (URL)

The input_request event received a url field. Channels render:

Channel Now (v1) Later (v2)
Widget (web) "Open" button via url client-side JS QR from the URL
Telegram / VK / Yandex message + link server-side PNG (QR) + photo
Android link button Kotlin QR

await_event/async_action contain nothing QR-specific — they return url, and the channel draws the QR.

Non-goals (not done)

Links