Skip to content

feat(matrix): make per-message read receipts and lifecycle reactions configurable - #96302

Closed
ZoloZiak wants to merge 1 commit into
NousResearch:mainfrom
ZoloZiak:pr-matrix-read-receipts
Closed

ZoloZiak wants to merge 1 commit into
NousResearch:mainfrom
ZoloZiak:pr-matrix-read-receipts

Conversation

@ZoloZiak

@ZoloZiak ZoloZiak commented Aug 27, 2026 •

Copy link
Copy Markdown

Summary

The Matrix adapter automatically annotates every incoming message the moment the gateway processes it, in two independent ways, and neither could be configured from config.yaml:

  1. Read receipts — an m.read receipt / fully-read marker is sent on arrival (_resolve_message_context).
  2. Lifecycle reactions — an eyes reaction (👀) is added when the agent starts a turn and a checkmark/cross (✅/❌) when it finishes (on_processing_start / on_processing_complete).

This is especially disruptive on Beeper: both signals propagate through the bridge to the underlying network (Messenger / Instagram / WhatsApp / …). So every bridged conversation gets marked read and stamped with a visible checkmark the instant the bot ingests a message — regardless of whether the human operator has actually seen or replied to it. For a bot that passively watches many rooms, this silently annotates everything, everywhere.

This PR makes both behaviors configurable under the matrix config namespace, per the repository policy that non-secret behavioral configuration belongs in config.yaml rather than an environment variable.

matrix.read_receipts (three modes)

Mode Behavior
immediate (default) Mark read on arrival — historical behavior, no change for existing deployments
after_processing Mark read only once the agent finishes the turn, so the receipt reflects an actual reply rather than mere ingestion
disabled Never send read receipts

after_processing defers the receipt to on_processing_complete, independent of the reaction lifecycle and skipped on cancellation. The legacy MATRIX_READ_RECEIPTS env var is honored as a fallback; the YAML value wins when both are set. Legacy boolean spellings map truthy → immediate, falsy → disabled.

This supersedes the read-receipt portion of the closed #4441, which added MATRIX_READ_RECEIPTS as an environment variable. That approach was declined in review because the config belongs in config.yaml; this PR implements it there, on the current mautrix adapter, with coverage for all modes.

matrix.reactions (boolean, default true)

Promotes the previously env-only MATRIX_REACTIONS toggle to a config.yaml boolean. true (default) keeps the historical eyes/checkmark lifecycle; false disables both the 👀 on start and the ✅/❌ on completion. The legacy MATRIX_REACTIONS env var remains a fallback, with YAML winning.

Both settings are bridged through the existing _apply_yaml_config hook (matrix: YAML → MATRIX_* env, env taking precedence), the same mechanism already used by auto_thread, session_scope, etc.

Files changed

File Change
plugins/platforms/matrix/adapter.py _parse_read_receipts_mode + _parse_reactions_enabled helpers; gate immediate receipt; deferred receipt in on_processing_complete; matrix.read_receipts / matrix.reactions → env bridges in _apply_yaml_config
hermes_cli/config_defaults.py Register matrix.read_receipts (immediate) and matrix.reactions (true) defaults
hermes_cli/config.py Keep MATRIX_READ_RECEIPTS / MATRIX_REACTIONS in the known-env allowlist so doctor doesn't flag the internal bridge vars
tests/gateway/test_matrix.py 23 regression tests

Tests

23 new tests, all passing (TestMatrixReadReceiptsMode ×13, TestMatrixReactionsToggle ×10):

  • Read receipts — parser: default, explicit modes, case/whitespace tolerance, legacy booleans, unrecognized-value fallback, env fallback, YAML-wins-over-env; behavior: immediate sends on arrival, disabled never sends, after_processing defers and sends on success but is skipped on cancellation, immediate does not double-send at completion.
  • Reactions — parser: default enabled, explicit bool, string spellings (off/no/0/…), case/whitespace tolerance, env fallback, YAML-wins-over-env; behavior: eyes on start / checkmark on success only when enabled, nothing when disabled.

(The Matrix backend has no pytest in the venv here; tests were run via a standalone runner importing the classes and executing sync + async methods — 23 passed, 0 failed.)

Compatibility

  • Defaults reproduce current behavior exactly (read_receipts: immediate, reactions: true) — no change for existing deployments.
  • Env vars and legacy boolean forms still work.
  • Matrix-specific; no cross-platform impact.

@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins platform/matrix Matrix adapter (E2EE) area/config Config system, migrations, profiles sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades P3 Low — cosmetic, nice to have labels Aug 27, 2026
@ZoloZiak ZoloZiak changed the title feat(matrix): make read receipts configurable (immediate/after_processing/disabled) feat(matrix): make per-message read receipts and lifecycle reactions configurable Aug 27, 2026
Two related config knobs for the Matrix adapter, both defaulting to the
current behavior so upgrades are no-ops:

matrix.read_receipts (immediate | after_processing | disabled)
  Controls when an m.read receipt is sent for an inbound message.
  "immediate" (default) marks read on arrival; "after_processing" defers
  the receipt until the agent finishes the turn (skipped on cancel);
  "disabled" never sends one. On Beeper the receipt propagates to every
  bridged network, so this lets you stop bridged chats from showing
  messages as read the instant the gateway ingests them.

matrix.reactions (bool, default true)
  Toggles the lifecycle reactions (eyes on start, checkmark/cross on
  completion). Set false to stop annotating every processed message.

Both read from config.yaml and honor the legacy MATRIX_READ_RECEIPTS /
MATRIX_REACTIONS env vars as a fallback (YAML wins when both are set).
Mode parsing is back-compatible with the earlier boolean spelling.

Adds parser helpers (_parse_read_receipts_mode, _parse_reactions_enabled)
with regression tests covering YAML/env precedence, legacy spellings,
whitespace/case handling, and the three receipt behaviors on
arrival/completion/cancel.
@ZoloZiak

Copy link
Copy Markdown
Author

Closing as superseded by #126283.

That PR reimplements this exact feature — per-profile matrix.read_receipts (immediate / after_processing / disabled) and the matrix.reactions toggle, same YAML keys, same env-var back-compat, same matrix.md docs — as part of a much larger Matrix lifecycle refactor (deferred-input handling, i18n, live integration tests). It's a strict superset of what's here, so this standalone PR adds nothing on top.

Left a note on #126283 in case the config slice is ever wanted as a smaller standalone fallback.

@ZoloZiak ZoloZiak closed this Sep 30, 2026
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 platform/matrix Matrix adapter (E2EE) 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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants