Skip to content

Channel sessions lack in-flight run control: no busy feedback, no queued follow-up semantics, no /stop interrupt despite core InterruptControllerΒ #1914

@MervinPraison

Description

@MervinPraison

Summary

When a user messages a bot while the agent is still processing their previous message, the new message silently blocks on a per-user lock: no acknowledgement, no way to cancel the running turn, no way to steer it. On chat platforms this is a daily-use failure mode β€” long agentic turns (tool loops, web research) routinely take 30–120 seconds, and users naturally send follow-ups ("actually, make it shorter", "stop"). The core SDK already ships an InterruptController and message steering, but the bot layer never exposes them, so from a channel there is no way to interact with an in-flight run at all.

Current behaviour

  • BotSessionManager serialises turns with a per-user asyncio.Lock (src/praisonai/praisonai/bots/_session.py:113-118, acquired at line 216 around the executor-run agent.chat() call). A mid-run message simply awaits the lock β€” invisible queuing with no feedback, no merging, no ordering guarantees beyond lock fairness, and no cap.
  • InboundDebouncer (bots/_debounce.py) only coalesces rapid bursts before a run starts; once agent.chat() is executing it does nothing for follow-ups.
  • Built-in commands are limited to /status, /new, /help (gateway/server.py Telegram handler; bots/_commands.py) β€” there is no /stop or /queue; grep for interrupt|busy|pending across bots/ and gateway/ matches only KeyboardInterrupt handlers.
  • Core already provides the contracts the bot layer needs:
    • praisonaiagents/agent/interrupt.py β€” InterruptController for cancelling/aborting a running agent;
    • SteeringMixin on Agent (praisonaiagents/agent/agent.py:261 mixin list) β€” mid-run message steering/prioritisation;
    • per-session inbox queue on GatewaySession (gateway/server.py:98-110) β€” but only for the WebSocket stepper path, not channel bots.

Desired behaviour

Per-session run control for every channel bot:

  1. Busy acknowledgement β€” a mid-run message gets immediate, debounced feedback ("⏳ still working on your last message β€” it'll be considered next" ), instead of silence.
  2. Pending-message slot β€” follow-ups received mid-run are held (repeats merged) and delivered as the next turn after the current run drains, rather than blindly queuing on a lock.
  3. /stop command β€” cancels the in-flight run via core InterruptController, releases the session, confirms to the user.
  4. Optional steer mode β€” configurable policy: busy_mode: queue | interrupt | steer, where steer injects the follow-up into the running turn via SteeringMixin.
  5. Run-generation guard so a cancelled run's late completion cannot overwrite the fresh turn's state.

Layer placement

  • Primary layer: wrapper (src/praisonai/praisonai/bots/)
  • Why not core: the run-control contracts (InterruptController, steering, session protocols) already live in core; what is missing is channel-side wiring and UX, which is platform delivery behaviour.
  • Why not tools: not agent-callable β€” it is how the framework handles inbound traffic around a running agent.
  • Why not plugins: busy/queue/interrupt semantics must be a first-class, always-available bot behaviour, not an optional lifecycle add-on; plugins could later customise policies via hooks.
  • Secondary touch (optional): core β€” add busy_mode and busy_ack fields to praisonaiagents/bots/config.py BotConfig.
  • 3-way surface (CLI + YAML + Python): yes β€” praisonai bot start --busy-mode queue, channels.<name>.busy_mode: queue in gateway.yaml, BotConfig(busy_mode="queue").

Proposed approach

  • Extension point: config (BotConfig.busy_mode) + a bots/_run_control.py module wired into BotSessionManager.chat() and the shared command handler.
  • Minimal API sketch:
# bots/_run_control.py
class SessionRunControl:
    """Per-session pending slot + interrupt handle around agent runs."""
    def submit(self, user_id, text) -> RunDecision:  # RUN_NOW | QUEUED | MERGED
    async def stop(self, user_id) -> bool:           # wires InterruptController
    def next_pending(self, user_id) -> str | None:   # drained after each turn

Resolution sketch

# Before (today)
user: "research X"            β†’ agent runs 90s
user: "stop"                  β†’ silently blocks on per-user lock
user: "hello?"                β†’ also blocks; after 90s the agent answers
                                "research X", then processes "stop" and
                                "hello?" as fresh prompts, out of context

# After (proposed)
user: "research X"            β†’ agent runs
user: "actually only EU data" β†’ bot: "⏳ noted β€” applying to the next turn"
                                (merged into pending slot, or steered live)
user: "/stop"                 β†’ run cancelled via InterruptController,
                                bot: "βœ… stopped. Send a new message."

Severity

High β€” affects every multi-turn conversation on every platform once turns exceed a few seconds; the current silent-lock behaviour reads as the bot being broken, and there is no escape hatch short of waiting out the run.

Validation

  • Traced BotSessionManager.chat() lock flow (bots/_session.py:113-118, 191-216): mid-run messages await the per-user lock with no feedback or merging.
  • Confirmed _debounce.py only operates pre-run; confirmed built-in commands lack /stop (bots/_commands.py, gateway Telegram handler).
  • Confirmed grep -rin "interrupt|busy|pending" src/praisonai/praisonai/bots/ src/praisonai/praisonai/gateway/ matches only KeyboardInterrupt handlers.
  • Confirmed core InterruptController (praisonaiagents/agent/interrupt.py) and SteeringMixin (mixin list at praisonaiagents/agent/agent.py:261) exist and are unused by the bot layer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claudeAuto-trigger Claude analysisenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions