Skip to content

feat(gateway): config-driven per-chat channel_context injection - #43728

Open
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:feat/channel-context-map
Open

feat(gateway): config-driven per-chat channel_context injection#43728
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:feat/channel-context-map

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

Problem

The inbound pipeline already supports a per-event channel_context field — gateway/run.py prepends it to the message text when present. However, only platform adapters can populate that field. There is no way for a user/operator to attach deterministic context to a specific chat via configuration.

Use case: Running an external orchestrator where each monitored coding session has a dedicated chat. When a message arrives from one of those chats, the agent needs a deterministic hint ("this chat is bound to session X — forward commands via tool Y") so routing doesn't depend on the LLM guessing. Other shapes: per-chat workspace/project descriptions, per-chat language or persona notes, customer-specific context.

Today the only way to do this is patching the gateway source and re-applying the patch after every hermes update.

Solution

Add gateway.channel_context_map config option that maps chat_id strings to context text prepended to every inbound message from that chat.

Two config shapes supported:

# File path (recommended for dynamic setups)
gateway:
  channel_context_map: ~/.hermes/chat-context.json

# Inline dict (for static setups)
gateway:
  channel_context_map:
    "telegram:123456": "This chat is bound to dev-session A."
    "discord:789": "Customer X — always respond formally."

Key design decisions:

  • File path mode: JSON file loaded with mtime-based caching — zero hot-path cost when unchanged, external tools can update without gateway restart
  • Precedence: Adapter-set channel_context (backfill) takes precedence; config context is appended
  • Reuses existing injection point: No new message-format semantics — feeds the existing channel_context field in _prepare_inbound_message_text

Changes

  • hermes_cli/config.py: Add channel_context_map config key with default "" under gateway section
  • gateway/run.py: Add _load_channel_context_map() helper with mtime caching + wire into _prepare_inbound_message_text
  • tests/gateway/test_channel_context_map.py: 15 tests (10 unit + 5 integration)

Closes #43650

Add gateway.channel_context_map config option that maps chat_id strings
to context text prepended to every inbound message from that chat.

Supports two config shapes:
- File path (string): JSON file with mtime-based caching (reloaded when
  file changes, zero hot-path cost when unchanged)
- Inline dict: for static setups

The config context is appended after any adapter-set channel_context
(backfill), preserving the existing precedence model. Reuses the
existing channel_context injection point in _prepare_inbound_message_text.

Closes NousResearch#43650
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have labels Jun 10, 2026
@Lazymonter

Copy link
Copy Markdown
Contributor

Author of #43650 here — this implementation covers the exact use case that motivated the issue. Thanks for picking it up so quickly!

I'm running an external orchestrator (tmux session bridge) where each monitored coding session has a dedicated chat, and today the chat→session hint requires patching gateway/run.py and re-applying after every hermes update. I read through the diff and verified it fits as a drop-in replacement:

  • File-path mode with mtime caching is exactly what a dynamic setup needs — my bridge can rewrite the JSON map when sessions are added/removed, no gateway restart.
  • Reusing the existing channel_context injection point in _prepare_inbound_message_text (adapter context first, config context appended) keeps the semantics identical to what adapters already produce — no new message-format behavior to reason about.
  • The failure modes degrade safely (missing file / bad JSON / non-dict → {}), so a broken map file can't take down inbound processing.

One non-blocking nit: the cache comment says it's keyed by (file_path, mtime) but the tuple only stores the mtime — if the config is repointed at a different file with a coincidentally identical mtime, the stale map would be served. Edge case, fine to leave as-is or fold the path into the cache key.

Once this lands I'll switch my setup to channel_context_map and drop the local patch. 👍

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for targeting a real gateway configuration gap: current main still only prepends adapter-provided event.channel_context at gateway/run.py:10391-10395.

Problems

  • gateway/run.py:7535 looks up source.chat_id, but the PR examples use telegram:<id> / discord:<id>. BasePlatformAdapter.build_source() stores raw chat ids at gateway/platforms/base.py:5478-5481, so those documented keys will not match ordinary events.
  • The new cache stores only mtime, although its comment says it is keyed by path and mtime. Repointing the setting to a distinct file with the same mtime can return stale context; the #43650 author noted the same issue.
  • The tests patch the new loader, so they do not validate actual config resolution or the documented key form.

Suggested changes

  • Establish one canonical key format and test normal Telegram/Discord SessionSource values against it.
  • Include the resolved path and a precise file signature in the cache key.
  • Use the existing gateway runtime config loader and add a real temporary-HERMES_HOME integration test plus user documentation.

This is an automated hermes-sweeper review.

Comment thread gateway/run.py
# happens after sender-prefix so the prefix only applies to the
# trigger message, not the backfill block.
# Also inject per-chat context from the channel_context_map config
# (adapter context takes precedence; config context is appended).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lookup uses the raw source.chat_id, but the documented examples use telegram:<id> and discord:<id>. BasePlatformAdapter.build_source() stores the raw id separately from source.platform, so normal events will not match those examples. Please either form a platform-qualified lookup key here or revise the key contract and tests to use raw ids.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 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/gateway Gateway runner, session dispatch, delivery 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 type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Config-driven per-chat channel_context injection for inbound messages

4 participants