Skip to content

fix(mattermost): make thread routing configurable and reliable - #20874

Open
potatosalad wants to merge 5 commits into
NousResearch:mainfrom
potatosalad:fix/mattermost-thread-aware
Open

fix(mattermost): make thread routing configurable and reliable#20874
potatosalad wants to merge 5 commits into
NousResearch:mainfrom
potatosalad:fix/mattermost-thread-aware

Conversation

@potatosalad

@potatosalad potatosalad commented May 6, 2026

Copy link
Copy Markdown

What does this PR do?

Rebases the remaining Mattermost thread work onto current main, drops behavior that has already landed upstream, and makes automatic threading explicit for channels and direct messages.

Current main already contains the original PR's progress/media metadata plumbing and top-level channel root behavior. Those duplicate commits are no longer part of this branch.

Remaining changes

  • Keep typing indicators inside the active Mattermost thread.

  • Cache successful post-to-root lookups while allowing transient lookup failures to retry.

  • Prefer trusted gateway thread metadata and preserve real Mattermost thread roots.

  • Add independent settings for automatic thread creation:

    mattermost:
      auto_thread: true
      dm_auto_thread: false
  • Add MATTERMOST_AUTO_THREAD and MATTERMOST_DM_AUTO_THREAD overrides.

  • Deprecate MATTERMOST_REPLY_MODE as an environment compatibility fallback. When neither new setting is present, legacy thread enables both policies; either new setting can override that fallback independently.

  • Preserve explicit channel and DM thread roots regardless of the automatic-thread settings.

  • Resolve and cache the Mattermost channel type for uncached outbound targets, including cron/notification paths.

  • Retain the existing silent no-op for missing local files; no local path is posted to Mattermost.

Behavior

Incoming message Result
Existing channel/group thread Always preserve its root and session
Existing DM thread Always preserve its root and session
Top-level channel/group Auto-thread only when auto_thread: true
Top-level DM Auto-thread only when dm_auto_thread: true

The automatic-thread settings affect synthetic roots only. They never flatten a real existing thread.

Cron scope

This PR makes uncached outbound Mattermost targets classify correctly and preserves a real thread when a user replies there. Seeding the content of a proactively delivered cron brief into a newly created Mattermost thread session is cross-cutting scheduler/session work and remains separate from this adapter-focused PR.

Related work

Covered or consolidated here

Already upstream and deliberately omitted from this branch

Related follow-ups not claimed by this PR

Validation

  • scripts/run_tests.sh tests/gateway/test_mattermost.py tests/gateway/test_send_multiple_images.py -q — 60 passed
  • scripts/run_tests.sh tests/gateway/test_config.py tests/gateway/test_platform_registry.py -q — 90 passed
  • ruff check on changed Python files
  • python3 -m py_compile on changed Python files
  • git diff --check

Rebased onto current origin/main (a3bcb2c23). All five commits in the PR range are GPG-signed. Contributor commit c84653107 retains its original authorship beneath signed merge commit 9237a811c.

Screenshot

image

@nickaknudson

Copy link
Copy Markdown

I have been using this on top of v0.13 for several days with no issues; would be happy to see it merged

@BrianMcBrayer

Copy link
Copy Markdown

Agreed. We need this to make Mattermost useful. Otherwise I cannot use this integration at all. I had coded up a fix but this PR is much better than mine!

@potatosalad

Copy link
Copy Markdown
Author

@teknium1 Did the changes that were manually merged address everything that this pull request addresses or should I rebase and resolve merge conflicts?

@legard

legard commented May 29, 2026

Copy link
Copy Markdown

Hey @potatosalad — I run Hermes against a Mattermost server and this one drives me up the wall, so I went digging to answer the question you asked @teknium1 (whether the manual merge already covered this).

From what I can tell, it didn't — not fully. What landed (06161c6ed plus the move to the bundled plugin) fixes replies inside an existing thread and the Invalid RootId error. But the first-turn case still looks broken: on a fresh top-level @mention the adapter sets thread_id = post.get("root_id") or None, so there's no root to hang anything on and the bot's progress/tool output still spills into the main channel — the original #4221 symptom. So this PR is still addressing something main doesn't.

The other thing worth knowing: the reason it won't rebase cleanly isn't that it's obsolete — the adapter got moved out from under it. gateway/platforms/mattermost.py is now plugins/platforms/mattermost/adapter.py (rename in af973e407), so the diff is now pointing at a file that no longer exists.

Figured that was worth flagging since you'd asked whether it was already covered.

@potatosalad

Copy link
Copy Markdown
Author

@legard Ah cool, I'll get it rebased soon then.

@legard

legard commented Jun 5, 2026

Copy link
Copy Markdown

Hi @teknium1, when you get a chance, could you review this? It's an important issue for corporate users relying on the agent, and I'd really value your call on whether it's something you'd consider merging.

@aaron-trout

aaron-trout commented Jun 28, 2026

Copy link
Copy Markdown

