Skip to content

Feature: Per-Topic Role/Skill Binding for Telegram Group Forum Topics #4622

@Akah-dev

Description

@Akah-dev

Summary

Add native support for per-topic skill and personality binding in Telegram group forum (supergroup) chats. This allows different forum topics (threads) within the same group to automatically load different skills, effectively giving the bot different "roles" depending on which topic the user is messaging in.

Motivation

In Telegram groups with forums enabled, each topic serves a distinct purpose (e.g., "Deep Research", "Coding Help", "General Chat"). Currently, Hermes applies the same persona/skill set regardless of which topic a message comes from. This means users cannot get specialized behavior per topic without creating separate bot instances.

Use case: A single bot in a community group where:

  • Thread "Deep Research" → loads deep-research skill (thorough analysis, citations)
  • Thread "Code Review" → loads code-review skill (security-focused, line-by-line review)
  • Thread "General" → default behavior (casual chat)

Proposed Configuration

Add a group_topics section under platforms.telegram.extra in config.yaml:

platforms:
  telegram:
    extra:
      group_topics:
        - chat_id: -100XXXXXXXXXX  # Your group/supergroup ID
          topics:
            - thread_id: 42
              name: "Deep Research"
              skill: "deep-research"
            - thread_id: 19
              name: "Code Review"
              skill: "code-review"
            - thread_id: 7
              name: "General"
              # no skill = default behavior

Each topic entry supports:

  • thread_id (required) — The Telegram message_thread_id for the forum topic
  • name (optional) — Human-readable topic name, surfaced as chat_topic in the session context
  • skill (optional) — Skill name to auto-load when a message arrives from this topic

Implementation Details

1. Config Loading (__init__)

# In TelegramAdapter.__init__
self._group_topics_config: List[Dict[str, Any]] = self.config.extra.get("group_topics", [])

Store the config list at startup for lookup.

2. Topic Lookup Method (_get_group_topic_info)

def _get_group_topic_info(self, chat_id: str, thread_id: Optional[str]) -> Optional[Dict[str, Any]]:
    """Look up group forum topic config by chat_id and thread_id."""
    if not thread_id or not self._group_topics_config:
        return None
    thread_id_int = int(thread_id)
    for chat_entry in self._group_topics_config:
        if str(chat_entry.get("chat_id")) == chat_id:
            for t in chat_entry.get("topics", []):
                if t.get("thread_id") == thread_id_int:
                    return t
    return None

3. Hot-Reload (_reload_group_topics_from_config)

Re-read config.yaml at runtime so new topics can be added without restarting the gateway:

def _reload_group_topics_from_config(self) -> None:
    """Re-read group_topics from config.yaml (hot-reload without restart)."""
    try:
        from hermes_constants import get_hermes_home
        config_path = get_hermes_home() / "config.yaml"
        if not config_path.exists():
            return
        import yaml as _yaml
        with open(config_path, "r") as f:
            config = _yaml.safe_load(f) or {}
        group_topics = (
            config.get("platforms", {})
            .get("telegram", {})
            .get("extra", {})
            .get("group_topics", [])
        )
        if group_topics:
            self._group_topics_config = group_topics
    except Exception as e:
        logger.debug("[%s] Failed to reload group_topics from config: %s", self.name, e)

4. Integration in _build_message_event

Add a branch for chat_type == "group" after the existing DM topic resolution:

# Resolve GROUP forum topic name and skill binding
elif chat_type == "group" and thread_id_str:
    group_topic_info = self._get_group_topic_info(str(chat.id), thread_id_str)
    if group_topic_info:
        chat_topic = group_topic_info.get("name")
        topic_skill = group_topic_info.get("skill")
    else:
        # Hot-reload config in case topics were added after startup
        self._reload_group_topics_from_config()
        group_topic_info = self._get_group_topic_info(str(chat.id), thread_id_str)
        if group_topic_info:
            chat_topic = group_topic_info.get("name")
            topic_skill = group_topic_info.get("skill")

Then pass topic_skill as auto_skill in the returned MessageEvent:

return MessageEvent(
    ...
    auto_skill=topic_skill,
    ...
)

How It Works (Flow)

1. User sends message in group forum topic (thread_id=42)
2. _build_message_event() is called
3. Detects: chat_type="group" + thread_id present
4. Calls _get_group_topic_info(chat_id, thread_id)
5. Finds matching config → {name: "Deep Research", skill: "deep-research"}
6. Sets auto_skill="deep-research" on MessageEvent
7. Gateway sees auto_skill → auto-loads the skill before responding
8. Bot responds with the specialized persona

Benefits

  • Single bot, multiple roles — no need for separate bot instances per function
  • Hot-reloadable — add/remove topics without restarting the gateway
  • Zero breaking changes — fully backward compatible; groups without group_topics config work exactly as before
  • Consistent with existing DM topics — mirrors the existing dm_topics feature pattern (already in Hermes) for private chat topics
  • Config-driven — no code changes needed to add new topic bindings

Relation to Existing Features

This mirrors the existing DM Topics feature (private chat forum topics via dm_topics config) but applies to group/supergroup forum topics. The implementation pattern is identical, just using a different config key (group_topics) and lookup path.

Testing Checklist

  • Messages in configured topic trigger correct skill loading
  • Messages in unconfigured topics use default behavior
  • Hot-reload works: add new topic to config.yaml, send message → skill loads without restart
  • Multiple groups with different topic configs work independently
  • chat_topic name is surfaced correctly in session context
  • Backward compatible: no config = no change in behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to haveplatform/telegramTelegram bot adaptersweeper:implemented-on-mainSweeper: behavior already present on current maintype/featureNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions