Skip to content

[Feature]: Matrix thread session isolation (per-thread session keys) #29729

@rube-de

Description

@rube-de

Summary

Matrix threads currently share the parent room's session. All messages in a room, whether room-level or inside threads, route to the same session key (agent:<agentId>:matrix:room:<roomId>). Threads should get their own isolated session keys, like Slack and Telegram already do.

Problem to solve

The Matrix plugin treats threads as reply formatting only (threadReplies config controls where the bot's message appears in the UI). There is no session-level awareness of threads. The docs confirm this:

Routing model: DMs share the agent's main session; rooms map to group sessions.

No thread-level session isolation exists. This means:

  • Context pollution: parallel conversations in different threads within the same room all land in a single session. The agent sees an interleaved mess of unrelated topics.
  • No focused conversations: starting a thread to discuss a specific task doesn't give the agent a clean slate. It carries the full room history, including unrelated threads.
  • No parallel workstreams: users who organize work by thread (e.g., one thread per task, per project, per debug session) get no isolation benefit. The agent conflates all threads.
  • Parity gap: Slack and Telegram both have thread/topic session isolation. Matrix users get a worse experience despite Matrix having solid native thread support (spec v1.4+, m.thread relation).

Proposed solution

Derive thread-specific session keys when messages arrive inside a Matrix thread:

# Room-level message (unchanged)
agent:main:matrix:room:!abc123:example.org

# Thread message (new)
agent:main:matrix:room:!abc123:example.org:thread:$rootEventId

Where $rootEventId is the thread root event ID from the m.thread relation on the inbound event.

Config

Add channels.matrix.thread.* settings following Slack's established pattern:

{
  channels: {
    matrix: {
      thread: {
        historyScope: "thread",      // "thread" (per-thread, default) | "room" (shared, current behavior)
        inheritParent: false,         // copy parent room transcript to new thread sessions
        initialHistoryLimit: 20,      // fetch N existing thread messages when starting a new thread session
      },
    },
  },
}

Behavior

historyScope Effect
"thread" (default) Messages inside a Matrix thread route to an isolated session keyed by thread root event ID. Thread gets its own conversation history.
"room" Current behavior preserved , threads share the room session. Reply formatting still works via threadReplies.
  • inheritParent: true: when a thread session is first created, copy the parent room's recent transcript so the agent has context.
  • initialHistoryLimit: fetch existing thread messages when the agent joins a thread conversation mid-way.
  • Room-level messages (not in any thread) continue using the room session key unchanged.
  • Outbound messages from a thread session always reply in-thread (using m.thread + m.in_reply_to relations), regardless of the threadReplies setting, since the thread IS the session.

Implementation pointers

The Matrix plugin already distinguishes threaded vs room-level messages for inbound dispatch (#27401: sender identity preservation in threads). The m.thread relation on inbound events provides the thread root event ID.

  1. Inbound routing: check if the incoming event has an m.thread relation. If yes and historyScope: "thread", derive a thread-specific session key.
  2. Outbound routing: when replying from a thread session, always use m.thread relation + m.in_reply_to fallback.
  3. Session key format: append :thread:<rootEventId> to the room session key.
  4. E2EE: no crypto changes needed; this is purely a routing change.

Alternatives considered

1. Use separate rooms instead of threads
Each "conversation" gets its own Matrix room. Works for isolation but is heavy, room creation is slow, clutters the room list, and doesn't match how users naturally organize work in a single room with threads.

2. Prompt-prefix workaround ("focus on this thread only")
Instruct the agent via system prompt to ignore messages from other threads. Unreliable, the agent still sees the full interleaved history, burns tokens on irrelevant context, and can't reliably filter.

3. Manual /new per thread
User resets the session when switching threads. Loses all context from the current thread and is error-prone. Doesn't work for parallel threads at all since there's only one room session.

4. Use Discord/Slack instead of Matrix for thread workflows
Defeats the purpose of running Matrix for self-hosted/privacy-first setups. Matrix's thread support is mature enough — the gap is in the OpenClaw plugin, not the protocol.

Impact

Affected users/systems/channels:
All Matrix plugin users who use threads. Matrix is a plugin channel but targets a specific audience (self-hosted, privacy-focused, E2EE) that often runs OpenClaw on their own infrastructure and relies heavily on room organization via threads.

Severity:
Blocks workflow. Users who organize work by thread (common in Element) get no session isolation benefit. Every thread conversation pollutes the shared room session, making focused multi-topic work in a single room impractical.

Frequency:
Always. Every threaded message in Matrix hits this , there is no workaround that provides real thread isolation.

Consequence:

  • Context pollution across threads leads to confused agent responses
  • Token waste from irrelevant cross-thread history in every turn
  • No ability to run parallel workstreams per thread
  • This is also a prerequisite for ACP thread-bound agents on Matrix (feat: ACP thread-bound agents #23580), which requires thread session binding — impossible without thread session keys

Evidence/examples

Slack (thread session isolation, shipped):

  • Session key: agent:<agentId>:slack:channel:<channelId>:thread:<threadTs>
  • Config: channels.slack.thread.historyScope (default: thread), thread.inheritParent, thread.initialHistoryLimit
  • Docs: https://docs.openclaw.ai/channels/slack

Telegram (topic session isolation, shipped):

  • Session key: agent:<agentId>:telegram:group:<chatId>:topic:<threadId>
  • Per-topic config overrides for groupPolicy, requireMention, skills, systemPrompt
  • DM message_thread_id also handled with thread-aware session keys
  • Docs: https://docs.openclaw.ai/channels/telegram

Discord (thread isolation + ACP thread binding, shipped):

Matrix protocol support

Matrix threads (m.thread relation) are stable since spec v1.4 (MSC3440). Element and other modern clients fully support threaded views. The protocol provides everything needed — the thread root event ID is available on every threaded event and is stable/unique per thread.

Existing Matrix plugin thread awareness

The Matrix plugin already distinguishes threaded vs room-level messages:

The inbound dispatch layer knows about threads — it just doesn't use that information for session routing.

Additional information

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions