# 15 — hermes-agent Source Reference Map A cheat-sheet of the **exact hermes-agent files/lines** to read for each integration point. Paths are relative to `hermes-agent/` (the read-only reference). This lets a coder jump straight to the right code instead of re-deriving the architecture. > ⚠️ Read-only. We **install** our plugin into `~/.hermes/plugins/android`; we > never edit these files. ## Plugin / platform registration | What | Where | |---|---| | How to add a platform (Plugin Path) | `gateway/platforms/ADDING_A_PLATFORM.md` | | Canonical plugin-platform example | `plugins/platforms/irc/adapter.py` (esp. `register(ctx)` at :953, `_env_enablement` :677, `_standalone_send` :743, `_get_scoped_secret` :42) | | ntfy plugin example (push-ish) | `plugins/platforms/ntfy/adapter.py`, `plugin.yaml` | | `register_platform()` (PluginContext) | `hermes_cli/plugins.py:2774` | | `PlatformEntry` dataclass (all fields) | `gateway/platform_registry.py:63` | | `Platform` enum | `gateway/config.py` | | Plugin discovery (`PluginManager`) | `hermes_cli/plugins.py` | ## Base adapter contract | What | Where | |---|---| | `BasePlatformAdapter` (ABC) | `gateway/platforms/base.py:2890` | | `MessageEvent` (inbound) | `gateway/platforms/base.py:2300` | | `MessageType` | `gateway/platforms/base.py:2278` | | `SendResult` | `gateway/platforms/base.py:2466` | | `build_source(...)` (SessionSource) | `gateway/platforms/base.py:7047` | | `handle_message(event)` | `gateway/platforms/base.py:5981` | | `send()` / `edit_message()` / `delete_message()` | `base.py:3920 / 3976 / 4005` | | `send_typing` / `stop_typing` | `base.py:4298 / 4307` | | Media send: `send_image/video/document/voice/animation/image_file/multiple_images` | `base.py:4396 / 4632 / 4659 / 4486 / 4415 / 4339 / 7834(tg)` | | `extract_images` / `extract_media` / `extract_local_files` | `base.py:4439 / 4884 / 5020` | | Media cache helpers: `cache_image/audio/video/document_from_bytes` | `base.py:854 / 1005 / 1122 / 2126` | | Inbound media size: `get_inbound_media_max_bytes` / `validate_inbound_media_size` | `base.py:758 / 779` | | Media delivery security: `validate_media_delivery_path` + roots/recency/denied | `base.py:1684 / 1312-1480` | | Interactive: `send_slash_confirm` / `send_clarify` / `send_private_notice` | `base.py:4169 / 4204 / 4278` | | `create_handoff_thread` | `base.py:3949` | | Streaming hooks: `supports_draft_streaming` / `send_draft` / `render_message_event` / `format_tool_event` | `base.py:3215 / 3274 / 3316 / 3337` | | Scoped secret read pattern (profile-safe) | `plugins/platforms/irc/adapter.py:42` | | Scoped lock (profile-safe bind) | `gateway/status.py` (`acquire_scoped_lock`) | ## Streaming (legacy callback path — what the main gateway uses) | What | Where | |---|---| | `GatewayStreamConsumer` (the sink) | `gateway/stream_consumer.py:156` | | `on_delta` / `on_commentary` / `on_segment_break` / `finish` | `stream_consumer.py:611 / 518 / 514 / 623` | | Consumer `run()` loop (edit cadence) | `stream_consumer.py:781` | | Structured events (ACP-only, NOT main gateway) | `gateway/stream_events.py`, `gateway/stream_dispatch.py:40` | | Callback wiring (agent → consumer) | `gateway/run.py:5642-5704` (`tool_progress_callback`, `stream_delta_callback`, `interim_assistant_callback`, `status_callback`, `event_callback`) | | `send_progress_messages` (tool progress → `send`) | `gateway/run.py:4603` | | Progress metadata | `gateway/run.py:28089` | ## Reasoning display | What | Where | |---|---| | Reasoning prepended to final response | `gateway/run.py:20089-20127` | | `show_reasoning` / `reasoning_style` defaults + per-platform | `gateway/display_config.py:33-181` | | `resolve_display_setting()` | `gateway/display_config.py:187` | | `last_reasoning` in agent result | `gateway/run.py:6471` | | Think-tag filtering in consumer | `stream_consumer.py:175-185, 627+` | ## Display settings (tool progress, interim, streaming, etc.) | What | Where | |---|---| | All overrideable display keys + defaults | `gateway/display_config.py:33` (`tool_progress`, `show_reasoning`, `reasoning_style`, `tool_preview_length`, `streaming`, `interim_assistant_messages`, `long_running_notifications`, `cleanup_progress`, `live_status`) | | Per-platform tiers | `gateway/display_config.py:81-181` | ## Slash commands | What | Where | |---|---| | `COMMAND_REGISTRY` / `CommandDef` (all commands) | `hermes_cli/commands.py:144+` | | `GATEWAY_KNOWN_COMMANDS` / `is_gateway_known_command` / `resolve_command` | `hermes_cli/commands.py` | | Gateway command dispatch (alias, access, hooks) | `gateway/run.py:16974-17099` | | `send_model_picker` / `send_choice_picker` (Telegram ref) | `plugins/platforms/telegram/adapter.py:6350 / 6424` | | Picker invocation from gateway | `gateway/slash_commands.py:1859, 2157, 3622-3657` | | Button-callback id conventions (`cl:`, `appr:`, `sc:`) | `gateway/platforms/ADDING_A_PLATFORM.md` (Interactive UX) | ## Cron delivery | What | Where | |---|---| | Resolve a delivery target (`platform:chat_id[:thread_id]`) | `cron/scheduler.py:2148` (`_resolve_single_delivery_target`) | | `_resolve_delivery_targets` / routing tokens (`all`) | `cron/scheduler.py:2296 / 2278` | | `cron_deliver_env_var` handling (home channel) | `cron/scheduler.py:1903+` | | `deliver` param normalization | `cron/scheduler.py:2251` | | `send_message` tool target resolution (`resolve_send_target`, `prepare_send_message_platforms`) | `tools/send_message_tool.py` | | `parse_target_ref_fn` usage | `tools/send_message_tool.py` (`_parse_target_ref`) | | Cron mirror delivery (default off) | `cron/scheduler.py:1520` | | `cronjob` tool schema (`deliver` description) | `tools/cronjob_tools.py` | ## Search (FTS5) | What | Where | |---|---| | Session store (SQLite + FTS5) | `hermes_state.py` | | Search implementation | `hermes_state_search.py` | | Schema | `hermes_state_schema.py` | ## Config / env / profiles | What | Where | |---|---| | `get_hermes_home()` / `display_hermes_home()` (profile-safe paths) | `hermes_constants.py` | | `DEFAULT_CONFIG` / `OPTIONAL_ENV_VARS` | `hermes_cli/config.py` | | Gateway config load (`load_gateway_config`, `_apply_env_overrides`) | `gateway/config.py` | | Profile override (`_apply_profile_override`) | `hermes_cli/main.py` | | Secret scope (multiplex fail-closed) | `agent/secret_scope.py` | | PII redaction | `agent/redact.py` | ## Dependencies (confirm zero new deps) | What | Where | |---|---| | `websockets==15.0.1` (core) | `pyproject.toml:111` | | `httpx[socks]==0.28.1` (core) | `pyproject.toml:44` | | `aiohttp` (messaging extra, NOT core) | `pyproject.toml:185` | | Dependency pinning policy | `pyproject.toml:19-39`, root `AGENTS.md` | ## Testing | What | Where | |---|---| | Hermetic test runner (use this, not bare pytest) | `scripts/run_tests.sh` | | `_isolate_hermes_home` fixture | `tests/conftest.py` | | Example platform tests | `tests/gateway/test_*.py` (e.g. `test_google_chat.py`, `test_line_plugin.py`) | | Stream-event tests (dispatcher) | `tests/gateway/test_stream_events.py` | ## Existing desktop app (for reference only — we do NOT reuse its backend) | What | Where | |---|---| | Electron desktop app | `apps/desktop/` (its own `AGENTS.md`, `DESIGN.md`) | | Shared JSON-RPC WS client (tui_gateway protocol) | `apps/shared/src/json-rpc-gateway.ts` | | tui_gateway WS transport (mobile-client-ready) | `tui_gateway/ws.py` | | tui_gateway method catalog | `tui_gateway/server.py` | > Note: the existing desktop app talks to the **`tui_gateway`** backend > (`hermes serve`), a *different* process from the messaging gateway. Our apps > talk to the **messaging gateway** via our own plugin + protocol. We borrow > naming conventions only.