Skip to content

feat(mattermost): isolate DM sessions per top-level message (Slack parity) - #80530

Open
weslien wants to merge 1 commit into
NousResearch:mainfrom
weslien:feat/mattermost-dm-session-isolation
Open

feat(mattermost): isolate DM sessions per top-level message (Slack parity)#80530
weslien wants to merge 1 commit into
NousResearch:mainfrom
weslien:feat/mattermost-dm-session-isolation

Conversation

@weslien

@weslien weslien commented Aug 6, 2026

Copy link
Copy Markdown

What does this PR do?

Ports Slack's dm_top_level_threads_as_sessions session isolation to the Mattermost adapter. Each top-level DM message now gets its own session (stamped with its own post_id as thread_id), while threaded replies continue the root's session — matching the behavior Slack has had since dm_top_level_threads_as_sessions was introduced there.

Problem: Mattermost top-level DM messages shared one continuous session. The channel stamping fix (#18279) explicitly excluded DMs (channel_type_raw != "D"), so every top-level DM in the same channel collapsed into session_key = platform:dm:{chat_id} — no thread suffix. This blocks parallel DM conversations and mixes unrelated topics into one context window.

Fix: Stamp top-level DM messages with their own post_id as thread_id, gated by a new _dm_top_level_threads_as_sessions() config method that mirrors Slack's exactly.

Related Issue

Closes #18279 (the DM portion — the channel portion was already fixed; this completes the parity).

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • plugins/platforms/mattermost/adapter.py:

    • New method _dm_top_level_threads_as_sessions() — reads platforms.mattermost.extra.dm_top_level_threads_as_sessions, defaults to true (mirrors Slack's default)
    • New DM stamping block: top-level DM messages (channel_type == "D", no root_id) get thread_id = post_id when the feature is enabled
    • Channel stamping logic unchanged — only the DM exclusion from the existing block is preserved (it's now handled by the separate DM block)
  • tests/gateway/test_mattermost.py:

    • test_dm_top_level_post_gets_own_thread_id_by_default — top-level DM gets its own thread_id
    • test_dm_reply_in_thread_keeps_root_id — threaded reply keeps root_id (continues session)
    • test_dm_top_level_disabled_when_config_false — config false → no stamping (legacy behavior)
    • test_dm_root_and_reply_share_session_key — end-to-end: root + reply produce same build_session_key()

How to Test

pytest tests/gateway/test_mattermost.py -v

All 27 tests pass (23 existing + 4 new).

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (feat(mattermost): ...)
  • I searched for existing PRs to make sure this isn't a duplicate — fix(mattermost): keep thread root and replies in one session #37144 addresses thread root/reply continuity but does not add DM session isolation or a config gate; this PR is complementary
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/gateway/test_mattermost.py -v and all tests pass
  • I've added tests for my changes
  • I've tested on my platform: macOS 15.5, Python 3.11

Documentation & Housekeeping

  • I've updated relevant documentation — docstrings added inline (matching Slack's pattern; dm_top_level_threads_as_sessions is an extra key not documented in cli-config.yaml.example, consistent with Slack's treatment)
  • I've updated cli-config.yaml.example — N/A (extra key, matches Slack convention)
  • I've updated CONTRIBUTING.md or AGENTS.md — N/A
  • I've considered cross-platform impact — N/A (platform-agnostic session-key logic)
  • I've updated tool descriptions/schemas — N/A

Design Notes

Why gate with a config option instead of always-on? Slack uses the same pattern — default true with an escape hatch. Some deployments may rely on the legacy single-session-per-DM behavior (e.g., a DM channel used as a persistent command surface). The config gate lets them revert without a code change.

Why independent of reply_mode? DM session isolation is orthogonal to reply placement. Whether the bot replies in threads or flat, each top-level DM message should still get its own session. The reply_mode flag controls outbound formatting; session keying is inbound routing.

Mattermost top-level DM messages shared one continuous session because the
adapter only stamped thread_id for non-DM channels (fix for NousResearch#18279). In DMs
the adapter left thread_id unset, so build_session_key() keyed every
top-level DM message by channel only — collapsing unrelated conversations
into one session and blocking parallel DM conversations.

Slack solved this with dm_top_level_threads_as_sessions (defaults true):
each top-level DM message is stamped with its own message ts as thread_id,
giving each root message its own session. Threaded replies keep the
thread's root_id and continue that session.

This ports the same behavior to Mattermost:

- New _dm_top_level_threads_as_sessions() method mirrors Slack's exactly
  (platforms.mattermost.extra.dm_top_level_threads_as_sessions, default true)
- Top-level DM messages (channel_type D, no root_id) get stamped with
  their own post_id as thread_id → unique session per message
- Threaded DM replies keep root_id → continue the root's session
- Configurable: set to false to revert to legacy single-session-per-DM

The channel stamping logic is unchanged — only the DM exclusion is lifted.
Independent of reply_mode (works in both thread and off modes), because DM
session isolation is orthogonal to reply placement.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 6, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #57357 and #20874 also change Mattermost DM/thread session semantics. This PR makes top-level DM isolation an opt-out Slack-parity default; maintainers should choose the intended policy across the cluster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mattermost root threads share one session and block parallel conversations

2 participants