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.
Channel bots can post a quick placeholder message and progressively edit it in place as the agent’s answer streams in — replacing the old “stare at a typing indicator for 45s, then a wall of text lands in one burst” experience.
from praisonaiagents import Agent

agent = Agent(name="assistant", instructions="You are a helpful assistant.")
agent.start("Explain quantum computing in simple terms.")
The user waits on Telegram or Discord; a placeholder message updates in place as the agent streams its answer.
Session-level streaming: true is a shortcut. For unified per-platform defaults and overrides, use Display Policy.
Progressive streaming on Telegram/Discord/Slack required a bug fix in PR #2004 — earlier versions crashed on the first message with TypeError: achat() got an unexpected keyword argument 'stream_callback'. Upgrade to the latest release if you hit that error.

Quick Start

1

Enable with one flag (CLI)

praisonai bot telegram --token $TELEGRAM_BOT_TOKEN --stream
2

Enable in YAML

channels:
  telegram:
    token: ${TELEGRAM_BOT_TOKEN}
    streaming: true
    stream_edit_interval_ms: 700
3

Enable in Python

from praisonaiagents import Agent
from praisonai.bots import TelegramBot
from praisonaiagents import BotConfig

agent = Agent(name="assistant", instructions="Be helpful and concise.")

bot = TelegramBot(
    token="...",
    agent=agent,
    config=BotConfig(
        token="...",
        streaming=True,
        stream_edit_interval_ms=700,
    ),
)
bot.start()

How It Works

Streaming events flow through agent.stream_emitter, not as a stream_callback kwarg to astart(). The bot adds a temporary callback for the duration of the run and removes it on completion (and on timeout/cancel).
ComponentPurposeResponsibility
DraftStreamerManages live updatesCoalesces edits, respects rate limits
BotAdapterPlatform interfaceSends/edits messages
StreamingConfigUser preferencesTiming, text, mode settings

Configuration Options

OptionTypeDefaultDescription
streamingboolFalseEnable progressive streaming — bot sends a placeholder then edits it live as the agent produces tokens
stream_edit_interval_msint700Minimum milliseconds between message edits (respects platform rate limits)

Per-channel streaming support

ChannelLive editsDefault edit intervalText limit
TelegramYesplatform default4096
SlackYes1.0s40000
DiscordYesplatform default2000
WhatsAppNo (single message)4096
EmailNo (single message)unlimited
See Channel Capabilities for the full matrix including reactions and typing.

Advanced configuration (StreamingConfig)

For progress mode, custom placeholder text, or fine-grained min_delta control, use the lower-level StreamingConfig API.

Streaming Modes

Three modes are available to match different use cases:
ModeBehaviorBest For
offSingle final message after completionDefault behavior, zero impact
draftProgressive edits with growing answerLong responses, platforms like Telegram
progressTool status updates, then final answerTool-heavy agents, user wants progress
Live streaming (draft mode) works on Telegram, Slack, and Discord — each adapter honours its own edit_rate_limit and text_limit from Channel Capabilities. WhatsApp and Email fall back to a single final message (live_edit=False). The simple streaming: true / --stream form enables draft mode automatically.

StreamingConfig options

OptionTypeDefaultDescription
modeStreamingModeStreamingMode.OFFStreaming mode: OFF, DRAFT, or PROGRESS
min_intervalfloat1.5Minimum seconds between message edits (rate-limit safe)
min_deltaint120Minimum new characters in the buffer before an edit fires
placeholder_textstr"🤔 Thinking..."Initial message text sent before any content arrives
progress_prefixstr"🤔 "Prefix used in progress mode tool-status updates
progress_stylestr"line"Progress-mode rendering style: "line" (single overwritten tool label, unchanged default) or "feed" (bounded multi-line rolling status feed with per-step ⏳/✓/✗ glyphs)
progress_max_linesint8Max trailing lines shown when progress_style="feed"; older lines scroll off the top of the window
progress_max_line_charsint120Per-line character cap in feed style (includes the leading glyph); words are truncated at a boundary with an ellipsis
disable_progressive_edits_afterint3Consecutive recoverable edit failures before progressive editing is turned off for the rest of the stream — the completed answer is then delivered as a single final send
flood_backoff_factorfloat2.0Multiplier applied to the edit interval on each recoverable (flood / 429 / transient) failure
max_intervalfloat30.0Cap in seconds for the adaptively widened edit interval — backoff stops growing past this
strip_reasoning_tagsboolTrueStrip <think>…</think> and <reasoning>…</reasoning> spans from streamed and final output. Default ON — set to false to opt out

Progress feed style

Set progress_style: feed to render tool calls as a bounded rolling multi-line status view instead of a single overwritten label. A research agent on Telegram chains web_search, fetch_url, and summarize. With the default progress_style="line", each tool overwrites the last, so a failed fetch_url vanishes. With feed, every step keeps its own line and outcome glyph:
from praisonaiagents import Agent
from praisonai.bots import TelegramBot, StreamingConfig, StreamingMode
from praisonaiagents import BotConfig

agent = Agent(
    name="research-assistant",
    instructions="Research the user's question with web tools and summarise.",
    tools=[web_search, fetch_url, summarize],
)

bot = TelegramBot(
    token="...",
    agent=agent,
    config=BotConfig(token="...", streaming=True),
)
bot.configure_streaming(StreamingConfig(
    mode=StreamingMode.PROGRESS,
    progress_style="feed",     # enable the multi-line rolling feed
    progress_max_lines=6,      # keep it short on mobile
    progress_max_line_chars=100,
))
bot.start()
The user’s Telegram message updates in place with a live per-step audit trail:
✓ web_search — 5 results
✗ fetch_url — timeout
⏳ summarize
Each tool gets its own line with a state glyph — running, done, error — instead of one label that overwrites itself.

When to use which style

How one tool flows through the feed

A tool’s start event and its matching finish event share one correlation id, so the line updates in place instead of duplicating.
The feed style activates only when mode: progress and progress_style: feed. The default progress_style="line" preserves the existing single-line behaviour bit-for-bit — feed is strictly opt-in per channel.

Flood-control

Your Telegram bot is streaming a long answer during peak hours. Telegram starts returning 429 after the third edit. The streamer doubles its edit interval (1.5s → 3s → 6s → capped at max_interval). After disable_progressive_edits_after consecutive failures it stops editing entirely and waits — when finalize() fires it delivers the completed answer as a fresh message. The user never sees a stuck placeholder, never sees a partial reply, and the rest of your bot’s chats are unaffected because the backoff is per-stream.
Backoff is per-stream — _current_min_interval is mutated on the stream instance, not the shared StreamingConfig. A flood in one chat will never slow streaming in another.
EventWhat the streamer does
Recoverable edit failure (flood / 429 / transient)fail_streak += 1; current_min_interval *= flood_backoff_factor (capped at max_interval). Per-stream — does not mutate the shared StreamingConfig
fail_streak >= disable_progressive_edits_afterProgressive editing turns OFF for the rest of this stream. Subsequent token events skip _perform_update. The placeholder is left in place until finalize()
Non-recoverable edit failureLogged at WARNING. Fail streak does NOT increment; progressive stays enabled
Successful editfail_streak resets to 0 (recovery)
finalize(final_content)Strip reasoning tags (if enabled), try edit-in-place, fall back to send_message if edit fails

Reasoning-tag filtering

Default ON. <think> and <reasoning> spans (case-insensitive, multi-line) are stripped from both streamed and final output. A trailing unclosed opening tag is also dropped so internal reasoning never leaks mid-stream while a block is still being produced. Set strip_reasoning_tags: false to opt out (for example, on a reasoning-transparency bot).
from praisonai.bots._streaming import strip_reasoning_tags

strip_reasoning_tags("Hi <think>secret</think> there")        # -> "Hi  there"
strip_reasoning_tags("a <REASONING>x\ny</REASONING> b")       # -> "a  b"
strip_reasoning_tags("Answer <think>still going")             # -> "Answer "
Used to see <think> or <reasoning> spans land in your Telegram/Slack/Discord chat? Fixed in PR #2356. Reasoning-tag filtering is now on by default. Upgrade praisonai.

YAML vs Python vs CLI

YAML Configuration

Add to your bot.yaml under the channel configuration:
channels:
  telegram:
    token: ${TELEGRAM_BOT_TOKEN}
    streaming:
      mode: progress
      progress_style: feed          # "line" (default) or "feed"
      progress_max_lines: 8
      progress_max_line_chars: 120
      min_interval: 1.5
      min_delta: 120
      placeholder_text: "🤔 Thinking..."
      progress_prefix: "🤔 "
      # New in #2356
      disable_progressive_edits_after: 3
      flood_backoff_factor: 2.0
      max_interval: 30.0
      strip_reasoning_tags: true

Python API

Configure streaming programmatically with the configure_streaming() method:
from praisonai.bots import TelegramBot, StreamingConfig, StreamingMode

bot = TelegramBot(token="...", agent=agent)

# Enable with defaults
bot.configure_streaming(StreamingConfig(mode=StreamingMode.DRAFT))

# Custom configuration
bot.configure_streaming(StreamingConfig(
    mode=StreamingMode.PROGRESS,
    min_interval=2.0,
    min_delta=200,
    placeholder_text="Working...",
    progress_prefix=""
))

# Multi-line feed style (per-step ⏳/✓/✗ glyphs)
bot.configure_streaming(StreamingConfig(
    mode=StreamingMode.PROGRESS,
    progress_style="feed",
    progress_max_lines=6,
    progress_max_line_chars=100,
))

Manual Streamer Usage

For advanced use cases, you can use DraftStreamer directly:
from praisonai.bots import DraftStreamer, StreamingConfig, StreamingMode

streamer = DraftStreamer(
    adapter=bot_adapter,              # implements BotAdapter Protocol
    channel_id="123456",
    config=StreamingConfig(mode=StreamingMode.DRAFT, min_interval=1.5),
    rate_limiter=None,                # optional, defaults from platform
    platform="telegram",              # enables platform-specific recoverable-error classification
)

message_id = await streamer.start()                      # sends placeholder
# ... agent run subscribes streamer.on_event through agent.stream_emitter.add_callback(...) ...
await streamer.finalize(final_response_text)             # final edit with full text

Best Practices

The default 700ms interval works well on Telegram. If you hit “message is not modified” or 429 errors on a shared/busy channel, raise it to 10002000. On Discord (stricter limits), prefer 2000 or use the advanced StreamingConfig form.
Each platform has different edit rate limits. Telegram allows ~1 edit/sec/chat, while Slack’s chat_update API is more generous. Set min_interval to match your platform’s limits to avoid 429 errors.
# Telegram (liberal editing)
streaming:
  mode: draft
  min_interval: 1.0

# Discord (stricter limits)
streaming:
  mode: draft
  min_interval: 2.0
When your agent frequently calls tools (web search, calculations, etc.), progress mode keeps users informed about what’s happening instead of showing a static “thinking” message.
# Good for agents that use many tools
bot.configure_streaming(StreamingConfig(
    mode=StreamingMode.PROGRESS,
    progress_prefix="🔧 "
))
progress_style: feed turns a black-box “still thinking…” indicator into a live per-step audit trail. Every tool call keeps its own line with a // glyph, so users see which step failed instead of watching one label overwrite itself. See the line-vs-feed comparison.
bot.configure_streaming(StreamingConfig(
    mode=StreamingMode.PROGRESS,
    progress_style="feed",
))
Telegram mobile crops long messages. Set progress_max_lines to 46 so the feed stays readable — older lines scroll off the top of the window while the newest steps stay visible.
streaming:
  mode: progress
  progress_style: feed
  progress_max_lines: 6
The feature has zero impact on existing bots. Only channels with explicit streaming: configuration will use the new behavior. All other channels continue with the original single-message approach.
When finalize() runs, the streamer first tries to edit the placeholder with the full answer. If that edit fails (or progressive editing was already disabled by a flood-control strike), the streamer falls back to a fresh send_message so you always receive the completed answer. No stale ”🤔 Thinking…” placeholder is left behind.

Troubleshooting

Fixed in PR #2356. Reasoning-tag filtering is now on by default. Upgrade praisonai to get the fix automatically.
Fixed in PR #2356. The streamer now backs off on 429/flood, and after 3 consecutive failures falls back to a single final send so you always get the completed answer. Tune disable_progressive_edits_after / flood_backoff_factor / max_interval per channel if your platform’s rate limits differ.
The schema validator only accepts "line" or "feed". Any other value raises Invalid progress_style '<v>'. Must be one of: feed, line at load time. Check for typos like feeds or multiline.
Impossible by design. Once a line reaches error () the compositor never downgrades it to done (), and a late done event never overwrites an error. If you ever see this, please file a bug against praisonai.

Channel Capabilities

What each platform supports — live edits, reactions, typing

Bot Status Reactions

Show run progress as emoji reactions

Streaming Tool Events

Understand tool-event details used in progress mode

Progress Compositor

Fold typed StreamEvents into your own multi-line status view

Bot Gateway

Set up and run channel bots with gateway configuration

Messaging Bots

Complete guide to messaging bot setup and features

Bot Platform Capabilities

How platform capabilities drive this feature