Skip to main content
Agents can return a MessagePresentation alongside their reply text โ€” Telegram renders it as a native inline keyboard; other platforms fall back to plain text until their renderer is wired.
The user receives a reply with inline buttons; their tap sends the choice back as the next message.

Quick Start

1

Quick-Reply Buttons

The simplest way to add buttons โ€” each tapped choice feeds the value back to the agent as the next user message:
2

One-line question builder

MessagePresentation.question(...) is the shortest way to ask a multi-choice question โ€” prompt, optional context, and option buttons in a single call:
Each option becomes a reply-action button โ€” a tap sends the chosen value back as the userโ€™s next message, so no callback handler is needed.
3

Inline Keyboard with Callbacks

Full control over button appearance and callback data:
4

Use with an Agent and Telegram Bot


How It Works

The adapt_presentation() function runs a channel-agnostic adaptation pass before rendering: it truncates buttons to platform limits, degrades select menus to button rows when unsupported, and degrades web_app actions to URLs on channels without mini-app support.

One-Line Question Builder

MessagePresentation.question(prompt, options, context=None) builds a prompt-plus-options presentation in a single call โ€” the symmetric counterpart to MessagePresentation.approval(...) for non-binary โ€œwhich of these?โ€ clarifications.
It composes a text prompt โ†’ optional context โ†’ a quick_replies button row. Every option carries PresentationAction.reply(value), so a tap feeds the chosen value straight into the next agent turn โ€” no callback handler required.
context= produces a smaller contextual line under the prompt. On channels that donโ€™t render reply natively (e.g. Telegram), the reply degrades to callback:reply:<value>. When the payload would exceed the 64-byte channel cap the framework persists the value under a short reference and emits reply:@<ref>; the registry resolves it back to the exact value on click. Without a store (custom adapters that opt out) it falls back to reply:#<sha1[:16]> โ€” distinct choices stay distinct but the original value cannot be recovered.

Which builder to pick

User interaction flow


Block Types

MessagePresentation.question(prompt, options, context=None) is the one-line shortcut for โ€œprompt + optional context + optionsโ€ โ€” it composes the text, context, and quick_replies blocks above for you.

Action Types


Platform Support & Limits

Telegram Limits

Callback data is capped at 64 UTF-8 bytes (Telegram platform limit). callback_data values that exceed this are truncated at the UTF-8 byte boundary. For non-ASCII payloads this means fewer than 64 characters may be kept. Use short, ASCII-safe identifiers as callback values.

WhatsApp Limits

WhatsApp uses split caps: reply-button titles are capped at 20 chars while list-row titles allow 24. Reply-button message bodies cap at 1024 chars (vs 4096 for text and list bodies). Every reply/list-row id is capped at 256 chars (Cloud API limit).

Button Priorities

When more buttons are defined than the platform limit allows, the adaptation pass keeps the highest-priority buttons and drops the rest:

Best Practices

PresentationAction.reply(value) feeds the chosen value back directly as the userโ€™s next message โ€” your agent sees it as normal input without needing any callback handler.
Reach for question(...) when you want a prompt, optional context, and options in a single call. Compose blocks manually (make_text + quick_replies + โ€ฆ) only when you also need dividers, extra text blocks, or mixed action types.
On Telegram this is now a performance / clarity recommendation, not a correctness one โ€” long values still work, they simply take a store round-trip via the callback payload store. Telegramโ€™s 64-byte callback cap is measured in UTF-8 bytes, so compact IDs (e.g. "approve:42") stay inline and skip the extra hop.
The captured presentation is popped after chat() in both streaming and non-streaming paths, so cancelling or returning never leaves stale buttons visible.
Call adapt_presentation(presentation, PresentationLimits.telegram()) before sending to preview which buttons survive the Telegram limit and how select menus are degraded. Use PresentationLimits.whatsapp(), .slack(), or .discord() to preview the other channels.

Approval Secure Backend

Durable approval buttons with actor authorisation

Bot Presentations

Overview of bot presentation capabilities

Interactive Bot Actions

Handling button taps and callbacks

Channel Capabilities

What each platform can render

Table & Chart Blocks

Portable table and chart blocks with per-channel rendering