MAX Messenger platform adapter for Hermes Agent.
This plugin lets Hermes receive and send messages through MAX (max.ru) using
either webhook delivery or long polling. It supports text, markdown formatting,
attachments, images, files, voice/audio, video, inline keyboards, callback
events, typing/read actions, and cron notification delivery to a default chat.
- Inbound messages through webhook or polling.
- Outbound text with automatic chunking for MAX message limits.
- Markdown-compatible formatting for bold, italic, strikethrough, code, links, and headings.
- Images, files, audio/voice, and video delivery.
- Inline keyboard attachments from send metadata.
- Callback event handling.
- Optional allow-list for permitted MAX user IDs.
- Optional default chat for cron and notification delivery.
- Hermes Agent with platform plugin support.
- Python package:
aiohttp. - A MAX bot token.
platform-api2.max.ru's TLS cert chains to the Russian Ministry of Digital
Development's own root CA, which isn't in the standard system/Mozilla trust
store. The ca/ directory bundles the official root+sub certs (from
gu-st.ru, the Mintsifry-operated distribution host) and the adapter
extends (not replaces) the default trust store with them — no extra system
CA installation needed on the host running Hermes.
Install the runtime dependency in the same environment that runs Hermes:
pip install aiohttpThis is a third-party (Plugin Path) adapter, not a bundled/core one — per
Hermes's own gateway/platforms/ADDING_A_PLATFORM.md, third-party plugins
belong directly under ~/.hermes/plugins/<name>/, not
~/.hermes/plugins/platforms/<name>/ (that platforms/ subdirectory is
reserved for adapters bundled with Hermes itself — the plugin scanner keys
a user-installed plugin by its directory name one level down from
~/.hermes/plugins/, so installing under platforms/max registers a
platform key of platforms/max instead of max, and the adapter silently
never loads).
The simplest correct install is via the Hermes CLI, which handles the path for you:
hermes plugins install <your-repo-url-or-owner/repo>
hermes plugins enable maxEquivalent manual install:
git clone <your-repo-url> ~/.hermes/plugins/max
hermes plugins enable maxThen restart the gateway (hermes gateway restart) for it to pick up the
newly enabled platform.
The plugin reads configuration from Hermes platform config and from environment variables. Environment variables take precedence for deployment-specific values.
Required:
| Variable | Description |
|---|---|
MAX_BOT_TOKEN |
MAX bot token used as the API Authorization header. |
Optional:
| Variable | Description |
|---|---|
MAX_TRANSPORT |
webhook or polling. Defaults to webhook when MAX_WEBHOOK_URL is set, otherwise polling. |
MAX_WEBHOOK_URL |
Public HTTPS webhook URL registered with MAX, for example https://example.com/max/webhook. MAX expects external HTTPS on port 443. |
MAX_WEBHOOK_SECRET |
Secret validated against X-Max-Bot-Api-Secret on webhook requests. |
MAX_WEBHOOK_HOST |
Local webhook bind host. Defaults to 0.0.0.0. |
MAX_WEBHOOK_PORT |
Local webhook listen port. Defaults to 8650. |
MAX_WEBHOOK_PATH |
Local webhook path. Defaults to /max/webhook. |
MAX_UPDATE_TYPES |
Comma-separated update types. Defaults to message_created,message_callback,bot_started. |
MAX_API_BASE_URL |
MAX Bot API base URL. Defaults to https://platform-api2.max.ru (the pre-2026-07-19 host, platform-api.max.ru, is retired — override only for tests, proxies, or compatible endpoints). |
MAX_ALLOWED_USERS |
Comma-separated MAX user IDs allowed to talk to the bot. |
MAX_ALLOW_ALL_USERS |
Set to true to allow any MAX user. Intended for development only. |
MAX_HOME_CHANNEL |
Default chat ID for cron and notification delivery. |
MAX_HOME_CHANNEL_NAME |
Optional display name for the home channel. |
MAX_HOME_CHANNEL_THREAD_ID |
Optional thread ID for home channel delivery metadata. |
Webhook mode starts a local aiohttp server and registers the public webhook URL
with MAX.
Typical deployment shape:
- Run Hermes with
MAX_TRANSPORT=webhook. - Bind the plugin locally, for example
0.0.0.0:8650/max/webhook. - Put a reverse proxy in front of it.
- Expose the public URL over HTTPS on port 443.
- Set
MAX_WEBHOOK_URLto that public URL.
Example:
export MAX_BOT_TOKEN="..."
export MAX_TRANSPORT="webhook"
export MAX_WEBHOOK_URL="https://example.com/max/webhook"
export MAX_WEBHOOK_SECRET="change-me"
export MAX_WEBHOOK_HOST="0.0.0.0"
export MAX_WEBHOOK_PORT="8650"
export MAX_WEBHOOK_PATH="/max/webhook"Polling mode does not require a public endpoint. It periodically reads updates from the MAX API.
export MAX_BOT_TOKEN="..."
export MAX_TRANSPORT="polling"If MAX_TRANSPORT is omitted and MAX_WEBHOOK_URL is not set, polling is used.
Hermes can send local files through platform adapters. When replying through
MAX, use an absolute local file path in a MEDIA: directive when Hermes needs
to deliver a generated file:
MEDIA:/absolute/path/to/file.png
Audio files such as .mp3, .wav, .m4a, and .ogg are best delivered as
files/documents when preserving sound quality matters.
The adapter supports inline keyboards in two ways.
Use inline keyboards when the user needs to choose a short action or open a link. Keep them compact, usually 2-4 buttons. Do not add buttons to ordinary informational replies.
Hermes clarify prompts with choices are rendered as native MAX inline
buttons automatically. Button taps unblock the waiting agent turn through the
standard Hermes clarify flow.
For ordinary Hermes replies, include a MAX-specific directive in the outgoing
text. The adapter removes the directive before sending and converts it to a
native MAX inline_keyboard attachment:
Choose an action:
<!-- max_buttons: [[{"text":"Continue","payload":"continue"}],[{"text":"Open","url":"https://example.com"}],[{"text":"Copy","copy":"text for clipboard"}]] -->Button shorthands:
{ "text": "Continue", "payload": "continue" }→ MAXcallbackbutton{ "text": "Open", "url": "https://example.com" }→ MAXlinkbutton{ "text": "Copy", "copy": "text for clipboard" }→ MAXclipboardbutton- raw MAX button objects also pass through, e.g.
{ "type": "clipboard", "text": "Copy", "payload": "text for clipboard" }
For manual testing, the same JSON can be written as a fenced block:
Choose an action:
```max_buttons
[[{"text":"Continue","payload":"continue"}]]
```Programmatic sends can also pass MAX-specific metadata. Supported metadata shapes include:
{
"max_inline_keyboard": [
[{"text": "Open", "url": "https://example.com"}],
[{"text": "Pick", "payload": "resume:123"}],
]
}or raw MAX attachments:
{
"max_attachments": [
{"type": "inline_keyboard", "payload": {"buttons": [[...]]}}
]
}The native command menu (/help, /model, etc. — see MAX_NATIVE_COMMANDS
in adapter.py) ships with Russian descriptions by default; the command
names stay English on purpose. MAX's command menu only stores a name +
description pair — name is the literal slash-command trigger, and
Hermes's gateway dispatcher (gateway/run.py) matches incoming text against
a fixed set of canonical English command names. Translating name would
show a nice Russian trigger in MAX's UI, but tapping it sends text the
dispatcher doesn't recognize and the command silently no-ops. Only
description — pure display text with no effect on dispatch — is safe to
translate, so that's the only field this plugin localizes.
For a full reference of every Hermes gateway command (not just the 12 in
MAX's menu) with Russian descriptions, see COMMANDS.ru.md.
For everything Hermes says beyond the command menu — approval prompts,
restart/drain notices, and the replies of several built-in slash commands —
Hermes ships its own translation catalog (agent/i18n.py,
locales/<lang>.yaml), independent of this plugin, and Russian (ru) is
already a supported, essentially complete catalog (as of this writing,
locales/ru.yaml is 416 lines against 428 in locales/en.yaml). It's not
wired to MAX specifically — it recolors every platform Hermes talks on at
once (Telegram, Discord, CLI, MAX, ...). Because it's global rather than
MAX-specific, this plugin does not turn it on for you at install time;
opt in yourself by adding to ~/.hermes/config.yaml:
display:
language: ruRestart the gateway (hermes gateway restart) afterward. Note /whoami's
reply specifically is not yet migrated into this catalog (it's an f-string
in gateway/slash_commands.py), so it stays English even with language: ru
set — everything else covered by the catalog will switch.
Useful checks:
python -m py_compile adapter.py __init__.py
git status --shortThis repository is maintained as an independent MAX platform plugin for Hermes.
The earliest implementation was imported from the public
MaZzZilka/hermes-max-plugin repository and then substantially reworked for
webhook support, media delivery, callbacks, chunking, and Hermes gateway
integration.