website/docs/user-guide/bot-mode.md
Bot Mode turns your Hermes profiles into a roster of named Bots. Each Bot has its own role, model, memory, skills, and avatar; Bots run recurring routines, deliberate together in group chats, and message each other directly. Build a specialist Bot once and it is there forever, one click away.
Bot Mode ships built into the desktop app and is on by default — no install needed. It appears as a Bots tab next to Sessions in the left sidebar, with a Routines tile docked beside the conversation while the Bots tab is active.
:::tip A Bot is a profile
There is no new primitive to learn: a Bot is a Hermes profile — isolated config, memory, skills, credentials, and chat history under ~/.hermes/profiles/<name>/. Bot Mode is a UI over that primitive, so everything you do in it is visible from the CLI too: hermes -p <bot> chat opens the same agent, and Bot routines appear in hermes cron list. No core patches, no background daemons, no extra storage.
:::
The roster shows one row per agent profile: avatar, latest-message preview, and timestamp.
:::note The canonical Bot Chat is a forever-chat
Typing /new (or /reset) inside a Bot's canonical chat would fork the relationship into a scratch session — the one thing Bot Mode promises never happens. The composer reroutes it to /compact instead: fresh working context, same conversation. Regular sessions on the same profile keep full /new freedom.
:::
Hit New Agent in the roster. The quick path is three fields — Name, Title, Description — and the Bot exists in seconds, introducing itself as the first message of its new Bot Chat.
An Advanced disclosure opens the full capabilities surface:
With more than one connection registered in Settings → Connections, the New Agent dialog grows a Create on picker. Pick a device and the profile is created on that machine's backend — your window never switches gateways. The new Bot then appears in the roster as a Connections Bot (with an @name-device handle when the name exists on several machines), and chatting with it routes to its own machine.
With a single connection (the common case) the picker is hidden and the Bot is created on the machine you're connected to — exactly the old behavior.
Remote-creation notes:
default) — a remote box doesn't have your local profiles to clone.Edit Profile (right-click a Bot) reopens the same surface on the live profile any time: avatar, title, description, model pin, skills, toolsets, MCP servers, and the full SOUL.md.
Duplicate (right-click) makes a full clone of a Bot — config, skills, SOUL.md, memory, and its look. Delete Profile permanently removes one, behind the same destructive confirmation the desktop's profile menu uses; the default profile cannot be deleted.
Every Bot gets a face:
image.generate RPC and works over both local and remote gateways).hermes pets in a terminal to explore the gallery.A Bot's look, title, and description are stored in the profile's metadata on the backend, so the same Bot appears the same way on every desktop connected to that backend.
The Routines pane attaches recurring tasks to the Bot that does them — "summarize my inbox every morning" lives next to the Bot responsible for it. The pane docks beside the chat only while the Bots tab is active and steps aside when you switch back to Sessions (older desktop builds keep it always visible). A structured schedule picker builds the schedule (frequency first, then only the detail that matters), with an Advanced field exposing the raw Hermes schedule string.
Routines are plain Hermes cron jobs namespaced [bot:<name>] <routine> — they also show up in hermes cron list and the core Cron page. Runs land in the Bot's own chat history, so the result is right where you would talk to that Bot anyway.
Right-click a local Bot → Manage groups to add or remove it from any number of group chats. Pick existing groups independently or create one inline. Local membership is stored in the Bot's backend-synced profile metadata, so it follows that profile across desktops; older profiles with one legacy group continue to work. Connections Bots join through the New Group Chat picker and remain source-qualified in the room's shared state.
Rooms follow your gateways, not one Desktop. Each room's recent transcript, members, picture, and name are mirrored into the shared profile metadata of every gateway your Desktop is connected to, with per-gateway versioning so two Desktops writing at once merge instead of overwriting each other. Open Hermes Desktop on another machine against the same gateway (local network, Tailscale, anywhere) and the room appears with its history; gateway-only clients see it too. Rooms carry a durable internal identity, so renaming one changes just its display name everywhere, disbanding one removes it permanently on every client — even ones that were offline at the time — and recreating a same-name group starts a genuinely fresh room. If a gateway dies or is removed, nothing is lost: every connected Desktop keeps the full room locally and re-seeds any gateway it reconnects to. (The full orchestration log stays in each Desktop's local storage; the shared mirror is a bounded recent-history projection.)
Groups are standalone rows in the same activity-ordered roster as Bot DMs. A Bot keeps one DM row even when it belongs to several groups, while every group gets its own room row with member count, latest-message preview, timestamp, and needs-you state.
Open chat on any group row (2–6 Bots) opens a shared room where the whole group coordinates:
@name, and escalate real judgment calls to you with @user — the group row shows a needs you badge when that happens.Group: <name> session, so room context survives like any other conversation.Group: <name> session there. Cross-machine members carry a device badge (dixie · Mac Mini) in the room and in other members' transcripts, and the disambiguated @name-device handle works in room mentions — so same-named agents on two machines never blur together.Bots message each other with attribution, and you can hand work off from any chat:
@researcher have a look at this in any chat and the composer's @ autocomplete helps you pick the right Bot; on send, the mention is resolved against the live roster and the active Bot is told exactly who you mean (profile, friendly name, and device for cross-connection Bots). The Bot then composes its own message and sends it with message_agent — your text is never forwarded verbatim, and the reply comes back attributed to that agent. An email address or an unknown @ passes through untouched. Bots on other connected machines are reachable the same way: the Desktop relays the message over that connection's own socket (see Bots across machines below).hermes profile rename) and it becomes taggable by that name: a Bot titled Research Buddy answers to @research-buddy (and @researchbuddy), in regular chats and in group rooms alike. The composer's @ autocomplete offers the renamed tag and also matches when you type the old profile name, which keeps resolving too.message_agent tool: a Bot messages a teammate by calling message_agent(target="researcher", message="…"). The tool validates the target against the live roster, prefixes the sender's Message from 🤖 <sender> (@<sender>): attribution automatically, and delivers into the teammate's canonical Bot Chat. Delivery is fire-and-forget: the sender gets an acknowledgement, finishes its turn, and the reply arrives later as a background completion notification. The message travels as a real parameter (nothing shell-interpreted — quotes, $(...), and backticks arrive verbatim), and the Bot composes its own message rather than forwarding your words. The teammate roster — names and roles from each profile's title/description — is part of every Bot Chat's system prompt, so Bots know who does what before choosing a recipient. The tool exists only in canonical Bot Chat sessions on Bot-Mode-managed installs; regular chats, group-room member sessions, and CLI sessions never see it.The backend teaches each Bot's canonical Bot Chat session the messaging protocol automatically at prompt-build time — including when a teammate opens it headlessly from the CLI. Only the canonical Bot Chat gets the protocol section; your regular sessions and your SOUL.md stay untouched. This is controlled by agent.bot_mode_protocol in config.yaml (default: on):
agent:
bot_mode_protocol: true # inject the bot-to-bot messaging protocol into canonical Bot Chats
:::note Bot-to-bot delivery is per-invocation: the receiving Bot picks the message up when it next runs. Live interrupt of a Bot mid-conversation is future work. :::
A failed delivery turn is retried at most once, and only when a retry can actually help. Transient failures (target runtime offline, delivery timeout, provider rate limit or server error) re-run the same Bot Chat session unchanged. A context-overflow failure also re-runs the same session — the retried turn compacts the over-threshold transcript via the standard context-compression pass before calling the model, so the retry fits where the original didn't. Auth, quota, and configuration failures never auto-retry: a second attempt cannot fix them and only burns quota, so the failure is surfaced immediately. A retried turn never starts a fresh session — your Bot Chat history and context stay intact.
A failed bot turn or relay delivery carries a machine-readable reason code alongside the human error text, end to end: the target gateway classifies the failure (provider_auth_or_access, provider_quota_limit, provider_rate_limit, provider_server_error, context_overflow, missing_config, model_unavailable, runtime_offline, queued_expired, delivery_timeout, target_busy, unknown), the Desktop forwards it, and the sending agent's completion notification is tagged [reason: <code>] ahead of the error text. A calling agent can branch on the code — "sign in again" vs "retry later" — instead of parsing provider prose. The Desktop's needs-attention badge uses the same codes.
Every gateway you register in Settings → Connections — local, remote URL, SSH, Hermes Cloud, docker — is a persistent line the Desktop holds open, and Bot Mode uses those lines for messaging automatically. No extra setup:
message_agent reaches them directly. A Bot on your laptop messages the cloud agent with message_agent(target="moxie", …) exactly like a local teammate. If the same handle exists on several machines, disambiguate with target="moxie@<connection>" (the tool's error tells the Bot the exact forms). Delivery rides the Desktop: the sending gateway queues the message, the Desktop relays it to the target connection's own gateway, the target Bot runs a turn in its canonical Bot Chat, and the reply comes back to the sender as the same background completion notification local DMs use.hermes peer, below) — the two routes coexist.hermes peer)Bots on one machine can message Bots on another machine's gateway without any desktop in the loop. Register the other gateway as a peer (its API server URL + API_SERVER_KEY):
hermes peer add spark --url http://spark.lan:8377 --key <API_SERVER_KEY>
hermes peer list
hermes peer dm spark < /tmp/dm.txt # message body from a file (nothing shell-interpreted)
hermes peer dm spark/researcher < /tmp/dm.txt # named profile on a multiplexed peer
hermes peer dm delivers into the remote agent's canonical Bot Chat over the peer's existing API server, runs one agent turn there, and prints the reply on stdout — the exact cross-machine twin of the local hermes -p <bot> chat command.
Once a peer is registered, the messaging protocol taught to every Bot Chat (agent.bot_mode_protocol) automatically includes the peer roster, and message_agent accepts peer targets directly — message_agent(target="spark/researcher", …), or target="spark" for the peer's main agent — so your bots learn on their own that teammates exist on other machines and how to reach them. Registering or removing a peer refreshes each Bot Chat's protocol on its next message (capability epoch).
Requirements: the peer machine runs the api_server gateway platform with a strong API_SERVER_KEY; reachability is your network's business (LAN, Tailscale, VPN). The key is a credential and lives in ~/.hermes/.env as HERMES_PEER_<NAME>_KEY; peer names/URLs live in config.yaml under bot_peers.
When you register several backends in Settings → Connections — the local runtime, remote gateways, SSH hosts, Hermes Cloud instances — the roster shows the Bots from every connected source, persistently: SSH sources are inventoried without spawning anything on the remote box, and machines that are momentarily unreachable keep their last-known rows instead of vanishing. When the same profile name exists on several sources, handles disambiguate as @name-device (for example @research-homelab). A Bot's chats, sessions, memory, and routines live on the machine that owns the profile.
Clicking a Connections Bot does not hop your window onto that machine — stay in your chat and @mention it, seat it in a group chat, or create new agents on it directly with the Create on picker. Cloud and local agents share one roster this way: register your Hermes Cloud instance and your desktop (say, over Tailscale or SSH) and their Bots can message each other and sit in the same rooms, with each agent's work running on its own machine. Bot-to-bot DMs across those machines go through the Desktop relay automatically (see Messaging across connected machines above).
See Connecting Desktop to Many Hermes Instances for the full multi-connection guide.
Bot Mode is a bundled desktop plugin. Flip it off in Settings → Plugins → Bots — the roster, the Routines pane, and the composer middleware unregister live, no restart needed. Your profiles, sessions, and cron jobs are untouched either way; Bot Mode never owns your data, it only renders it.
There is also a preference to hide the canonical Bot Chats from the regular sidebar session list, so they only appear inside the Bots pane. (This uses the core hidden-session flag; on older gateways the chats simply stay visible.)
Because Bots are profiles, everything has a terminal equivalent:
| In Bot Mode | From a shell |
|---|---|
| Chat with a Bot | hermes -p <bot> chat |
| A Bot's files, skills, memory | ~/.hermes/profiles/<bot>/ |
| Routines | hermes cron list (jobs named [bot:<name>] …) |
| Create / inspect profiles | hermes profile create, hermes profile list |
See Profiles for the underlying primitive and Profile Commands for the full CLI reference.