Skip to content

feat: per-thread serialization, conditional streaming, and multi-bot fixes#420

Merged
thepagent merged 14 commits intoopenabdev:mainfrom
dogzzdogzz:feat/per-thread-serialization
Apr 18, 2026
Merged

feat: per-thread serialization, conditional streaming, and multi-bot fixes#420
thepagent merged 14 commits intoopenabdev:mainfrom
dogzzdogzz:feat/per-thread-serialization

Conversation

@dogzzdogzz
Copy link
Copy Markdown
Contributor

@dogzzdogzz dogzzdogzz commented Apr 17, 2026

Fixes #419
Fixes #440
Fixes #441

What problem does this solve?

Two bugs that break Slack bot-to-bot communication, plus one UX bug where agents reply to channel root instead of thread.

See issue #419 for full root cause analysis.

Prior Art Research

OpenClaw

  • Streaming: send initial message → chat.update every 1s (or native chat.startStream)
  • Bot-to-bot: allowBots: false by default — drops all bot messages, no partial-content fix
  • No per-thread serialization found

Hermes Agent

  • Streaming: GatewayStreamConsumer — send initial + chat.update loop, ~1s interval
  • Bot-to-bot: SLACK_ALLOW_BOTS=none by default — drops all bot messages
  • _active_sessions dict for some serialization, but blocking

Key difference: both frameworks avoid bot-to-bot by dropping all bot messages. OpenAB supports configurable bot-to-bot (allow_bot_messages), which requires this fix to work correctly.

What's in this PR

File Change
src/adapter.rs Conditional streaming: edit_message() + use_streaming() on ChatAdapter trait (default: no-op / false); stream_prompt() branches based on adapter config
src/slack.rs SlackAdapter stores allow_bot_messages, implements edit_message via chat.update, returns use_streaming()=true when Off
src/slack.rs KeyedAsyncQueue with lazy cleanup + panic-safe acquire; per-thread serialization for both app_mention and message
src/discord.rs Remove mention_roles from is_mentioned (fixes multi-bot false positive)
charts/openab/templates/configmap.yaml Add allowUserMessages support for [discord] section (parity with [slack])
src/format.rs Add truncate_chars_tail() — tail-priority truncation for streaming display

Behavior change

Conditional streaming (Slack)

allow_bot_messages Delivery Rationale
off (default) Streaming edit ("…" → live updates → final) Bot messages dropped anyway — zero UX regression
mentions / all Send-once (complete reply at end) Protects bot-to-bot from placeholder and message_changed interference

Bot-to-bot (send-once path)

Before:
  BotA sends "..."          → BotB sees "...", responds to it  ✗
  BotA edits (streaming)    → message_changed, BotB filters    ✗

After:
  BotA reaction 👀🤔🔥     → BotB ignores reactions
  BotA sends complete reply → BotB sees full content            ✓

Human UX (streaming path, default)

Before: "..." appears immediately, text updates every 1.5s (always)
After:  "…" appears immediately, text updates every 1.5s (only when allow_bot_messages=off)

Multi-bot Discord fix

Before: @AgentDealer → both AgentDealer AND AgentBroker respond (mention_roles false positive)
After:  @AgentDealer → only AgentDealer responds (mentions_user_id check only)

thread_id in SenderContext

{
  "schema": "openab.sender.v1",
  "channel": "slack",
  "channel_id": "C0123456789",
  "thread_id": "1234567890.123456",
  "sender_id": "U0123456789"
}

Test plan

  • cargo check — clean, no warnings
  • cargo clippy — clean
  • Slack: streaming edit works when allow_bot_messages = "off"
  • Slack: send-once works when allow_bot_messages = "mentions"
  • Slack bot-to-bot: BotA reply triggers BotB with complete content (not "…")
  • Discord: @AgentBroker → only AgentBroker responds
  • Discord: @AgentDealer → only AgentDealer responds
  • Discord: allowUserMessages = "mentions" correctly requires @mention in threads
  • Helm: agents.X.discord.allowUserMessages renders in configmap
  • Agent with Slack MCP tool: reply lands in correct thread via sender_context.thread_id

