Skip to content

bug(multibot): keep Discord threads responsive during long-running tasks #1436

Description

@chaodu-obk

Description

In multibot mode, the main bot can become completely silent while it is carrying out a long-running task, especially when working together with stronger sub-agents such as Sol or Fable. A task may run for roughly 30 minutes without any visible message, progress update, or heartbeat. The bot may eventually return a large amount of output all at once, or it may appear to be stuck until a user sends another message to wake it up.

This creates a serious usability problem: users cannot tell whether the task is still running, whether the ACP session has failed, or whether the main bot is waiting for input. Users have started checking the ACP session manually just to determine whether work is still in progress. When the bot is actually stuck, sending a wake-up message can result in the bot responding that it has no context or does not know why it was interrupted.

The current multibot behavior disables streaming because partial output could contain a mention of another bot. The concern is that another bot might see and act on an incomplete message. However, Discord message edits may not be treated as new mention notifications in the same way as newly created messages, so this assumption should be validated rather than requiring the entire thread to remain silent during long tasks.

Steps to Reproduce

  1. Run OpenAB in Discord multibot mode with a main bot that can delegate work to a long-running sub-agent such as Sol or Fable.
  2. Send a request that requires an extended task involving delegation, tool calls, or substantial reasoning.
  3. Observe the Discord thread while the task is running.
  4. Wait for a sufficiently long execution, such as 10-30 minutes.
  5. Check whether the thread receives any progress, streaming output, heartbeat, or other indication that the task is still active.
  6. If no response arrives, inspect the ACP session or send a follow-up message to determine whether the bot is still running.

The issue is intermittent in its final outcome: some tasks eventually produce a burst of output, while others appear to stop responding until manually prompted.

Expected Behavior

During a long-running task, the thread should provide a reliable indication that work is still in progress without requiring the user to inspect an ACP session or send a wake-up message. The final response should preserve the complete task context and should not trigger duplicate or premature work in other bots.

A suitable implementation could provide one or more of the following:

  • Stream incremental output by editing a placeholder message.
  • Emit a configurable progress heartbeat while the task is active.
  • Show the current execution phase or delegated sub-agent status.
  • Preserve correct multibot mention routing and ensure that partial output does not activate another bot prematurely.

Architectural Direction: Separate Presentation and Control Planes

The design should treat human-facing progress and bot-facing work delegation as two different protocols:

  • Presentation plane: Streaming edits, progress updates, and heartbeats exist for humans. They show that the main bot is alive and make the latest progress visible, but they are incomplete by definition and must never be treated as executable bot-to-bot input.
  • Control plane: A bot-to-bot handoff is emitted only after the main bot has produced a complete and stable request. It is a single structured event containing the source bot, target bot, event ID, origin thread/message, expiry or hop limits, and the complete payload.

A Discord-based first implementation could use one new MESSAGE_CREATE as the transport: the message targets the receiving bot and carries an openab.multibot.handoff.v1 JSON attachment (or equivalent structured envelope). The receiving bot validates the trusted source, target identity, event ID, expiry, and hop count before submitting payload.text to its own dispatcher. The streaming progress message remains human-facing and is never used to trigger the receiving bot.

This separation allows streaming to remain enabled for human observability without exposing partial output to other bots. It also avoids requiring the receiving bot to reconstruct a request from edited messages or Discord history.

Non-Negotiable Mention Safety Invariants

  • allowed_mentions = none MUST be applied to the initial Discord send_message that creates the streaming placeholder, not only to later edit_message calls. This remains safe even if a future implementation uses the first output chunk as the initial message.
  • Streaming must maintain a raw accumulated buffer separately from the human-visible display buffer. The complete accumulated content must be sanitized before every display edit.
  • Sanitization must neutralize structured mentions (<@id>, <@!id>, and <@&id>) and broadcast mentions (@everyone and @here). It must also handle mention syntax split across deltas.
  • Sanitization must be applied again after truncation and to every display or overflow chunk. No streaming or overflow chunk may trigger another bot.
  • The final handoff must be a single new MESSAGE_CREATE. Its target mention must be constructed from the validated target_bot_id exactly once; the implementation must not restore every mention found in the raw buffer.
  • The final handoff must set allowed_mentions to the validated target bot only. Non-target users, bots, roles, @everyone, and @here must never become dispatch targets through the handoff payload.
  • The Agent may request a handoff through an explicit output intent such as [[handoff:<target_bot_id>]], but OAB owns validation, event creation, routing, retries, and deduplication. The intent is not itself a Discord dispatch event.

Impact

This makes long-running multibot tasks feel unreliable even when the underlying ACP session is healthy. It causes users to:

  • Repeatedly check whether the ACP process is still alive.
  • Lose confidence that a task will complete without intervention.
  • Send unnecessary wake-up messages that can break or confuse the task context.
  • Miss useful intermediate information and receive a large, difficult-to-follow output burst at the end.
  • Avoid using delegation for larger tasks because the thread appears frozen.

The issue affects the core interactive experience of multibot mode and is particularly visible as agent capabilities improve and individual tasks take longer to complete.

Proposed Direction

Evaluate a streaming or progress-update design behind a feature flag, starting with an isolated Discord test thread. One possible design is:

  1. Create a placeholder response message when the long-running task starts.
  2. Edit that message at a throttled interval (for example, every 500-1000 ms or at a platform-safe cadence) as output becomes available.
  3. Ensure the multibot dispatcher does not react to every MESSAGE_UPDATE event as if it were a new user message.
  4. Treat bot-to-bot mentions as dispatchable only after the relevant content is complete and stabilized, or otherwise define an explicit safe boundary for mention processing.
  5. Retain a low-frequency heartbeat or status update as a fallback when no streamable text is available.
  6. Add observability for task start, progress updates, completion, timeout, and interruption so that streaming failures can be distinguished from agent failures.

The implementation should also account for Discord message length limits, edit rate limits, cancellation, retries, and recovery after a process restart.

Alternatives Considered

  • Heartbeat only: Lower risk than streaming and likely sufficient to show that the task is alive, but it does not provide intermediate output or reduce the final output burst.
  • Re-enable full streaming without mention safeguards: Improves perceived responsiveness but could cause partial bot mentions, duplicate activation, or cross-bot feedback loops.
  • Keep the current behavior: Avoids partial mention concerns but leaves users with no reliable way to distinguish a slow task from a dead session.

Acceptance Criteria

  • A long-running multibot task gives the user a visible liveness or progress signal without requiring manual ACP inspection.
  • Streaming or heartbeat updates are throttled and remain within Discord API limits.
  • Partial output cannot cause another bot to execute prematurely or more than once.
  • The final response remains complete, correctly associated with the original task, and recoverable after interruption where supported.
  • Automated tests cover update handling, mention routing, throttling, completion, cancellation, and failure paths.
  • The behavior can be tested and rolled back independently, preferably through a feature flag or equivalent configuration.

Environment

  • Platform: Discord
  • Mode: multibot
  • Observed with: long-running delegated tasks involving agents such as Sol or Fable
  • Repository: openabdev/openab

Screenshots / Logs

No logs are attached yet. The primary symptom is the absence of any visible response for an extended period, followed by either a delayed output burst or a thread that requires a follow-up message to respond.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions