Problem
Adding a new messaging platform adapter requires touching 17+ files with manual boilerplate. Every platform (Telegram, Discord, Slack, WhatsApp, Signal, Mattermost, Matrix, DingTalk, Feishu, etc.) repeats the same pattern — enum values, factory branches, allowlist maps, toolset definitions, setup wizard entries, cron maps, send_message dispatch branches, and label strings scattered across the codebase.
This was highlighted during the Feishu salvage (PR #3817): the adapter itself is a single file, but wiring it in required surgical edits to 16 other files. Miss one spot and something silently breaks. The tool system already solved this exact problem with tools/registry.py — platform adapters should follow the same pattern.
Current State (per platform, 17 touchpoints)
| File |
What's added |
Why it exists |
gateway/config.py |
Platform.FEISHU = "feishu" enum |
Platform identity |
gateway/config.py |
_apply_env_overrides() block |
Env → config bridging |
gateway/config.py |
get_connected_platforms() check |
Connection validation |
gateway/run.py |
_create_adapter() factory branch |
Adapter instantiation |
gateway/run.py |
Allowlist env var map |
Auth gating |
gateway/run.py |
Allow-all env var map |
Auth gating |
toolsets.py |
hermes-feishu toolset definition |
Tool schema selection |
toolsets.py |
hermes-gateway includes list |
Gateway union toolset |
tools/send_message_tool.py |
Platform map entry |
Cross-platform send |
tools/send_message_tool.py |
Target ref parser |
ID format detection |
tools/send_message_tool.py |
Dispatch branch + send function |
Standalone send |
hermes_cli/config.py |
SENSITIVE_VARS entries |
Credential masking |
hermes_cli/gateway.py |
_PLATFORMS setup wizard entry |
Interactive setup |
hermes_cli/tools_config.py |
Platform label/toolset map |
hermes tools UI |
hermes_cli/skills_config.py |
Platform label map |
hermes skills UI |
cron/scheduler.py |
Platform map entry |
Cron delivery routing |
tools/cronjob_tools.py |
Description string |
Schema docs |
Plus 3+ test files and 3+ doc files.
Proposed: Platform Registry
A gateway/platforms/registry.py that mirrors the tool registry pattern. Each adapter self-registers with all its metadata at import time.
Adapter Registration
# gateway/platforms/feishu.py
from gateway.platforms.registry import platform_registry
platform_registry.register(
name="feishu",
label="Feishu / Lark",
emoji="🪽",
adapter_class=FeishuAdapter,
check_fn=check_feishu_requirements,
# Auth
env_vars={
"FEISHU_APP_ID": {"prompt": "App ID", "password": False, "required": True},
"FEISHU_APP_SECRET": {"prompt": "App Secret", "password": True, "required": True},
"FEISHU_DOMAIN": {"prompt": "Domain (feishu or lark)", "password": False},
"FEISHU_CONNECTION_MODE": {"prompt": "Connection mode", "password": False},
},
sensitive_vars=["FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_ENCRYPT_KEY"],
allowlist_var="FEISHU_ALLOWED_USERS",
allow_all_var="FEISHU_ALLOW_ALL_USERS",
# Toolset
tools=_HERMES_CORE_TOOLS, # or could just be "core" to mean the standard set
# Setup
setup_instructions=[
"1. Go to https://open.feishu.cn/ → Create app",
"2. Copy App ID and App Secret",
...
],
# Connection check (for gateway config)
is_connected=lambda config: bool(config.extra.get("app_id")),
# Env → config bridge
env_to_config=_apply_feishu_env, # function that reads env vars into PlatformConfig
# Send message support (for cross-platform send_message tool)
send_fn=_send_feishu, # async function for standalone sending
target_re=re.compile(r"..."), # regex for parsing target IDs
max_message_length=8000,
)
Consumer Changes
Each file that currently hardcodes platform names would read from the registry:
# gateway/run.py — adapter factory
from gateway.platforms.registry import platform_registry
def _create_adapter(platform, config):
entry = platform_registry.get(platform.value)
if not entry:
return None
if entry.check_fn and not entry.check_fn():
logger.warning("%s: requirements not met", platform.value)
return None
return entry.adapter_class(config)
# toolsets.py — auto-generate platform toolsets
for name, entry in platform_registry.items():
TOOLSETS[f"hermes-{name}"] = {
"description": f"{entry.label} bot toolset",
"tools": entry.tools,
"includes": [],
}
TOOLSETS["hermes-gateway"]["includes"] = [f"hermes-{n}" for n in platform_registry]
# cron/scheduler.py, send_message_tool.py — platform maps
PLATFORM_MAP = {name: Platform(name) for name in platform_registry}
Discovery
Two options (not mutually exclusive):
Option A: Import-time registration (like tools/registry.py)
A _discover_platforms() function in gateway/run.py imports each adapter module, triggering platform_registry.register() calls at module level. Simple, proven pattern.
Option B: Entry-point plugins (like hermes_cli/plugins.py)
Third-party platform adapters register via pyproject.toml entry points:
[project.entry-points."hermes.platforms"]
feishu = "gateway.platforms.feishu"
This enables community-contributed platforms as pip-installable packages without forking the repo.
Recommend starting with Option A for built-in platforms, with Option B as a future extension for community platforms.
Platform Enum
The Platform enum in gateway/config.py can be auto-generated from the registry, or kept as a static enum with validation that registered platforms have matching enum values. The enum is used as dict keys throughout the codebase so removing it entirely may not be worth the churn — but new platforms wouldn't need to manually add enum values if the registry handles it.
Migration Plan
- Create
gateway/platforms/registry.py with PlatformEntry dataclass and PlatformRegistry class
- Add
register() calls to each existing adapter (Telegram, Discord, Slack, etc.) — purely additive, no behavior change
- Migrate consumers one at a time — each consumer (gateway/run.py factory, toolsets.py, send_message_tool.py, etc.) switches from hardcoded maps to registry lookups
- Remove the hardcoded entries once all consumers use the registry
- Verify with full test suite at each step
Each step is independently mergeable. The total diff should be net-negative lines (removing ~200 lines of boilerplate across consumers, adding ~50 lines of registry infrastructure).
Success Criteria
Adding a new platform adapter requires:
- 1 new file:
gateway/platforms/new_platform.py (with register() call)
- 1 test file:
tests/gateway/test_new_platform.py
- 1 doc file:
website/docs/user-guide/messaging/new_platform.md
- 1 line in
pyproject.toml (optional dep)
- 0 changes to gateway/run.py, toolsets.py, config.py, send_message_tool.py, etc.
Problem
Adding a new messaging platform adapter requires touching 17+ files with manual boilerplate. Every platform (Telegram, Discord, Slack, WhatsApp, Signal, Mattermost, Matrix, DingTalk, Feishu, etc.) repeats the same pattern — enum values, factory branches, allowlist maps, toolset definitions, setup wizard entries, cron maps, send_message dispatch branches, and label strings scattered across the codebase.
This was highlighted during the Feishu salvage (PR #3817): the adapter itself is a single file, but wiring it in required surgical edits to 16 other files. Miss one spot and something silently breaks. The tool system already solved this exact problem with
tools/registry.py— platform adapters should follow the same pattern.Current State (per platform, 17 touchpoints)
gateway/config.pyPlatform.FEISHU = "feishu"enumgateway/config.py_apply_env_overrides()blockgateway/config.pyget_connected_platforms()checkgateway/run.py_create_adapter()factory branchgateway/run.pygateway/run.pytoolsets.pyhermes-feishutoolset definitiontoolsets.pyhermes-gatewayincludes listtools/send_message_tool.pytools/send_message_tool.pytools/send_message_tool.pyhermes_cli/config.pySENSITIVE_VARSentrieshermes_cli/gateway.py_PLATFORMSsetup wizard entryhermes_cli/tools_config.pyhermes toolsUIhermes_cli/skills_config.pyhermes skillsUIcron/scheduler.pytools/cronjob_tools.pyPlus 3+ test files and 3+ doc files.
Proposed: Platform Registry
A
gateway/platforms/registry.pythat mirrors the tool registry pattern. Each adapter self-registers with all its metadata at import time.Adapter Registration
Consumer Changes
Each file that currently hardcodes platform names would read from the registry:
Discovery
Two options (not mutually exclusive):
Option A: Import-time registration (like tools/registry.py)
A
_discover_platforms()function ingateway/run.pyimports each adapter module, triggeringplatform_registry.register()calls at module level. Simple, proven pattern.Option B: Entry-point plugins (like hermes_cli/plugins.py)
Third-party platform adapters register via
pyproject.tomlentry points:This enables community-contributed platforms as pip-installable packages without forking the repo.
Recommend starting with Option A for built-in platforms, with Option B as a future extension for community platforms.
Platform Enum
The
Platformenum ingateway/config.pycan be auto-generated from the registry, or kept as a static enum with validation that registered platforms have matching enum values. The enum is used as dict keys throughout the codebase so removing it entirely may not be worth the churn — but new platforms wouldn't need to manually add enum values if the registry handles it.Migration Plan
gateway/platforms/registry.pywithPlatformEntrydataclass andPlatformRegistryclassregister()calls to each existing adapter (Telegram, Discord, Slack, etc.) — purely additive, no behavior changeEach step is independently mergeable. The total diff should be net-negative lines (removing ~200 lines of boilerplate across consumers, adding ~50 lines of registry infrastructure).
Success Criteria
Adding a new platform adapter requires:
gateway/platforms/new_platform.py(withregister()call)tests/gateway/test_new_platform.pywebsite/docs/user-guide/messaging/new_platform.mdpyproject.toml(optional dep)