Skip to content

feat(slack): make stripping the bot's own mention configurable (slack.strip_bot_mentions) - #27

Merged
nikitaBarkov merged 1 commit into
mainfrom
nikita.barkov/strip_mention_flag
Aug 11, 2026
Merged

feat(slack): make stripping the bot's own mention configurable (slack.strip_bot_mentions)#27
nikitaBarkov merged 1 commit into
mainfrom
nikita.barkov/strip_mention_flag

Conversation

@nikitaBarkov

Copy link
Copy Markdown

What does this PR do?

Makes the deletion of the bot's own Slack mention optional, behind a new slack.strip_bot_mentions config field (default true — exactly today's behavior).

The adapter deletes the bot's own <@U…> token from the text before the agent reads it, and reports nothing in its place. That is fine while the adapter alone decides who gets an answer, but a thread keeps waking the bot after the first mention (_mentioned_threads, with thread_require_mention off by default), so every delivered turn then looks identical: the agent cannot tell "someone just tagged me" from "I was woken by thread routing".

That distinction is what an agent needs to decide for itself whether a turn deserves an answer at all — a stay-silent contract can only be described to it if the two cases look different in the text. With strip_bot_mentions: false an explicit tag stays visible where the author put it, and its absence means the bot was woken by channel or thread routing. The asymmetry itself is the signal; no marker text is injected.

The mention is rendered as @BotName, the same shape _humanize_user_mentions() already gives mentions of other participants, so the agent reads an ordinary tag rather than a raw <@U…> id or a synthetic (directed at you) prefix. The name comes from _team_bot_names / _bot_display_name, both resolved at connect time — no extra Slack API call per message. If the display name is not resolved yet, the raw token is left in place rather than deleted.

Routing is untouched: is_mentioned, _mentioned_threads, require_mention, strict_mention, thread_require_mention all keep their current meaning and are all evaluated before this. Command parsing is untouched too — it runs off the separate mention_stripped variable, so @bot /status and @bot !new dispatch identically in both states.

Related Issue

None — needed for our Slack deployment.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • plugins/platforms/slack/adapter.py: _slack_strip_bot_mentions() next to its four siblings (_slack_require_mention, _slack_strict_mention, _slack_ignore_other_user_mentions, _slack_thread_require_mention) — same resolution order, config.extraSLACK_STRIP_BOT_MENTIONS → default true, same explicit-false parsing.
  • plugins/platforms/slack/adapter.py: _own_bot_name() (the display name already in memory, workspace-scoped) and _render_own_mention() (<@U123> / <@U123|label>@BotName, in place, callable re.sub replacement so a backslash in a display name cannot raise re.error).
  • plugins/platforms/slack/adapter.py: the strip site in _handle_slack_message() is now gated — strip, or render in place.
  • plugins/platforms/slack/adapter.py: thread history follows the same policy, so past turns don't read as "nobody ever tagged me" — _render_message_text() takes strip_bot_mention / bot_name, _format_thread_context() passes the resolved flag through both of its strip sites.
  • plugins/platforms/slack/adapter.py: Block Kit dedupe is fed the pre-render text, and bot_uid only while stripping. The dedupe deletes <@bot_uid> from both sides to bridge the strip below it; with the mention kept, comparing a rendered @BotName against the block's raw token never matches (every mentioned thread message would be appended twice, raw token included), and deleting the token from both sides reduces a mention-only block to the empty string, which reads as new content and appends the mention a second time. Both were reproduced directly and are covered by tests.
  • plugins/platforms/slack/adapter.py: _fetch_thread_parent_text() forwards the caller's strip_bot_mention into the render on a cache miss. That path is the root-mention wake check (fix(slack): route replies from mentioned thread parents NousResearch/hermes-agent#24848), which greps the parent for the raw <@id> — previously the render deleted the token unconditionally, so on a cold cache the check could never match, in either flag state.
  • hermes_cli/config_defaults.py: slack.strip_bot_mentions: True with a comment.
  • plugins/platforms/slack/plugin.yaml: optional_env entry for the SLACK_STRIP_BOT_MENTIONS mirror.
  • website/docs/user-guide/messaging/slack.md, website/docs/reference/environment-variables.md: the new key, its default, and what it explicitly does not change.
  • tests/gateway/test_slack_strip_bot_mentions.py: 52 tests, most parameterized over both flag states.

How to Test

  1. Default path — change nothing. @hermes what's up? still arrives as what's up?, and no existing test needed editing.
  2. Opt in via config.yaml:
    slack:
      strip_bot_mentions: false
  3. In Slack: hey @hermes look arrives as hey @hermes look — tag in place, not a raw <@U…>, not moved to the front.
  4. Reply in the same thread without tagging — arrives unchanged, no marker. That difference is the signal.
  5. @hermes /status and @hermes !new still dispatch as /status / /new, in both states.
  6. No line is delivered twice — neither a mention-only ping (@hermes alone) nor a message Slack mirrors into a rich_text block.
  7. scripts/run_tests.sh tests/gateway/test_slack_strip_bot_mentions.py tests/gateway/test_slack.py tests/gateway/test_slack_mention.py tests/gateway/test_slack_mention_humanization.py tests/test_slack_thread_require_mention.py tests/gateway/test_config.py388 passed.
  8. scripts/run_tests.sh tests/gateway/test_slack_block_kit.py tests/gateway/test_slack_block_kit_adapter.py tests/gateway/test_slack_ignore_other_user_mentions.py tests/gateway/test_slack_require_mention_channels.py tests/gateway/test_slack_peer_agent_smoke.py41 passed.
  9. scripts/run_tests.sh tests/gateway/5009 passed (6 pre-existing environment failures on macOS: the optional XML dependency for wecom, Linux-only abstract sockets, api_server health/readiness).

Checklist

Code

  • I've read the Contributing Guide
  • My commit message follows Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • This PR contains only changes related to this feature, in one commit
  • I've run the affected suites and they pass
  • I've added tests for both flag states, including the two duplication cases that only appear with the mention kept
  • I've tested on my platform: macOS 15

Documentation & Housekeeping

  • I've updated the Slack user guide and the environment-variable reference
  • cli-config.yaml.example — N/A, the Slack block lives in hermes_cli/config_defaults.py, which is updated
  • CONTRIBUTING.md / AGENTS.md — N/A, no architecture or workflow change
  • Cross-platform impact considered — pure Python Slack adapter behavior
  • Tool descriptions/schemas — N/A, no tool changes

Notes

  • The default is upstream's behavior byte for byte, which is why no existing test needed editing — that is the invariant this change was held to.
  • The identity prompt (_build_identity_prompt) is deliberately left as it is in both states. It says the routing mention "may have been stripped", which is modal and stays true either way; whether a message deserves an answer is stated in the operator's own prompt, not here.
  • Known and accepted: with the mention kept, answering a gateway prompt with a tag (@bot 2, @bot always, @bot y) no longer exact-matches the clarify-choice / slash-confirm / pending-/update branches in gateway/run.py. Those are unreachable in our deployment (the clarify toolset is disabled, approvals.mode: "off", /update unused), and the same exposure exists for any approach that keeps the mention in the text.
  • Left alone as a pre-existing defect, identical in both flag states: section / header / context blocks and legacy attachments are deduped against the post-strip text, so an app post whose block mirrors a flat text containing the bot token is appended twice on the default too.

… visibility

- Introduced `strip_bot_mentions` setting to allow toggling bot mention visibility in agent-readable Slack messages.
- Default behavior (`true`) continues to remove the bot's own mention (`<@U_ID>`) before passing text to the agent.
- Setting it to `false` retains the mention, rendered as `@BotName`, enabling distinction between explicit tags and thread-based wake-ups.
- Extended `config.yaml` with `strip_bot_mentions` and environmental support (`SLACK_STR
@nikitaBarkov

Copy link
Copy Markdown
Author

HERMES pr: NousResearch#83905

@nikitaBarkov
nikitaBarkov merged commit b7e0960 into main Aug 11, 2026
42 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant