بنية البوابة من الداخل
Gateway Internals
ما هذه الصفحة، وماذا تحتوي.
بوابة المراسلة: الوصلة التي تجعلك تكلّم Hermes من تطبيق تستعمله أصلًا، مثل Telegram أو WhatsApp، بدل الطرفية. الوكيل الذي تصله من هاتفك تستعمله فعلًا. الذي يحتاج فتح الحاسوب تنساه بعد أسبوع. ستستعمل هنا hermes gateway stop وhermes chat، والقراءة نحو 9 دقائق. انتبه: افتح القناة لنفسك فقط في البداية عبر قائمة سماح. القناة المفتوحة تعني أن أي شخص يراسل وكيلك.
How the messaging gateway boots, authorizes users, routes sessions, and delivers messages
نتائج مأخوذة من هذه الصفحة، لا من قالب.
- تعرف ما بوابة المراسلة ولماذا قد تحتاجه.
- تنفّذ
hermes gateway stopوhermes chatوتفهم ما يحدث بعدها. - تقرأ الجدول وتأخذ منه السطر الذي يخصّك فقط.
- تضبط
TELEGRAM_ALLOW_ALL_USERSفي المكان الصحيح.
كما تظهر تمامًا داخل Hermes.
hermes gateway stophermes chathermes sendhermes gateway start
TELEGRAM_ALLOW_ALL_USERSTELEGRAM_ALLOWED_USERSGATEWAY_ALLOW_ALL_USERSGATEWAY_KNOWN_COMMANDSGATEWAY_RELAY_URL
انتقل مباشرة إلى ما تحتاجه.
بلا اختصار أو حذف.
النص أدناه منقول من المصدر الرسمي بالإنجليزية حتى تبقى الأوامر والأسماء دقيقة كما هي. قبل كل قسم شرح عربي يوضّح ما بداخله.
The messaging gateway is the long-running process that connects Hermes to 20+ external messaging platforms through a unified architecture.
Key Files
جدول مرجعي. لا تقرأه كله، ابحث عن السطر الذي يخصّك فقط.
| File | Purpose |
|---|---|
gateway/run.py | GatewayRunner — main loop, slash commands, message dispatch (large file; check git for current LOC) |
gateway/session.py | SessionStore — conversation persistence and session key construction |
gateway/delivery.py | Outbound message delivery to target platforms/channels |
gateway/pairing.py | DM pairing flow for user authorization |
gateway/channel_directory.py | Maps chat IDs to human-readable names for cron delivery |
gateway/hooks.py | Hook discovery, loading, and lifecycle event dispatch |
gateway/mirror.py | Cross-session message mirroring for send_message |
gateway/status.py | Token lock management for profile-scoped gateway instances |
gateway/builtin_hooks/ | Extension point for always-registered hooks (none shipped) |
gateway/platform_registry.py | Adapter registry, factories, and deferred (lazy) loaders for bundled platform plugins |
plugins/platforms/<name>/ | Bundled messaging adapters (most platforms: adapter.py + plugin.yaml) |
gateway/platforms/ | Shared base.py plus legacy/direct adapters (Signal, API server, webhooks, …) |
Architecture Overview
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه. تذكير: الوصلة التي تجعلك تكلّم Hermes من تطبيق تستعمله أصلًا، مثل Telegram أو WhatsApp، بدل الطرفية.
┌─────────────────────────────────────────────────┐
│ GatewayRunner │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Telegram │ │ Discord │ │ Slack │ │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ _handle_message() │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ Slash command AIAgent Queue/BG │
│ dispatch creation sessions │
│ │ │
│ ▼ │
│ SessionStore │
│ (SQLite persistence) │
└───────┴─────────────┴─────────────┴─────────────┘Message Flow
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
When a message arrives from any platform:
- Platform adapter receives raw event, normalizes it into a
MessageEvent - Base adapter checks active session guard:
- If agent is running for this session → queue message, set interrupt event
- If
/approve,/deny,/stop→ bypass guard (dispatched inline) - GatewayRunner._handle_message() receives the event:
- Resolve session key via
_session_key_for_source()(format:agent:main:{platform}:{chat_type}:{chat_id}) - Check authorization (see Authorization below)
- Check if it's a slash command → dispatch to command handler
- Check if agent is already running → intercept commands like
/stop,/status - Otherwise → create
AIAgentinstance and run conversation - Response is sent back through the platform adapter
Session Key Format
Session keys encode the full routing context:
agent:main:{platform}:{chat_type}:{chat_id}For example: agent:main:telegram:private:123456789
Thread-aware platforms (Telegram forum topics, Discord threads, Slack threads) may include thread IDs in the chat_id portion. Never construct session keys manually — always use build_session_key() from gateway/session.py.
Two-Level Message Guard
When an agent is actively running, incoming messages pass through two sequential guards:
- Level 1 — Base adapter (
gateway/platforms/base.py): Checks_active_sessions. If the session is active, queues the message in_pending_messagesand sets an interrupt event. This catches messages before they reach the gateway runner.
- Level 2 — Gateway runner (
gateway/run.py): Checks_running_agents. Intercepts specific commands (/stop,/new,/queue,/status,/approve,/deny) and routes them appropriately. Everything else triggersrunning_agent.interrupt().
Commands that must reach the runner while the agent is blocked (like /approve) are dispatched inline via await self._message_handler(event) — they bypass the background task system to avoid race conditions.
Authorization
إعدادات تضبطها مرة وتنساها. غيّر واحدًا في كل مرة حتى تعرف أثر كل تغيير. تضبط TELEGRAM_ALLOW_ALL_USERS، TELEGRAM_ALLOWED_USERS خارج المحادثة، في بيئة التشغيل.
The gateway uses a multi-layer authorization check, evaluated in order:
- Per-platform allow-all flag (e.g.,
TELEGRAM_ALLOW_ALL_USERS) — if set, all users on that platform are authorized - Platform allowlist (e.g.,
TELEGRAM_ALLOWED_USERS) — comma-separated user IDs - DM pairing — authenticated users can pair new users via a pairing code
- Global allow-all (
GATEWAY_ALLOW_ALL_USERS) — if set, all users across all platforms are authorized - Default: deny — unauthorized users are rejected
DM Pairing Flow
Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."Pairing state is persisted in gateway/pairing.py and survives restarts.
Slash Command Dispatch
إعدادات تضبطها مرة وتنساها. غيّر واحدًا في كل مرة حتى تعرف أثر كل تغيير. تضبط GATEWAY_KNOWN_COMMANDS خارج المحادثة، في بيئة التشغيل.
All slash commands in the gateway flow through the same resolution pipeline:
resolve_command()fromhermes_cli/commands.pymaps input to canonical name (handles aliases, prefix matching)- The canonical name is checked against
GATEWAY_KNOWN_COMMANDS - Handler in
_handle_message()dispatches based on canonical name - Some commands are gated on config (
gateway_config_gateonCommandDef)
Running-Agent Guard
Commands that must NOT execute while the agent is processing are rejected early:
if _quick_key in self._running_agents:
if canonical == "model":
return "⏳ Agent is running — wait for it to finish or /stop first."Bypass commands (/stop, /new, /approve, /deny, /queue, /status) have special handling.
Config Sources
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
The gateway reads configuration from multiple sources:
| Source | What it provides |
|---|---|
~/.hermes/.env | API keys, bot tokens, platform credentials |
~/.hermes/config.yaml | Model settings, tool configuration, display options |
| Environment variables | Override any of the above |
Unlike the CLI (which uses load_cli_config() with hardcoded defaults), the gateway reads config.yaml directly via YAML loader. This means config keys that exist in the CLI's defaults dict but not in the user's config file may behave differently between CLI and gateway.
Platform Adapters
إعدادات تضبطها مرة وتنساها. غيّر واحدًا في كل مرة حتى تعرف أثر كل تغيير. الأوامر هنا: hermes chat. تضبط GATEWAY_RELAY_URL خارج المحادثة، في بيئة التشغيل.
Most messaging platforms ship as plugin adapters under plugins/platforms/<name>/adapter.py; a few legacy adapters still live directly in gateway/platforms/. All extend BasePlatformAdapter from gateway/platforms/base.py:
plugins/platforms/ # plugin-packaged adapters (one dir each)
├── telegram/adapter.py # Telegram Bot API (long polling or webhook)
├── discord/adapter.py # Discord bot via discord.py
├── slack/adapter.py # Slack Socket Mode
├── whatsapp/adapter.py # WhatsApp Business Cloud API
├── matrix/adapter.py # Matrix via mautrix (optional E2EE)
├── mattermost/adapter.py # Mattermost WebSocket API
├── email/adapter.py # Email via IMAP/SMTP
├── sms/adapter.py # SMS via Twilio
├── dingtalk/adapter.py # DingTalk WebSocket
├── feishu/adapter.py # Feishu/Lark WebSocket or webhook
├── wecom/adapter.py # WeCom (WeChat Work) callback
├── line/adapter.py # LINE Messaging API
├── teams/adapter.py # Microsoft Teams
├── irc/adapter.py # IRC (canonical scoped-lock example)
├── homeassistant/adapter.py # Home Assistant conversation integration
└── … # google_chat, ntfy, photon, raft, simplex, …
gateway/platforms/ # core base + legacy direct adapters
├── base.py # BasePlatformAdapter — shared logic for all platforms
├── signal.py # Signal via signal-cli REST API
├── weixin.py # Weixin (personal WeChat) via iLink Bot API
├── bluebubbles.py # Apple iMessage via BlueBubbles macOS server
├── qqbot/ # QQ Bot (Tencent QQ) via Official API v2 (sub-package)
├── yuanbao.py # Yuanbao (Tencent) DM/group adapter
├── msgraph_webhook.py # Microsoft Graph change-notification webhook (Teams, Outlook, etc.)
├── webhook.py # Inbound/outbound webhook adapter
└── api_server.py # REST API server adapterDeferred loading: Bundled kind: platform plugins register cheap register_deferred loaders in gateway/platform_registry.py (via hermes_cli/plugins.py) so platform SDKs import only when the gateway starts, delivers, or runs setup/status — not on plain hermes chat. Resolution loads one adapter on lookup; full enumeration runs pending loaders only on paths that need every platform.
Experimental connector-backed platforms use the generic relay adapter in gateway/relay/ instead of a direct platform module. When GATEWAY_RELAY_URL or gateway.relay_url is configured, the gateway registers the relay platform, dials the connector over an outbound WebSocket, and receives descriptor, inbound, and interrupt_inbound frames on that same socket. The connector advertises a CapabilityDescriptor; Hermes can send normal outbound replies, token-less follow_up operations, and interrupt frames back through the relay. The source-grounded wire contract lives in docs/relay-connector-contract.md ↗.
Adapters implement a common interface:
connect()/disconnect()— lifecycle managementsend()— outbound message delivery- inbound events are normalized into a
MessageEventand forwarded viahandle_message()
Token Locks
Adapters that connect with unique credentials call acquire_scoped_lock() in connect() and release_scoped_lock() in disconnect(). This prevents two profiles from using the same bot token simultaneously.
Delivery Path
أوامر تكتبها في الطرفية. افهم ما يفعله الأمر قبل نسخه. الأوامر هنا: hermes send.
Outgoing deliveries (gateway/delivery.py) handle:
- Direct reply — send response back to the originating chat
- Home channel delivery — route cron job outputs and background results to a configured home channel
- Explicit target delivery — the send engine specifying
telegram:-1001234567890, exposed via thehermes sendCLI for shell scripts and via crondeliver:targets - Cross-platform delivery — deliver to a different platform than the originating message
Cron job deliveries are NOT mirrored into gateway session history — they live in their own cron session only. This is a deliberate design choice to avoid message alternation violations.
Hooks
جدول مرجعي. لا تقرأه كله، ابحث عن السطر الذي يخصّك فقط.
Gateway hooks are Python modules that respond to lifecycle events:
Gateway Hook Events
| Event | When fired |
|---|---|
gateway:startup | Gateway process starts |
session:start | New conversation session begins |
session:end | Session completes or times out |
session:reset | User resets session with /new |
agent:start | Agent begins processing a message |
agent:step | Agent completes one tool-calling iteration |
agent:end | Agent finishes and returns response |
command:* | Any slash command is executed |
Hooks are discovered from gateway/builtin_hooks/ (an extension point — currently empty in the shipped distribution; _register_builtin_hooks() is a no-op stub) and ~/.hermes/hooks/ (user-installed). Each hook is a directory with a HOOK.yaml manifest and handler.py.
Memory Provider Integration
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
When a memory provider plugin (e.g., Honcho) is enabled:
- Gateway creates an
AIAgentper message with the session ID - The
MemoryManagerinitializes the provider with the session context - Provider tools (e.g.,
honcho_profile,viking_search) are routed through:
AIAgent._invoke_tool()
→ self._memory_manager.handle_tool_call(name, args)
→ provider.handle_tool_call(name, args)- On session end/reset,
on_session_end()fires for cleanup and final data flush
Memory Flush Lifecycle
When a session is reset, resumed, or expires:
- Built-in memories are flushed to disk
- Memory provider's
on_session_end()hook fires - A temporary
AIAgentruns a memory-only conversation turn - Context is then discarded or archived
Background Maintenance
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
The gateway runs periodic maintenance alongside message handling:
- Cron ticking — checks job schedules and fires due jobs
- Session expiry — cleans up abandoned sessions after timeout
- Memory flush — proactively flushes memory before session expiry
- Cache refresh — refreshes model lists and provider status
Process Management
أوامر تكتبها في الطرفية. افهم ما يفعله الأمر قبل نسخه. الأوامر هنا: hermes gateway stop، hermes gateway start.
The gateway runs as a long-lived process, managed via:
hermes gateway start/hermes gateway stop— manual controlsystemctl(Linux) orlaunchctl(macOS) — service management- PID file at
~/.hermes/gateway.pid— profile-scoped process tracking
Profile-scoped vs global: start_gateway() uses profile-scoped PID files. hermes gateway stop stops only the current profile's gateway. hermes gateway stop --all uses global ps aux scanning to kill all gateway processes (used during updates).
3 أسئلة إجاباتها كلها في هذه الصفحة.
كل خيار اسم حقيقي من توثيق Hermes. حتى الخيارات الخاطئة حقيقية، لكنها من صفحات أخرى.