Skip to content

fix(gateway): always prefix non-DM messages with sender name - #80235

Closed
Wintle wants to merge 1 commit into
NousResearch:mainfrom
Wintle:fix/sender-prefix-decoupled-from-session-isolation
Closed

fix(gateway): always prefix non-DM messages with sender name#80235
Wintle wants to merge 1 commit into
NousResearch:mainfrom
Wintle:fix/sender-prefix-decoupled-from-session-isolation

Conversation

@Wintle

@Wintle Wintle commented Aug 6, 2026

Copy link
Copy Markdown

Summary

The LLM never saw sender attribution on group / thread messages under the
default group_sessions_per_user=True config. The prefix code in
GatewayRunner._prepare_inbound_message_text only fired when
is_shared_multi_user_session(source) was True, and that predicate is
False by default — session keys isolate per-participant, so the LLM saw
plain text without any [user_name] prefix.

Decouple the two concerns

is_shared_multi_user_session describes session-key isolation (whether
multiple participants share the same conversation history). The sender
prefix describes message attribution (whether the LLM can tell who
sent the current message). Those are independent questions and conflating
them was the bug.

New predicate _should_prefix_sender:

source.chat_type != "dm" and bool(source.user_name)

Prefix every non-DM inbound (group + thread), regardless of
group_sessions_per_user / thread_sessions_per_user config. DMs keep
the existing 1:1 contract — no prefix.

Backward compatibility

  • _is_shared_multi_user is still computed at the top of the block (kept
    as a named local so any future consumer in the same scope that expects
    it does not break). It is no longer used for the prefix decision.
  • DM messages: identical behaviour to before (no prefix).
  • Group/thread with non-shared sessions: NEW — prefix now fires.
    The previous behaviour was a latent bug; if a downstream caller depended
    on the absence of a prefix on isolated group sessions it was depending
    on a bug.
  • source.user_name None / empty: no prefix (same as before).
  • Hostile display names containing newlines or control chars: still
    neutralized via neutralize_untrusted_inline_text before interpolation
    (same defence that already existed for shared sessions).

Tests

tests/gateway/test_sender_prefix_decoupling.py (new, 6 tests):

  • group message under default isolation gets prefix (the bug)
  • thread message gets prefix
  • DM message never gets prefix
  • user_name=None does not produce a prefix
  • hostile display names with newlines are neutralized
  • Slack channel adds the verifiable <@U...> user id next to the display
    name (existing behaviour preserved)

Verification

RED on origin/main (stashed source fix, kept tests):

$ git stash push -- gateway/run.py
$ ./venv/bin/python -m pytest tests/gateway/test_sender_prefix_decoupling.py
5 failed, 1 passed

GREEN after fix (popped stash):

$ git stash pop
$ ./venv/bin/python -m pytest tests/gateway/test_sender_prefix_decoupling.py
6 passed

Sibling regression (test_internal_event_never_interrupts_busy_session.py
and other gateway tests that exercise _prepare_inbound_message_text):
all green.

Files changed

  • gateway/run.py — new module-level helper _should_prefix_sender(source);
    _prepare_inbound_message_text uses it instead of the is_shared_multi_user
    gate. _is_shared_multi_user local kept for backward compat (still
    computed, no longer consulted for prefixing).
  • tests/gateway/test_sender_prefix_decoupling.py (new) — 6 invariant tests
    pinning the prefix-on-group / no-prefix-on-DM / neutralization contract.

Bot / duplicate check

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 6, 2026
@Wintle
Wintle force-pushed the fix/sender-prefix-decoupled-from-session-isolation branch from 116279f to 9ec378b Compare August 6, 2026 10:31
The LLM never saw a sender attribution on group/thread messages under
the default ``group_sessions_per_user=True`` config. The prefix code in
``GatewayRunner._prepare_inbound_message_text`` only fired when
``is_shared_multi_user_session(source)`` was True, and that predicate is
False by default — session keys isolate per-participant, so the LLM saw
plain text without any ``[user_name]`` prefix.

Decouple the two concerns
-------------------------

``is_shared_multi_user_session`` describes **session-key isolation**
(whether multiple participants share the same conversation history).
The sender prefix describes **message attribution** (whether the LLM
can tell who sent the current message). Those are independent questions
and conflating them was the bug.

New predicate ``_should_prefix_sender``::

    source.chat_type != "dm" and bool(source.user_name)

Prefix every non-DM inbound (group + thread), regardless of
``group_sessions_per_user`` / ``thread_sessions_per_user`` config.
DMs keep the existing 1:1 contract — no prefix.

Backward compatibility
----------------------

- ``_is_shared_multi_user`` is still computed at the top of the block
  (kept as a named local so any future consumer in the same scope that
  expects it does not break). It is no longer used for the prefix
  decision.
- DM messages: identical behaviour to before (no prefix).
- Group/thread with non-shared sessions: **NEW** — prefix now fires.
  The previous behaviour was a latent bug; if a downstream caller
  depended on the absence of a prefix on isolated group sessions it
  was depending on a bug.
- ``source.user_name`` None / empty: no prefix (same as before).
- Hostile display names containing newlines or control chars: still
  neutralized via ``neutralize_untrusted_inline_text`` before
  interpolation (same defence that already existed for shared sessions).

Tests
-----

``tests/gateway/test_sender_prefix_decoupling.py`` (new, 6 tests):

  - group message under default isolation gets prefix (the bug)
  - thread message gets prefix
  - DM message never gets prefix
  - ``user_name=None`` does not produce literal "[None]"
  - hostile name with embedded newline is neutralized
  - prefix is independent of session-isolation config

RED on origin/main: 3 failed, 3 passed
GREEN with this fix: 6 passed

Sibling matrix + session tests: all green.

Related
-------

  - NousResearch#80206 (PR 1): MessageEvent gains user_id/user_name fields.
  - NousResearch#80211 (PR 3): phantom intake filter that breaks the self-loop.
  - This PR (NousResearch#2): makes the prefix actually fire so the LLM can read
    the sender field PR NousResearch#1 added. Together these three form the
    minimal fix surface for the orphan-intake-notice issue.

Files changed
-------------

  - gateway/run.py: ``_prepare_inbound_message_text`` ~L15776 —
    replace ``if _is_shared_multi_user and source.user_name:`` with
    ``if _should_prefix_sender:`` plus block comment explaining the
    decoupling.
  - tests/gateway/test_sender_prefix_decoupling.py (new) — 6 tests,
    ~195 lines.
@Wintle
Wintle force-pushed the fix/sender-prefix-decoupled-from-session-isolation branch from 9ec378b to e80878a Compare August 6, 2026 10:33
@Wintle Wintle closed this Aug 6, 2026
@Wintle
Wintle deleted the fix/sender-prefix-decoupled-from-session-isolation branch August 6, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists 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.

3 participants