Skip to content

Gateway channel bots cannot stream responses β€” progressive message-edit delivery is missing despite core stream events and adapter edit_message supportΒ #1912

@MervinPraison

Description

@MervinPraison

Summary

Every channel bot (Telegram, Discord, Slack, WhatsApp, Email, AgentMail) delivers agent responses as a single blocking message: the user stares at a typing indicator for the full duration of the LLM turn, then receives the whole answer at once. The core SDK already emits token-level stream events, and every bot adapter already implements edit_message() β€” but nothing bridges the two. The only consumer of stream events today is the WebSocket gateway relay for browser clients. For a gateway/bot product that aims to be best-in-class on chat platforms, live progressive responses are the single most visible quality feature missing.

Current behaviour

  • Core emits stream events per agent: praisonaiagents/streaming/events.py β€” StreamEventEmitter with DELTA_TEXT, DELTA_TOOL_CALL, FIRST_TOKEN, STREAM_END; exposed via the lazy Agent.stream_emitter property (praisonaiagents/agent/agent.py:285-294).
  • The gateway relays these deltas only to WebSocket clients: _make_stream_relay() in src/praisonai/praisonai/gateway/server.py:925-975 maps DELTA_TEXT β†’ EventType.TOKEN_STREAM.
  • The channel path is fully blocking: BotSessionManager.chat() (src/praisonai/praisonai/bots/_session.py:166-279) swaps history, calls agent.chat(prompt) in an executor, and returns only the final string, which the bot then sends.
  • Every adapter already has the primitive needed for progressive delivery but it is never used for streaming:
    • src/praisonai/praisonai/bots/telegram.py:591 β€” edit_message()
    • src/praisonai/praisonai/bots/discord.py:353, slack.py:515, whatsapp.py:913, email.py:520, agentmail.py:753
  • grep -rn "stream" src/praisonai/praisonai/bots/ returns no hits in the session/delivery path; praisonaiagents/bots/config.py (BotConfig) has no streaming option.
  • The typing-indicator renewal loop in gateway/server.py:1928-1937 exists precisely because long turns currently show nothing β€” treating the symptom, not the cause.

Desired behaviour

A bot-layer stream consumer that subscribes to the agent's stream_emitter during a channel turn and progressively edits a single placeholder message as tokens arrive, with:

  • A configurable minimum edit interval (e.g. 700Β ms default) so platform rate limits are respected, integrating with the existing RateLimiter (bots/_rate_limit.py).
  • Adaptive backoff when the platform signals flood control (reuse _resilience.is_recoverable_error).
  • Markdown/code-fence-safe partial rendering (reuse bots/_chunk.py fence logic so a half-open code block never renders broken).
  • Overflow handling: when the streamed text exceeds the platform message limit, finalise the current message and continue in a new one.
  • Graceful fallback: platforms whose adapter lacks reliable edits (email) keep today's blocking behaviour automatically.
  • Off by default (current behaviour preserved), opt-in per bot/channel.

Layer placement

  • Primary layer: wrapper (src/praisonai/praisonai/bots/)
  • Why not core: core already provides the right contract (StreamEventEmitter, GatewayProtocol TOKEN_STREAM events); platform delivery mechanics (edit cadence, flood control, per-platform limits) belong with the adapters. Only a small BotConfig field is a core touch.
  • Why not wrapper-only without core touch: the streaming toggle should live in praisonaiagents/bots/config.py so BotConfig stays the single config surface.
  • Why not tools: this is framework-driven delivery of agent output, not an agent-callable capability.
  • Why not plugins: it is per-platform transport logic tied to bot adapters, not a cross-cutting lifecycle hook.
  • Secondary touch (optional): core β€” add streaming: bool = False and stream_edit_interval_ms: int to BotConfig.
  • 3-way surface (CLI + YAML + Python): yes β€” praisonai bot start --stream, channels.<name>.streaming: true in gateway.yaml, BotConfig(streaming=True).

Proposed approach

  • Extension point: config (BotConfig.streaming) + a new bots/_stream.py consumer wired inside BotSessionManager.chat().
  • Minimal API sketch:
# bots/_stream.py
class ChannelStreamConsumer:
    """Bridges Agent.stream_emitter deltas to progressive adapter edits."""
    def __init__(self, adapter, channel_id, *, edit_interval_ms=700): ...
    async def start(self, placeholder_msg_id: str): ...
    def on_delta(self, event): ...   # sync callback from agent worker thread β†’ queue
    async def finalize(self, final_text: str): ...

Resolution sketch

# Before (today): user waits 40s staring at "typing…", then one wall of text
channels:
  telegram:
    platform: telegram
    token: "${TELEGRAM_BOT_TOKEN}"

# After (proposed): tokens appear live in an edited message
channels:
  telegram:
    platform: telegram
    token: "${TELEGRAM_BOT_TOKEN}"
    streaming: true
    stream_edit_interval_ms: 700
# Python, after
from praisonai.bots import Bot
Bot(platform="telegram", agent=agent, streaming=True).start()

Severity

High β€” the most user-visible quality gap on every supported chat platform; all the building blocks (stream events, edit_message, rate limiter, chunker) already exist but are unconnected.

Validation

  • Read src/praisonai/praisonai/bots/_session.py (blocking chat() flow, lines 166–279); confirmed no streaming references in bots/ delivery path via grep.
  • Confirmed edit_message() exists on all six adapters (paths above) and is only used for command UI (e.g. pairing buttons, telegram.py:353).
  • Confirmed core StreamEventEmitter (praisonaiagents/streaming/events.py) and the WebSocket-only relay (gateway/server.py:925-975).
  • Confirmed BotConfig (praisonaiagents/bots/config.py) has no streaming field.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingclaudeAuto-trigger Claude analysisenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions