feat(gateway): optional speaker-name prefix on every stored message - #82552
Open
withzombies wants to merge 1 commit into
Open
feat(gateway): optional speaker-name prefix on every stored message#82552withzombies wants to merge 1 commit into
withzombies wants to merge 1 commit into
Conversation
The [Name] sender prefix is currently applied only in shared multi-user sessions, where it disambiguates participants. DM messages are stored bare, so a DM transcript carries no durable record of who was speaking — any per-turn identity context is ephemeral, and DM vs group transcripts end up structurally different. Add an opt-in knob that extends the prefix to every stored inbound message: - gateway/run.py: _always_prefix_speaker() reads HERMES_ALWAYS_NAME_PREFIX (truthy: 1/true/yes/on, case-insensitive) and widens the sender-prefix guard in _prepare_inbound_message_text. The prefix reuses the existing neutralize_untrusted_inline_text path, so hostile display names are handled identically to shared sessions. - Config bridge: gateway.always_name_prefix in config.yaml is bridged config-authoritatively to HERMES_ALWAYS_NAME_PREFIX (same convention as gateway.trust_recent_files, normalized to "1"/"0"). - cli-config.yaml.example + environment-variables.md document the knob. - Tests: helper truthiness table, DM prefix on/off through the real _prepare_inbound_message_text, hostile-name neutralization, shared- session behavior unchanged, and config-bridge authority cases. Default behavior is byte-identical: with the env var unset, only shared multi-user sessions get the prefix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds an opt-in knob —
gateway.always_name_prefixinconfig.yaml/ env varHERMES_ALWAYS_NAME_PREFIX— that extends the[Name]sender prefix toevery stored inbound message, not just messages in shared multi-user
sessions.
Today the prefix in
GatewayRunner._prepare_inbound_message_textfires onlywhen
is_shared_multi_user_session(source)is True, where it disambiguatesparticipants. In a DM there is only one human, so the prefix is omitted — but
that makes DM and group transcripts structurally different, and a DM transcript
carries no durable record of who was speaking at all: any per-turn identity
context (channel banners, system-prompt notes) is ephemeral to the current
turn, while the stored message text is what survives in session history,
compaction, and
session_search. Operators running one agent across several1:1 channels (e.g. an assistant deployment fielding DMs from multiple people
on multiple platforms) have no way to keep attribution in history.
With the knob enabled every stored message carries the same
[<display name>] <text>shape already used in shared sessions, reusing theexact same code path — including
neutralize_untrusted_inline_text, so hostiledisplay names get the identical hardening. Default behavior is byte-identical
to today: env var unset ⇒ only shared multi-user sessions get the prefix.
Related Issue
No existing issue. Duplicate search performed (see checklist notes below);
closest existing work:
changes the default for group/thread messages under
group_sessions_per_user=True. This PR is complementary, not a duplicate:it is strictly opt-in and also covers DMs (
chat_type="dm"), which fix(gateway): always prefix non-DM messages with sender name #80295deliberately leaves unprefixed. If fix(gateway): always prefix non-DM messages with sender name #80295 lands, this knob still adds the
DM case; the guard composes (
or) cleanly.Label unnamed shared-session participants #44705 (unnamed participants) — adjacent to the same code path, different
concerns.
Fixes # (none — feature)
Type of Change
Changes Made
gateway/run.py_always_prefix_speaker()(next to the otherenv readers, after
_float_env): readsHERMES_ALWAYS_NAME_PREFIX,truthy values
1/true/yes/on(case-insensitive, whitespacetolerant). Read live so config reloads/tests take effect without
re-import.
_prepare_inbound_message_textwidened fromif _is_shared_multi_user and source.user_name:toif (_is_shared_multi_user or _always_prefix_speaker()) and source.user_name:.The body — including the
neutralize_untrusted_inline_texthardening andits security comment, and the Slack
<@U...>author-mention append — isuntouched.
gateway.always_name_prefixbridged toHERMES_ALWAYS_NAME_PREFIX, config-authoritative and normalized to"1"/"0"(same convention as the neighboringgateway.trust_recent_fileskey; unlikeplatform_connect_timeout,which is intentionally env-wins).
cli-config.yaml.example— commentedgateway:block documentingalways_name_prefix: false(placed aftergroup_sessions_per_user, therelated session-identity knob).
website/docs/reference/environment-variables.md—HERMES_ALWAYS_NAME_PREFIXrow in the gateway env table (next to
HERMES_GATEWAY_BUSY_ACK_ENABLED).tests/gateway/test_always_name_prefix.py(new)1/true/yes/on(any case) on;empty/
0/false/no/off/garbage off._prepare_inbound_message_text(same harnessas
tests/gateway/test_shared_group_sender_prefix.py): a Telegram DM isstored bare with the env unset and as
[Alice] hello therewith it set;a hostile display name with embedded newlines is neutralized on the
opt-in path; a shared Slack thread keeps its existing
[Alice | Slack user <@U123>]prefix regardless of the knob.tests/gateway/test_config_env_bridge_authority.pyHERMES_ALWAYS_NAME_PREFIXadded to the subprocess env-key probe list.trueoverrides a stale.env0→"1"; explicit configfalseoverrides a stale.env1→"0".How to Test
pytest tests/gateway/test_always_name_prefix.py tests/gateway/test_config_env_bridge_authority.py tests/gateway/test_shared_group_sender_prefix.py -q→ 31 passed.
inspect the stored session transcript (
~/.hermes/sessions/) — the usermessage is stored bare, exactly as before.
~/.hermes/config.yaml:[<your display name>] <text>, matching the shape group messages alreadyhave. (Or set
HERMES_ALWAYS_NAME_PREFIX=1in.env; config wins if bothare set.)
already prefixed).
Checklist
Code
feat(gateway): ...)gh search prs --repo NousResearch/hermes-agent "user name prefix","speaker","sender prefix","always name prefix", plus the samevia
gh search issues. Closest matches (fix(gateway): always prefix non-DM messages with sender name #80295/fix(gateway): always prefix non-DM messages with sender name #80235, fix(gateway): keep shared-group sender prefix outermost #67886, fix(gateway): skip sender prefix for internal events + NO_REPLY contract in async delegation (#66480) #66507,Label unnamed shared-session participants #44705, issues [Bug] Telegram Group Chat: Agent cannot see sender names / user identity #26581/[Gateway/Telegram] user.id is captured but not included in shared multi-user message prefix #32417/[Feature]: Expose user_id alongside user_name in single-user session context #35147) are adjacent, not duplicates —
none adds an opt-in prefix for DMs (see Related Issue).
pytest tests/ -qand all tests pass (targeted suites above,plus neighbors of the touched guard:
tests/gateway/test_session.py,test_reply_to_injection.py,test_context_ref_expansion_runtime.py— 65 passed)Documentation & Housekeeping
website/docs/reference/environment-variables.md)cli-config.yaml.example(newgateway.always_name_prefixkey)CONTRIBUTING.mdorAGENTS.md— N/A (no architecture/workflow change)Screenshots / Logs
🤖 Generated with Claude Code