Discord thread

https://discord.com/channels/1491295327620169908/1493496229168939130/1494352889563320430

@dogzzdogzz dogzzdogzz marked this pull request as ready for review April 17, 2026 13:22
@dogzzdogzz dogzzdogzz requested a review from thepagent as a code owner April 17, 2026 13:22
@thepagent thepagent added the b2b label Apr 18, 2026
@chaodu-agent
Copy link
Copy Markdown
Collaborator

Review Discussion: Conditional Streaming vs Blanket Send-Once

Context

This PR switches all response delivery to send-once (emoji progress indicators + single message at completion), removing streaming edit entirely. This cleanly fixes bot-to-bot communication, but it also regresses the most common scenario: one human + one bot.

Line of Reasoning

1. The core problems this PR fixes are specifically two:

  • The "..." placeholder triggers other bots in all mode (they respond to it as real content)
  • Streaming edit delivers the final content via message_changed, which gets filtered by skip_subtype in mentions mode — so @mentions from bot replies are silently dropped

Both share the same root cause: no message events should appear on Slack before the reply is complete.

2. In mentions mode, do multiple bots tagged simultaneously interfere with each other?

Mostly no — each bot answers its own question and doesn't mention the other. An LLM could theoretically add an @mention on its own, but this is rare in practice and can be mitigated via system prompt.

3. What about allow_bot_messages = "off" (the default)?

All bot messages are dropped, including message_changed. Streaming edit is completely safe in this mode — there is no bot-to-bot problem to solve.

4. Conclusion: most users don't need to pay the UX cost for a bot-to-bot feature they haven't enabled.

Suggestion: Conditional Streaming

Branch behavior based on the allow_bot_messages config:

if allow_bot_messages == "off":              # default, majority of users
  -> streaming edit (good human UX, bot messages are dropped anyway)

if allow_bot_messages == "mentions" | "all":
  -> send-once (protects bot-to-bot communication)

Benefits:

  • Default behavior (off) retains streaming with zero UX regression
  • Decision is deterministic — purely config-driven, no edge cases
  • Streaming edit code doesn't need to be deleted, just made conditional
  • Users who enable bot-to-bot explicitly accept the send-once trade-off

This is not a blocker — the blanket send-once in this PR is correct and safe. This is a suggestion for a follow-up optimization so the default experience remains unchanged.

pahud pushed a commit that referenced this pull request Apr 18, 2026
…onfig

When allow_bot_messages=off (default): use streaming edit (placeholder + live
updates via chat.update) for better human UX.

When allow_bot_messages=mentions|all: use send-once to protect bot-to-bot
communication from placeholder/message_changed interference.

Addresses: #420 (comment)
@thepagent
Copy link
Copy Markdown
Collaborator

Follow-up commits pushed: conditional streaming + multi-bot fixes

Pushed 3 commits addressing the conditional streaming suggestion and two bugs found during multi-bot testing.

1. 7ee492c — feat(slack): conditional streaming edit based on allow_bot_messages

Implements the suggestion from the review: branch Slack response delivery based on config rather than blanket send-once.

allow_bot_messages Delivery Why
off (default) Streaming edit Bot messages are dropped anyway — majority of users keep the live-update UX
mentions / all Send-once Protects bot-to-bot from "…" placeholder triggering responses and message_changed being filtered by skip_subtype

Changes:

  • Added edit_message() and use_streaming() to ChatAdapter trait (default: no-op / false)
  • SlackAdapter implements edit_message via chat.update, returns use_streaming() = true when allow_bot_messages == Off
  • stream_prompt() branches: streaming mode sends "…" placeholder + 1.5s edit loop; send-once mode accumulates and sends at completion
  • Added format::truncate_chars() for streaming display truncation

2. d814158 — fix(discord): remove mention_roles from is_mentioned check

Bug: when multiple bots share a Discord channel, @mentioning one bot triggered the other. Root cause: the is_mentioned check included mention_roles — if both bots share a role, the role-based match fired for both.

