Full-featured Hermes Chatto Plugin. So you can use Chatto to talk to your Hermes Agent. https://gogs.netdome.biz/paul/hermes-chatto-plugin
|
|
1 week ago | |
|---|---|---|
| vendor | 1 week ago | |
| .gitignore | 1 week ago | |
| AGENTS.md | 1 week ago | |
| LICENSE | 1 week ago | |
| PLAN.md | 1 week ago | |
| README.md | 1 week ago | |
| VENDORING.md | 1 week ago | |
| __init__.py | 1 week ago | |
| adapter.py | 1 week ago | |
| after-install.md | 1 week ago | |
| platform_config.py | 1 week ago | |
| plugin.yaml | 1 week ago | |
| pyproject.toml | 1 week ago | |
| test_adapter.py | 1 week ago | |
| test_platform_config.py | 1 week ago | |
| uv.lock | 1 week ago | |
| vendor_chattolib.sh | 1 week ago | |
| vendor_path.py | 1 week ago | |
| vendor_split.py | 1 week ago |
A Chatto platform adapter plugin for Hermes Agent. Connects your self-hosted Chatto server to Hermes so you can interact with the agent through chat rooms and DMs.
All API interaction goes through chattolib: a persistent WebSocket realtime connection delivers inbound events, ConnectRPC over HTTP handles outbound messages, reactions, uploads and more. Messages are processed through the full Hermes Agent pipeline (tool use, memory, reasoning) and answered in real time.
Tested against Chatto v0.4.20 with the vendored chattolib[realtime] 0.4.20.post1 (realtime protocol v1). Chatto servers older than v0.4.20 are not supported.
Before setup, here's the part most people want to know: how Hermes behaves once it's in your Chatto instance.
| Context | Behavior |
|---|---|
| DMs | Hermes responds to every message. No @mention needed. Each DM has its own session. |
| Rooms | Hermes responds to every room message by default. With CHATTO_REQUIRE_MENTION=true, channel-kind rooms answer only @mentions; group-kind rooms always respond. |
| Threads | If you reply in a thread, Hermes keeps the thread context isolated from the parent room. The bot auto-follows threads it participates in. |
| Processing indicators | Hermes adds a 👀 reaction when it starts processing a message, and replaces it with ✅ on success, ❌ on failure, or 🚫 when processing was cancelled. |
| Typing indicators | Hermes broadcasts persistent typing indicators while it's working, so users know the bot is active. |
| Message splitting | Long responses (>10000 chars) are automatically split into multiple messages. |
Tip: By default Hermes answers every message in a room. Set
CHATTO_REQUIRE_MENTION=trueto have him answer only@mentionsin channel-type rooms.
Capabilities natively implemented by the Chatto plugin adapter:
| Capability | Supported |
|---|---|
| text | yes |
| markdown | yes |
| threads | yes |
| reactions | yes |
| message editing | yes |
| message deletion | yes |
| typing indicators | yes |
| processing notifications | yes (👀/✅/❌/🚫) |
| file attachments | yes (chunked upload) |
| image / video / audio / documents | yes (native attachments) |
| DM initiation | yes |
| room creation | yes |
| room join/leave over DM | yes (/join, /leave) |
| member directory | yes (cached) |
| presence broadcasting | yes (online, refreshed) |
| custom status | yes |
| read state management | yes |
| auto-reconnect | yes (with resume cursor) |
hermes)./join <room-id or #name> (see Managing Rooms over DM). For DMs, simply start a direct message with the bot./api/realtime.Info: The adapter uses WebSocket protocol v1 (compatible with Chatto v0.4.20+). Ensure your Chatto server is up to date.
hermes plugins install https://gogs.netdome.biz/paul/hermes-chatto-plugin.git
Hermes clones the plugin from that Git URL into its platform-plugin directory and auto-discovers it on gateway startup — no manual registration needed.
Note: During install Hermes reads the env vars declared in the plugin's
plugin.yaml(requires_env/optional_env) and prompts you for each — connection details plus a few behaviour options. Your answers are written to~/.hermes/.env; secrets should still live there only (see Configuration).
Copy the plugin files into the Hermes Agent plugin directory so they sit at ~/.hermes/plugins/platforms/chatto/. Hermes auto-discovers platform plugins on gateway startup — no manual registration needed.
Run the guided setup command:
hermes gateway setup
Select Chatto when prompted, then provide your server URL, login, and password when asked.
Add the following to your ~/.hermes/.env file:
# Required (token instead of login/password also works)
CHATTO_BASE_URL=https://chat.example.com
CHATTO_LOGIN=hermes
CHATTO_PASSWORD=your-password
# Optional: home channel for cron/notification delivery
# CHATTO_HOME_CHANNEL=REljMv5Pgolo6Y9
# Optional: restrict who can talk to the bot (comma-separated logins)
# CHATTO_ALLOWED_USERS=alice,bob
# Optional: allow any user to talk to the bot (default: false)
# CHATTO_ALLOW_ALL_USERS=true
# Optional: answer only @mentions in channels — group rooms and DMs always respond
# CHATTO_REQUIRE_MENTION=true
# Optional: rooms that answer without a mention even when require_mention is on
# CHATTO_FREE_RESPONSE_CHANNELS=REljMv5Pgolo6Y9
# Optional: auto-create threads for replies in rooms (default: true)
# CHATTO_AUTO_THREAD=true
# Optional: 👀/✅/❌/🚫 processing reactions (default: true)
# CHATTO_REACTIONS=true
Secrets (CHATTO_TOKEN, CHATTO_PASSWORD) belong in ~/.hermes/.env, never in config.yaml.
Or configure via ~/.hermes/config.yaml:
gateway:
platforms:
chatto:
enabled: true
extra:
base_url: https://chat.example.com
home_channel: REljMv5Pgolo6Y9
require_mention: false # only respond to @mentions in channels
allowed_users: [] # empty = deny all (or set allow_all_users)
allow_all_users: true
Note: Environment variables override
config.yamlvalues.
| Variable | Required | Default | Description |
|---|---|---|---|
CHATTO_BASE_URL |
Yes* | ChattoHQ (https://chat.chatto.run) |
Base URL of the Chatto server (e.g., https://chat.example.com) |
CHATTO_LOGIN |
Yes* | — | Chatto username (login) |
CHATTO_PASSWORD |
Yes* | — | Chatto password |
CHATTO_TOKEN |
No | — | Existing bearer token — alternative to login/password |
CHATTO_HOME_CHANNEL |
No | First watched room | Room ID for cron/notification delivery |
CHATTO_ALLOWED_USERS |
No | (deny all) | Comma-separated Chatto logins allowed to talk to the agent |
CHATTO_ALLOW_ALL_USERS |
No | false |
Allow any Chatto user to talk to the agent (true/false) |
CHATTO_REQUIRE_MENTION |
No | false |
Only respond to @mentions in channels. DMs always get a response. |
CHATTO_FREE_RESPONSE_CHANNELS |
No | — | Room IDs that answer without a mention even when require_mention is enabled |
CHATTO_AUTO_THREAD |
No | true |
Auto-create threads for replies in rooms (true/false) |
CHATTO_REACTIONS |
No | true |
👀/✅/❌/🚫 processing reactions (true/false) |
* Either login+password or CHATTO_TOKEN is required.
Once configured, start the gateway:
hermes gateway
The bot should connect to your Chatto server within a few seconds. Send it a message — a DM or any message in a room — to test.
If nothing happens at startup — plugin failed to load, credentials rejected, server unreachable — run hermes logs to see what the Chatto adapter reported.
You can run hermes gateway in the background or as a systemd service for persistent operation.
The adapter maintains a persistent WebSocket connection to the Chatto server through chattolib's event streaming API. All realtime events — room messages, reactions, DM notifications — arrive pre-decoded and are dispatched to the Hermes Agent pipeline.
All outbound actions — sending messages, reactions, typing indicators, file uploads, etc. — use chattolib's high-level API methods, which handle ConnectRPC calls over HTTP with authentication and error recovery automatically.
Hermes sends messages with full Markdown formatting. Chatto renders Markdown natively, so code blocks, lists, bold/italic, links, and inline code all display correctly.
When you reply to a message in Chatto (creating a thread), Hermes responds within that thread. Thread context stays isolated from the parent room — each thread has its own session namespace. With auto-threading enabled (default), fresh room replies open a thread under the incoming message, keeping busy-room timelines clean. The bot auto-follows threads it participates in.
Hermes uses emoji reactions for processing lifecycle notifications:
Disable the lifecycle reactions with CHATTO_REACTIONS=false.
The adapter supports editing existing messages (via chattolib's update_message) and deleting them (via delete_message). Streaming replies are delivered as incremental edits to a single message.
While the agent works, the adapter broadcasts a persistent typing indicator per room, refreshing it until the response is sent.
Messages longer than 10000 characters are automatically split into multiple sequential messages (split threshold: 9900 chars to leave room for separators).
File attachments are uploaded via chattolib's chunked upload API:
create_upload — initiates an upload sessionupload_chunk — uploads the file in 256 KB chunks (SHA-256 verified per chunk)complete_upload — finalizes the upload and attaches it to the messageImages, videos, audio and documents are posted as native Chatto attachments; several images can be bundled into a single message.
If the realtime stream drops, the adapter reconnects automatically with exponential backoff (1 s → 30 s max, jittered) and honours server-provided retry hints. On reconnect it resumes from the last event cursor, so no messages are lost or replayed during transient disconnects.
After discovering rooms, the adapter marks watched rooms as read and dismisses pending notifications (MarkRoomAsRead / DismissAllNotifications), keeping the bot's unread indicators clean.
The adapter can proactively start direct messages with other Chatto users via the StartDM RPC endpoint. This is used for cron job delivery and notifications.
The adapter can create new rooms via CreateRoom. This is available to the agent for organizing conversations or creating dedicated channels for tasks.
User lookups (for mention resolution etc.) go through GetUser and are cached per user ID and per handle to reduce API calls.
Presence is a TTL the server lets lapse, not a flag that stays set: UpdatePresence rejects OFFLINE outright ("stop refreshing to go offline"). The bot therefore announces itself online on startup and a background task re-announces it every 60 seconds until disconnect cancels that task. Going offline is exactly that cancellation — the adapter never sends an offline status.
| Presence Status | API Value |
|---|---|
| Online | 1 |
| Away | 2 |
| Do Not Disturb | 3 |
With CHATTO_REQUIRE_MENTION=true, Hermes answers channel-kind rooms only when the message contains an @mention of the bot's login or display name. In group-kind rooms and DMs every message gets a response. A channel message aimed at someone else is acknowledged with a 🫥 reaction instead of an answer.
By default, if neither CHATTO_ALLOWED_USERS nor CHATTO_ALLOW_ALL_USERS is set, the bot denies all users as a safety measure. Configure one of:
CHATTO_ALLOWED_USERS=alice,bob — only these Chatto logins can interact with the botCHATTO_ALLOW_ALL_USERS=true — any Chatto user can interactSetting both at once is rejected as conflicting configuration.
Warning: Setting
CHATTO_ALLOW_ALL_USERS=truemeans any user on your Chatto server has full access to the agent's capabilities, including tool use and system access. Use this only on trusted, private Chatto instances.
The home channel is where the bot sends proactive messages — cron job output, reminders, and notifications. Set it via:
CHATTO_HOME_CHANNEL env var (room ID)config.yaml → gateway.platforms.chatto.extra.home_channelIf unset, the first watched room is used as the default home channel.
The bot can manage its own room memberships when you DM it:
/join <room-id> — join a room by ID/join #name — join a room by name (case-insensitive; ambiguous names return the candidate IDs)/leave <room-id or #name> — leave a roomThese commands work only in direct messages with the bot and require you to pass the same user check as normal messages (CHATTO_ALLOWED_USERS / CHATTO_ALLOW_ALL_USERS). They change membership on the Chatto server itself (RoomService/JoinRoom, RoomService/LeaveRoom), so joined rooms survive gateway restarts and /leaved rooms stay left.
Two refusals by design: direct messages cannot be left, and leaving the configured home channel is rejected because cron/notification delivery posts there. If CHATTO_HOME_CHANNEL names a room the bot has not joined, the adapter logs a warning at each reconnect — invite the account natively in Chatto or DM it /join.
First step for every issue below: look at what the gateway logged around the time things went wrong:
hermes logs
The Chatto adapter logs each connection attempt, room discovery (_refresh_rooms), rejected users, failed sends and reconnects there — most problems name their cause directly.
Cause: The bot account is not a member of the room, or the user is not in CHATTO_ALLOWED_USERS.
Fix: Verify the bot is a member of the room (the adapter watches all joined rooms by default; DM the bot /join <room-id or #name> to add it). Check that your Chatto login is in CHATTO_ALLOWED_USERS, or set CHATTO_ALLOW_ALL_USERS=true. Restart the gateway.
Cause: The Chatto server is unreachable, or the URL is incorrect.
Fix: Verify CHATTO_BASE_URL points to your Chatto server (include https://, no trailing slash). Test connectivity:
curl -s -o /dev/null -w "%{http_code}" https://chat.example.com/api/connect/chatto.api.v1.ViewerService/GetViewer
If you use a reverse proxy (nginx, Apache), ensure WebSocket upgrade headers are configured for /api/realtime:
location /api/realtime {
proxy_pass http://chatto-backend;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
}
Cause: The CHATTO_LOGIN or CHATTO_PASSWORD is incorrect.
Fix: Verify credentials by testing the login endpoint directly:
curl -X POST https://chat.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"login":"hermes","password":"your-password"}'
If the response does not include a token field, the credentials are wrong. Reset the bot account password in Chatto and update ~/.hermes/.env.
Cause: The Chatto server is running an older version that doesn't support realtime protocol v1.
Fix: The adapter uses protocol v1, supported by Chatto v0.4.20 and later. Upgrade your Chatto server:
curl -s https://chat.example.com/api/version | jq .
If you see a RealtimeError with a protocol-related message in the logs, upgrading the server is the fix.
Cause: Network instability, Chatto server restarts, or firewall/proxy issues with WebSocket connections.
Fix: The adapter automatically reconnects with exponential backoff and resumes from the last cursor. Check:
Run hermes logs for recent adapter output, or dig through the gateway log directly:
grep -i "chatto\|websocket\|realtime" ~/.hermes/logs/gateway.log | tail -30
Cause: The bearer token expired during a session.
Fix: Check that the bot account hasn't been deactivated and the password hasn't changed. Update CHATTO_PASSWORD (or CHATTO_TOKEN) in ~/.hermes/.env and restart the gateway.
Cause: The Hermes gateway isn't running, or it failed to connect.
Fix: Check that hermes gateway is running. Look at the terminal output or gateway logs for error messages. Common issues: wrong URL, expired credentials, Chatto server unreachable.
Warning: Always set
CHATTO_ALLOWED_USERSto restrict who can interact with the bot. Without it (and withoutCHATTO_ALLOW_ALL_USERS=true), the gateway denies all users by default as a safety measure. Only add logins of people you trust — authorized users have full access to the agent's capabilities, including tool use and system access.
For more information on securing your Hermes Agent deployment, see the security documentation of your Hermes Agent distribution.
chattolib[realtime]>=0.4.20.post1, which is vendored — nothing to install.MIT — see LICENSE.