Bot platform adapters now ship in the
praisonai-bot package. praisonai bot serve still works exactly as documented here; for a standalone install see praisonai-bot Migration.Quick Start
1
Reply with two buttons
2
Dropdown selection
3
One-line approval prompt
4
One-line question / choice prompt
How It Works
Block Types
Table and chart blocks default to
supports_tables=False / supports_charts=False on every channel, so channel authors get an automatic markdown-table / text-summary degrade for free — see Table & Chart Blocks.
Channel renderers always run adapt_presentation() first. Buttons over the per-channel cap are dropped lowest-priority first, so high-priority actions like Confirm or Deny survive on Discord and Slack.
Channel Adaptation
adapt_presentation() runs automatically inside every renderer — you can also call it yourself to preview what a channel will receive.
presentation is never mutated — adapt_presentation() always returns a new MessagePresentation.
For adapters that want to surface downgrades to the user, use adapt_presentation_with_report() — it returns the adapted presentation plus a typed DegradedDelivery report of what was downgraded (with machine-readable reason codes).
Channel Limits
max_buttons is per-row capacity; total button cap is max_buttons × max_button_rows.
Telegram has no native select menu (
supports_select=False). Any select block is automatically converted to a column of callback buttons by adapt_presentation().WhatsApp uses split caps:
≤3 non-URL tappable buttons render as native reply buttons (title cap 20). More than 3 buttons — or any select block — promotes to a native list message (row title cap 24, up to 10 rows). URL buttons are never tappable widgets; their links are inlined into the message body as Label: URL lines.Renderer Registry
Every channel plugs into a platform-keyed registry so adapters resolve a renderer uniformly and unknown channels degrade to readable text.render_for(platform, presentation) resolves the registered renderer and returns its native payload; channels with no renderer fall back to fallback_text(presentation).
PresentationRenderer Protocol — two static methods, get_limits() and render(). Register a new channel by adding a class with these methods to _RENDERERS.
fallback_text(presentation) flattens a presentation for channels without a registered renderer: text/context/divider blocks become lines, buttons and select options become • Label bullets, and URL buttons inline as • Label: URL so nothing is silently dropped.
See Presentation Renderers for the full registry API, the built-in renderer table, and an “add a channel” recipe.
Native rendering per channel
Call a renderer directly to preview the exact native payload a channel receives.Capability Degradation
When a channel lacks a capability,adapt_presentation() gracefully degrades to the next best option.
select → buttons on Telegram:
web_app → URL button on Slack/Discord:
On channels with a durable callback payload store attached (built-in default for
TelegramBot), long reply / select values are persisted under a short reference and the callback carries reply:@<ref> / select:<action_id>:@<ref>. The registry resolves the reference back to the exact value on click, so long option values (URLs, file paths, free-text) round-trip losslessly.On channels without a store attached, values that exceed the 64-byte cap fall back to a truncated SHA1 hash (reply:#<digest>) — distinct choices stay distinct but the original value is not recoverable, so the handler is skipped.Approval Prompts
On channels implementingSupportsPresentation, approval prompts render as inline Allow Once, Allow Always, and Deny buttons wired to /approve <approval_id> ... commands — replacing fragile yes/no text classification.
Text-keyword backends (TelegramApproval, SlackApproval, DiscordApproval) remain valid fallbacks for channels without presentation support.
When the underlying bot uses MessagePresentation.approval(...), the button namespace is automatically actor-bound — see Interactive Callback Authorization.
Long callback values
Two paths keep long callback values usable past a channel’s 64-byte inline-callback cap.TelegramBot shares one InMemoryCallbackPayloadStore between its renderer and inbound registry, so long values round-trip by default. See Interactive Callback Payload Store for a dedicated walkthrough.
Best Practices
Use factory methods for blocks
Use factory methods for blocks
PresentationBlock.make_text() and make_buttons() are the agent-friendly path — fewer field mistakes than raw dataclass construction.Set high priority on destructive or critical actions
Set high priority on destructive or critical actions
When a channel forces truncation,
adapt_presentation() drops the lowest-priority buttons first. Give Confirm / Deny / Cancel high priority so they always reach the user.Provide plain-text content as fallback
Provide plain-text content as fallback
Always set
MessagePresentation text content so channels without a registered renderer (e.g. Email) still deliver a readable message. WhatsApp now renders interactive presentations natively.Use MessagePresentation.approval for tool gates
Use MessagePresentation.approval for tool gates
The built-in helper wires standard Allow/Deny buttons consistently across Telegram, Slack, and Discord.
Use MessagePresentation.question for non-binary clarifications
Use MessagePresentation.question for non-binary clarifications
question(...) is the counterpart to approval(...) for “which of these?” prompts — one call renders native buttons on every channel, and the tapped reply value flows back as the next agent turn.Related
Approval Protocol
Tool approval backends
Bot Gateway
Multi-channel gateway server
Bot Platform Capabilities
PresentationLimits governs interactive widgets; PlatformCapabilities governs message length, chunking, and editing.Typed report of controls a channel could not render natively