Fix: removed the mention_roles clause. mentions_user_id() + raw content <@BOT_ID> check is sufficient and correctly scoped to the specific bot being mentioned.

3. 1260507 — fix(helm): add allowUserMessages for Discord in configmap

Bug: allowUserMessages was only rendered in the [slack] section of the Helm configmap template. Setting agents.kiro.discord.allowUserMessages=mentions via Helm was silently ignored — Discord always defaulted to involved.

Fix: added the same allowUserMessages block to the [discord] section with validation (involved / mentions).

Testing

Validated on local OrbStack cluster with two Kiro-powered bots (AgentBroker + AgentDealer) in the same Discord channel:

  • @AgentBroker → only AgentBroker responds
  • @AgentDealer → only AgentDealer responds
  • ✅ Slack streaming edit works when allow_bot_messages = "off"
  • ✅ Slack send-once works when allow_bot_messages = "mentions"
  • allowUserMessages = "mentions" correctly requires @mention in Discord threads

No breaking changes — default behavior is unchanged.

@thepagent
Copy link
Copy Markdown
Collaborator

變更摘要(繁體中文)

起因

處理 PR #420 的 review comment — 建議 Slack 的回覆方式不要一刀切改成 send-once,應該根據 allow_bot_messages 設定來決定。

完成的事項

1. Slack 條件式串流

  • allow_bot_messages = "off"(預設)→ 串流編輯,使用者看到即時更新的回覆
  • allow_bot_messages = "mentions" / "all" → 一次送出,避免 bot 對 bot 通訊被佔位符干擾

2. Discord 多 bot 修正

  • 發現 is_mentionedmention_roles 檢查有 bug — 同頻道兩個 bot 共用 role 時,@其中一個會觸發另一個
  • 移除 mention_roles,只用 mentions_user_id 判斷

3. Helm chart 修正

  • allowUserMessages 設定原本只對 Slack 生效,Discord 區塊漏掉了
  • 補上後可以透過 Helm 控制 Discord bot 是否需要 @mention 才回覆

4. 部署 AgentDealer(第二個 bot)驗證

  • 從 AgentBroker 的 PVC 複製認證資料
  • 兩個 bot 共存同一頻道,各自只回應自己的 @mention

影響

零 breaking change,預設行為不變。


架構圖

                        條件式串流(Slack)
                        ==================

  allow_bot_messages = "off"(預設,大多數使用者)

    User: @Bot 幫我看 code
      │
      ▼
    Bot: "…"  ──1.5s──►  "正在分析..."  ──1.5s──►  "分析完成,這段 code..."
         │                     │                          │
      placeholder          chat.update                chat.update
      (即時回饋)           (串流更新)                 (最終結果)


  allow_bot_messages = "mentions" / "all"(bot 對 bot 模式)

    BotA: @BotB 幫我 deploy
      │
      ▼
    BotB: (靜靜等待完成...)
      │
      ▼
    BotB: "已部署到 staging ✅"
           │
        chat.postMessage
        (一次送出,不會觸發其他 bot)


                     多 Bot 修正(Discord)
                     ====================

  修正前(bug):
  ┌─────────────────────────────────────┐
  │  User: @AgentDealer HI             │
  │         │                          │
  │         ├──► AgentDealer: 收到 ✅   │
  │         │                          │
  │         └──► AgentBroker: 也收到 ❌ │  ← mention_roles 誤判
  └─────────────────────────────────────┘

  修正後:
  ┌─────────────────────────────────────┐
  │  User: @AgentDealer HI             │
  │         │                          │
  │         ├──► AgentDealer: 收到 ✅   │  ← mentions_user_id 正確匹配
  │         │                          │
  │         └──► AgentBroker: 忽略 ✅   │  ← 不是 @我,不理
  └─────────────────────────────────────┘


                   OrbStack 驗證架構
                   ================

  ┌─ Kubernetes (OrbStack) ─────────────────────────────┐
  │                                                     │
  │  ┌─ openab-kiro (AgentBroker) ───────────────────┐  │
  │  │  openab → kiro-cli acp                        │  │
  │  │  Discord: #channel-1  +  Slack ✅              │  │
  │  │  PVC: openab-kiro (auth + settings)           │  │
  │  └───────────────────────────────────────────────┘  │
  │                                                     │
  │  ┌─ openab-dealer (AgentDealer) ─────────────────┐  │
  │  │  openab → kiro-cli acp                        │  │
  │  │  Discord: #channel-1 + #channel-2             │  │
  │  │  PVC: openab-dealer (cloned from kiro)        │  │
  │  └───────────────────────────────────────────────┘  │
  │                                                     │
  └─────────────────────────────────────────────────────┘

@thepagent
Copy link
Copy Markdown
Collaborator

@thepagent thepagent force-pushed the feat/per-thread-serialization branch from dc87eb9 to 0026143 Compare April 18, 2026 06:20
Comment thread charts/openab/templates/configmap.yaml
Comment thread src/adapter.rs
Comment thread src/adapter.rs
Comment thread src/slack.rs
Comment thread src/discord.rs
Comment thread src/format.rs Outdated
@thepagent thepagent self-assigned this Apr 18, 2026
@thepagent thepagent added the p1 High — address this sprint label Apr 18, 2026
dogzzdogzz and others added 10 commits April 18, 2026 02:26
…onfig

When allow_bot_messages=off (default): use streaming edit (placeholder + live
updates via chat.update) for better human UX.

When allow_bot_messages=mentions|all: use send-once to protect bot-to-bot
communication from placeholder/message_changed interference.

Addresses: openabdev#420 (comment)
The mention_roles check caused false positives when multiple bots share
a channel — @mentioning one bot would trigger the other if they share
a Discord role. The mentions_user_id + content check is sufficient.
…gmap

Previously allowUserMessages was only rendered for the [slack] section.
This adds the same support for [discord], enabling per-agent control of
whether the bot requires @mention in threads.
@thepagent thepagent force-pushed the feat/per-thread-serialization branch from 0026143 to 61f9b96 Compare April 18, 2026 06:28
…ority truncation

- KeyedAsyncQueue::acquire() now does lazy cleanup when entries > 100,
  evicting idle semaphores (strong_count == 1 && permits available)
- Replaced expect() with match + warn + skip (returns Option) to avoid
  task panic on closed semaphore
- Removed unused cleanup_idle() (dead code)
- Switched streaming truncation from head-priority to tail-priority so
  users see the most recent agent output during live updates
- Added comments explaining queue_key construction differences between
  app_mention (ts-only) and message (channel:ts for DM uniqueness)
@thepagent thepagent merged commit 66b5714 into openabdev:main Apr 18, 2026
10 checks passed
@thepagent thepagent changed the title fix(slack): bot-to-bot communication — per-thread serialization + send-once reply feat(slack): per-thread serialization + conditional streaming + multi-bot fixes Apr 18, 2026
@thepagent thepagent changed the title feat(slack): per-thread serialization + conditional streaming + multi-bot fixes feat: per-thread serialization, conditional streaming, and multi-bot fixes Apr 18, 2026
@thepagent
Copy link
Copy Markdown
Collaborator

Note: Role mentions no longer trigger the bot

Commit c281b2e removed mention_roles from the is_mentioned check to fix the multi-bot false positive (#440). As a side effect, role mentions (@BotRole) no longer trigger the bot — only direct user mentions (@BotUser) work.

This is the correct behavior:

  • Role mentions are shared across bots and can cause false positives in multi-bot setups
  • User mentions are unique per bot and unambiguous
  • Discord autocomplete shows both options — users should pick the bot user, not the role

No code change needed — just documenting the intentional behavior change.

thepagent added a commit that referenced this pull request Apr 19, 2026
…config guide

Covers: config reference, @mention behavior (user vs role), thread behavior,
multi-bot setup, bot-to-bot communication, Helm values, and troubleshooting.

References: #420, #440, #441, #455, #457
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

b2b p1 High — address this sprint

Projects

None yet

3 participants