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) andasync_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:
- SBP / payment: create a payment → show a QR → webhook
payment.succeeded; - OAuth (a special case, already solved pointwise via
oauth_connect); - confirmation by code (SMS / e-mail / push);
- "confirm in the mobile app/bank";
- any "request → user does something outside the dialog → callback".
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)
source: webhook— theevent_idargument (required) sets the id we are waiting for; on callback/v1/events/callbackthe core matches it viaevent.id_pointer/event.id_queryand returns{"event_id":"...","payload":{...}}.source: cron— thedelay_secsargument: simply suspend the turn for N seconds.
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:
- accepts a JSON body (and/or query parameters);
- the core scans pending
await_event/async_actionand resolves the one whose correlation id matched (byid_pointer/id_query); - auth — bearer token from
events.token_env(config/server.yaml), empty = open (dev); - no match — idempotent
{"status":"noop"}.
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)
- Rendering a QR as an image (currently a link/button).
- Delivery guarantees/retries for callbacks (responsibility of the external system).
- Queues/priorities for pending turns.
- Persistence of pending across restarts — only for durable agents (written to
pending.dband re-raised on boot); for ephemeral ones, pending is in memory. - Callback signature verification (currently only the bearer token
events.token_env). - Sources
message/ipc/processas separateevent.source.
Links
- Tool reference:
05-tools-compute.md§5.28.3 (await_event), §5.28.4 (async_action), §5.28.5 (show_link). - Demo agent:
config/agents/await-demo/(wait_payment.yaml,sleep.yaml,sbp_payment.yaml,open_link.yaml).