I am new to Hermes and trying out MatterMost as the chat interface. An issue I've seen is that when you start a thread Hermes can lose context of earlier messages. For example I asked it to write a skill about my kubernetes setup; ~30 mins later when I asked for a follow up modification in the same thread, it had no idea what I was talking about:

❓ What are we trying to connect to? Is it:

Your desktop via computer_use / cua-driver?
A specific service or application?
Something else entirely?
desktop via computer_use
specific service/app
something else
Reply with the number, the option text, or your own answer.

Would this fix also resolve that issue? 🤔 I can try to test out this PR on my instance perhaps...

@alt-glitch alt-glitch added the sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages label Jun 28, 2026
@potatosalad

Copy link
Copy Markdown
Author

Would this fix also resolve that issue? 🤔 I can try to test out this PR on my instance perhaps...

Yes, that's one of the issues that this PR fixes for me, too.

@cantrellsolutions

Copy link
Copy Markdown

I'm concerned with flattening DMs in thread mode. If I send a DM and the mode is thread, I expect the reply back to be in a thread from that DM, and I’d expect replies in that DM thread to keep the same Hermes context.

I'm 100% in agreement with the rest of this. I just don’t think DM threads should all collapse into one flat DM session when thread mode is enabled. For workflows where bots send multiple separate task/status messages by DM, those threads need to stay scoped independently or the contexts bleed together.

Cron probably needs to be looked at as well. If a cron message is delivered automatically and I reply to it in a thread, the Hermes agent should know the context I’m replying to instead of me having to explain what the thread is about.

@alt-glitch alt-glitch removed the comp/gateway Gateway runner, session dispatch, delivery label Jul 6, 2026
@imapotato123

Copy link
Copy Markdown

Current-state check against current main (9da6d455c): this PR is still needed, and the narrow metadata-precedence fix remains live in the branch.

  • Head 2b02df8e0 contains the metadata-root resolution work from fix(mattermost): resolve metadata thread root before use potatosalad/hermes-agent#1 / 378ffd319.
  • send() now prefers authoritative metadata["thread_id"] over a stale reply_to, resolves reply IDs to their real root, and has direct regression coverage.
  • tests/gateway/test_mattermost.py + test_mattermost_plugin_setup.py: 52 passed locally on the PR head.
  • A merge-tree against current main finds only one content conflict, in tests/gateway/test_mattermost.py; the adapter and config changes auto-merge.

@potatosalad could you rebase once more and resolve that test-file conflict? I don't think this should be closed as obsolete: current main still starts _thread_root_for_send() from reply_to and consults metadata only when it is absent, so conflicting stale reply_to can still win upstream.

@alt-glitch alt-glitch added comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins and removed comp/gateway Gateway runner, session dispatch, delivery needs-decision Awaiting maintainer decision before any implementation comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 12, 2026
potatosalad and others added 5 commits August 12, 2026 06:53
_thread_root_for_send() returns metadata["thread_id"] directly as the
Mattermost root_id. Mattermost requires root_id to be the root of a
thread and rejects a reply's ID with "Invalid RootId parameter", and a
recorded thread_id is not guaranteed to be a root — delayed deliveries
and synthesized metadata can carry a reply's ID.

Resolve it through _resolve_root_id() the same way the reply_to branch
below already does. A failed lookup returns None and may be transient,
so fall back to the recorded ID rather than dropping the thread.

This trades the metadata path's no-lookup fast path for one GET per
thread — _thread_root_cache absorbs every subsequent message, so the
cost is per thread, not per message.

test_thread_metadata_avoids_redundant_root_lookup pinned the old fast
path and is replaced by a cache-behavior test asserting exactly one
lookup across two sends. test_explicit_dm_thread_metadata_is_always
_preserved kept its intent; its bare AsyncMock() only worked while the
metadata path skipped the lookup, so it now returns real post data.
fix(mattermost): resolve metadata thread root before use
@potatosalad
potatosalad force-pushed the fix/mattermost-thread-aware branch from 2b02df8 to 13bebda Compare August 12, 2026 11:56
@alt-glitch alt-glitch added the comp/cli CLI entry point, hermes_cli/, setup wizard label Aug 12, 2026
@potatosalad

Copy link
Copy Markdown
Author

@teknium1 This has been open for over three months. Is it still on track to merge, or are there remaining changes needed?

@cantrellsolutions

Copy link
Copy Markdown

I took another pass over this from the perspective of our Mattermost-based Hermes orgs.

This PR covers the Mattermost behavior we have been carrying a small local patch for: task-rooted Mattermost sessions where top-level DMs can become their own Hermes session roots and replies under that Mattermost root reuse the same Hermes session. With mattermost.auto_thread: true and mattermost.dm_auto_thread: true, this would let us drop that local adapter overlay and move to upstream behavior.

The explicit split between channel and DM auto-threading is useful — for our operator/agent UX we want both enabled, but I can see why upstream defaults may stay conservative. The metadata-root precedence/resolution and preserving real existing roots are also important for avoiding the “same visible Mattermost thread, different Hermes session” failure mode.

So from our usage: this PR is material and would replace downstream patching for better Mattermost support, assuming the new settings are configured explicitly.

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/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants