agent-workflows: workspace-context-organization autonomous-ai-agents: hermes-agent computer-use devops: hermes-config-bulk-update, hermes-profile-management, holographic-memory, telegram-integration, webhook-subscriptions email: himalaya mcp: native-mcp, searxng-smart-search media: voice-systems, youtube-content mlops: local-vector-memory, qdrant-collection-management productivity: maps, notion, ocr-and-documents project-knowledge-base research: arxiv, blogwatcher, ecosystem-surveillance save-agents-md social-media: social-media-scraping, xurl software-development: agent-self-audit, simplify-code, spike, subagent-driven-development, systematic-debugging, test-driven-development, writing-plans user-response-style
12 KiB
name, description, version, author
| name | description | version | author |
|---|---|---|---|
| telegram-integration | Hermes Telegram platform config, Bot API features, local server setup, and integration improvements. | 1.0.0 | Hermes Agent |
Telegram Integration
Configure and extend Hermes' Telegram platform adapter. Covers platforms.telegram.extra options, Bot API feature enablement, and local Bot API server setup.
Config Extras
All live under platforms.telegram.extra in config.yaml. Apply to ALL profiles when changing shared settings.
| Extra | Default | Effect |
|---|---|---|
rich_messages |
false | Bot API 10.1 — native tables, task lists, details, math, code blocks |
disable_link_previews |
false | Suppress URL preview cards |
local_mode |
false | Use local telegram-bot-api server instead of api.telegram.org |
guest_mode |
false | Bot API 10.0 — reply in chats without joining |
dm_topics |
[] | Per-DM isolated topic sessions |
channel_prompts |
{} |
Per-chat ephemeral system prompts injected at API call time. Map of chat_id → prompt string. Never persisted to history. Use for voice-optimization, per-chat personality overrides, or platform-specific constraints. |
require_mention |
varies | Force @mention to trigger response |
Runtime Footer
display.runtime_footer.enabled: true appends model · 12% to every reply. Fields: model, context_pct, cwd. Per-platform overrides at display.platforms.telegram.runtime_footer.
Local Bot API Server
For privacy: runs a local telegram-bot-api binary that connects directly to Telegram's MTProto backend instead of api.telegram.org. Requires api_id + api_hash from my.telegram.org/apps.
Setup via Docker:
mkdir -p ~/telegram-bot-api-data
docker run -d --name telegram-bot-api --restart unless-stopped \
-p 8081:8081 \
-v ~/telegram-bot-api-data:/var/lib/telegram-bot-api \
-e TELEGRAM_API_ID=<id> \
-e TELEGRAM_API_HASH=<hash> \
aiogram/telegram-bot-api
Then set in ALL profile configs:
platforms:
telegram:
extra:
base_url: "http://localhost:8081/bot"
base_file_url: "http://localhost:8081/file/bot" # MUST include /file/ — file downloads 404 without it
local_mode: true
Critical: base_file_url vs base_url — PTB has two separate URL bases. base_url is for API calls (getUpdates, sendMessage, getFile). base_file_url is for file downloads. The local Bot API server serves files at /file/bot<token>/<path>, not /bot<token>/<path>. If base_file_url is missing or set to the same as base_url, file downloads return 404 which PTB reports as telegram.error.InvalidToken: Not Found — misleading because the token is fine. This must be fixed in every profile that has a platforms.telegram.extra block, not only the active one; otherwise a spawned profile or subagent can hit the same 404 when handling voice.
Docker permission issue — the aiogram/telegram-bot-api container creates files owned by postfix:_ssh (uid 101:gid 101) with mode 640. The Hermes gateway (user n8n, uid 1000) cannot read them. With local_mode: true, PTB tries to read files from disk first, fails due to permissions, falls back to HTTP, and 404s. Fix permissions from inside the container:
docker exec telegram-bot-api sh -c 'find /var/lib/telegram-bot-api/ -type d -exec chmod 755 {} \;'
docker exec telegram-bot-api sh -c 'find /var/lib/telegram-bot-api/ -type f -exec chmod 666 {} \;'
The container uses BusyBox find, so find -printf is unavailable; use the simple -type d/-type f form above. Also add n8n to the _ssh group on the host (sudo usermod -aG _ssh n8n) and restart the gateway. For persistence, schedule a cron job that re-chmods new files every minute. See voice-systems/references/telegram-bot-api-permission-fix.md for the full recipe.
Critical: Before switching, log the bot out of api.telegram.org:
curl -s "https://api.telegram.org/bot<TOKEN>/logOut"
Without this, updates split between cloud and local — the bot misses messages.
Phone still works remotely — local server maintains its own outbound MTProto connection. No inbound port forwarding needed.
Channel Prompts
telegram.channel_prompts maps chat_id → ephemeral system prompt injected at API call time, never persisted to history. Use for voice-optimization, per-chat personality overrides, or platform-specific constraints.
Pitfall — YAML corruption of XML tag references: When a channel prompt contains response or thinking (Hermes internal XML markers), YAML processing can mangle them into corrupted strings like response... response. The agent then emits responses wrapped in these tags, and the gateway's extraction pipeline strips them, leaving empty content. The error in logs is response_delivery_dropped: non-empty response ... empty after extract, recovery yielded nothing. Fix: remove all references to response/ thinking tags from channel prompts. Use plain language instead ("no markdown, no code blocks, no lists, no tables" is sufficient — the agent doesn't need to be told about internal XML tags).
Pitfall — stale gateway PID blocking restart: When a gateway process was started manually (e.g., hermes gateway run --replace in background), the systemd service sees the PID and refuses to start. The error in logs is Another gateway instance is already running (PID N). Fix: kill -9 <PID> then hermes -p <profile> gateway restart. The systemd service will then start cleanly.
Pitfall — Auto-TTS failed: name 'tempfile' is not defined: When the gateway tries to auto-TTS a voice reply, it calls tempfile.gettempdir() in gateway/platforms/base.py line 4313. If import tempfile is missing from the file's imports, this raises NameError, the TTS generation fails, and the response is dropped with response_delivery_dropped: non-empty response ... empty after extract, recovery yielded nothing. The root cause is a missing import in the gateway source, not a config issue. Fix: add import tempfile to the imports block in gateway/platforms/base.py (after import sys, before import time). Gateway restart required after the fix.
Pitfall — multi-profile gateway token conflict (Telegram gateway crash-loop): When two Hermes profiles both have platforms.telegram config blocks, the first gateway to start grabs the Telegram bot token via a scoped lock file. Any second gateway (even a dedicated telegram profile's systemd service) hits Telegram bot token already in use (PID N) and exits. Systemd's Restart=on-failure creates an infinite crash-loop. The gateway loads platforms based on config presence (platforms.telegram block exists), NOT on plugins.enabled. A profile with plugins.enabled: [] but a platforms.telegram.extra block still loads the Telegram adapter and claims the token.
Fix (remove config block from non-Telegram profiles):
# Remove the platforms.telegram block from the conflicting profile
hermes config set platforms.telegram null --profile <name>
# Also remove from base config if present
hermes config set platforms.telegram null
# Kill the conflicting gateway so the dedicated one can start
kill <PID>
# Wait for systemd restart cycle (~5-10s), then verify
hermes gateway status --profile telegram
journalctl --user -u hermes-gateway-telegram --since "1 minute ago" --no-pager | grep '✓ telegram connected'
Diagnosing which PID holds the lock:
# Inspect the scoped lock file
cat ~/.local/state/hermes/gateway-locks/telegram-bot-token-*.lock
# Shows: {"pid": <PID>, "scope": "telegram-bot-token", ...}
# Check which gateway process owns it
ps aux | grep 'hermes.*gateway' | grep -v grep
Verification checklist after fix:
- Only ONE gateway process running with Telegram (
ps aux | grep 'hermes.*gateway') - Lock file shows the correct PID
hermes gateway status --profile telegramshows✓ telegram connected- Gateway log shows
✓ telegram connected(not✗ telegram failed to connect) - No
token already in useerrors injournalctl --user -u hermes-gateway-telegram
Pitfall — stale gateway_voice_mode.json in wrong HERMES_HOME: The gateway reads voice mode state from HERMES_HOME / "gateway_voice_mode.json" (run.py:2790). When Telegram was handled by the general profile, the voice mode file lived at ~/.hermes/gateway_voice_mode.json. After moving Telegram to its own profile, the active file is at ~/.hermes/profiles/telegram/gateway_voice_mode.json. The old copy at the base HERMES_HOME is dead weight — delete it to avoid confusion.
Pitfall — cron job delivery requires platform-connected profile: Cron jobs with deliver='telegram' (or any platform-specific delivery) must live in the profile whose gateway actually holds that platform's connection. If the general profile has platforms.telegram in its config but the telegram profile's gateway holds the bot token, a cron job in the general profile with deliver='telegram' will fail with no delivery target resolved for deliver=telegram. The cron scheduler runs inside each profile's gateway and can only deliver through platforms that gateway has connected. Move the job to the profile where the platform is actually connected (write the job JSON to that profile's cron/jobs.json and restart its gateway).
Pitfall — cron ticker silent failure (cron.enabled not set): The cron scheduler won't start unless cron.enabled: true is explicitly set in the profile config. Without it, the gateway starts, Telegram connects, but no cron jobs fire — and there's no error or warning in the logs. The ticker heartbeat file (cron/ticker_heartbeat) is never created. Also set cron.max_parallel_jobs: 3 (or a reasonable number) — null may block the ticker. Verify with: cat ~/.hermes/profiles/<name>/cron/ticker_heartbeat — if the file exists and has a recent timestamp, the ticker is alive. Fix:
hermes config set cron.enabled true --profile <name>
hermes config set cron.max_parallel_jobs 3 --profile <name>
hermes -p <name> gateway restart
Profile-Scoped Organization
All Telegram-related user files should live under the telegram profile or workspace, not scattered across the base Hermes directory or other profiles.
Files that belong in the telegram profile (~/.hermes/profiles/telegram/):
config.yaml— Telegram platform config, TTS/STT settings, channel promptsgateway_voice_mode.json— per-chat voice mode stateskills/— Telegram-specific skills (telegram-integration, telegram-voice-mode, voice-systems references)scripts/— Telegram maintenance scripts (fix-telegram-bot-api-perms.sh)
Files that belong in the telegram workspace (~/workspace/telegram/):
AGENTS.md,README.md— workspace contextdocker-compose.yml,.env,entrypoint-fix.sh,rebuild-bot-api-container.sh— Bot API container managementdocs/— Telegram documentation (how_telegram_works.md, telegram.md)
Files that stay outside by necessity:
~/.config/systemd/user/hermes-gateway-telegram.service— systemd unit path is fixed~/.local/state/hermes/gateway-locks/telegram-bot-token-*.lock— machine-local lock, scoped by HERMES_HOME~/telegram-bot-api-data/— Docker bind-mount data, not Hermes config~/.hermes/hermes-agent/— shared source code, not profile-specific
Cleanup after migrating Telegram to its own profile:
- Remove
platforms.telegramfrom base config:hermes config set platforms.telegram null - Remove
platforms.telegramfrom general profile:hermes config set platforms.telegram null --profile general - Delete stale
~/.hermes/gateway_voice_mode.json(telegram profile has its own) - Move any lingering Telegram scripts/skills/docs into the telegram profile or workspace
references/bot-api-2026.md— Telegram Bot API changelog (10.1, 10.0, 9.6) with Hermes implementation statusreferences/telegram-voice-validation-checklist.md— 12-point systematic validation checklist for Telegram + voice pipeline audits