Skip to main content
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.
Platform capabilities tell PraisonAI what your bot’s platform can do, so streaming, chunking, and rate limiting work the same way everywhere.
Capabilities describe what a channel can render; Display Policy controls what you want shown.
The user receives a reply; platform capabilities tell PraisonAI how to chunk, stream, and rate-limit on that channel.

Quick Start

1

Look up built-in capabilities

2

Register a custom platform

How it works

UnifiedDelivery (via create_delivery(bot)) reads platform_capabilities to chunk long replies, stream edits, and apply rate limits.

Configuration options

Methods: to_dict() and from_dict(data).

Delivery reconciliation (reconciles_unknown_send)

After a gateway restart, can the adapter confirm that a client-side-keyed send actually landed — so a recovered outbox entry is marked sent instead of re-dispatched? That question is answered per adapter by reconciles_unknown_send. It is not a silent global default: at-least-once vs effectively-once is a per-channel fact. An adapter opts in only when it both declares reconciles_unknown_send=True and exposes an async was_delivered(idempotency_key) -> bool. The durable outbox auto-wires the reconciler from these two signals (DurableDelivery._build_reconciler); when either is absent, recovered entries fall back to at-least-once re-send.
No built-in adapter declares reconciles_unknown_send=True in the current SDK — every channel is at-least-once out of the box. The flag and its was_delivered hook are the extension point a custom adapter implements to reach effectively-once. See Effectively-Once Delivery for a worked was_delivered implementation.

Labelling the at-least-once fallback

For channels that fall back to at-least-once re-send, mark_recovered=True prefixes each crash-recovered copy with a visible marker so the recipient knows it may be a duplicate. The recipient sees this prefix on a labelled re-send:
♻️ Recovered reply — the gateway restarted during delivery, so this may be a duplicate.
Enable it once at setup:
What gets labelled
  • Only the unreconciled recovered branch — fresh sends and reconciled sends never carry the prefix.
  • Only string content payloads — media and structured payloads pass through unchanged.
  • Idempotent — a re-drained copy that already carries the prefix is not double-prefixed.
  • Sticky — a transient failure of a labelled re-send keeps the entry in recovered so the next retry is still labelled.
  • Fail-open — if labelling errors, the original unlabelled payload is sent (logged at WARNING); the send never fails because of it.
When to enable
  • Adapters where reconciles_unknown_send=False in the table above.
  • Product surfaces where honest “this may be a duplicate” is preferable to a silent duplicate.
Default mark_recovered defaults to False — behaviour is unchanged unless you explicitly set it. See Durable Delivery for the full outbox reference.

How markdown_dialect is consumed

Every adapter that inherits BasePlatformAdapter.format_message() gets platform-correct rendering just by declaring markdown_dialect — the base class calls format_for_dialect(text, caps.markdown_dialect) for you. format_for_dialect(text, dialect) returns a (rendered_text, parse_mode) pair. The parse_mode is the value a transport expects (Telegram needs "MarkdownV2"); it is None when the text is already in the platform’s native form. All four helpers are importable from praisonaiagents.bots:
An agent replies in ordinary markdown; declaring telegram_markdown_v2 makes special characters escape automatically instead of being dropped:
On Telegram, unescaped specials trigger an HTTP 400 can't parse entities response and the reply is dropped. markdown_dialect="telegram_markdown_v2" escapes every reserved character (escape_markdown_v2 is conservative — it shows existing markup literally rather than reinterpreting it), so the message is always accepted verbatim.

Webhook-based platforms

Platforms that set accepts_webhooks=True must also expose a webhook_verifier so enforce_webhook_verification can enforce signatures fail-closed. See Webhook Verification.

Built-in platform defaults

Native / command menu

Some platforms publish the bot’s commands to their native / menu so typing / shows autocomplete. Adapters override publish_command_menu to project the shared CommandRegistry; the base implementation is a no-op. See Native / Autocomplete for user-facing behaviour and the Discord shim caveat.

Common patterns

Subclass with default_capabilities() (Telegram and Discord use this):
Entry-point registrations (via praisonai.channels) get default capabilities unless the adapter class exposes a default_capabilities() classmethod. This keeps zero-config connectors functional while letting polished adapters declare exact limits:
Serialise for config files:

Best Practices

Telegram counts UTF-16 code units. Wrong length_unit can silently truncate messages.
Discord.py handles limits internally; raw Telegram HTTP does not.
UnifiedDelivery streams via edits when this flag is true.
Keeps registry caching consistent when platforms override defaults.

Durable Outbound Delivery

Effectively-once delivery via crash reconciliation

Display Policy

Operator policy for streaming and footers

Bot Platform Adapter

Build a channel — the format_message / markdown_dialect seam

Bot Platform Plugins

Register custom adapters

Bot Streaming Replies

Uses supports_edit and edit_interval_ms

Bot Rate Limiting

Uses needs_rate_limit

Chunking Strategies

Uses max_message_length and length_unit