feat(bot-mode): bots on every Desktop connection can message each other - #92784
Conversation
Connections ARE the peer set: every gateway connected to the Desktop (local, remote URL, SSH, Hermes Cloud, docker) is now message_agent- reachable. The Desktop relays over the persistent sockets it already holds — roster sync per connection, envelope drain/deliver/reply loops — so cross-connection DMs work exactly like local ones, replies included. Also fixes the legacy-SOUL gate bug: profiles whose SOUL.md carries the old plugin-appended protocol silently lost the message_agent tool because the injection/execution gates keyed on protocol-section non-emptiness instead of managed-install.
૮ >ﻌ< ა ci reviewran on a0c4b45 — feat(bot-mode): bots on every Desktop connection can message
|
Live E2E evidence (no mocks anywhere in the chain)Level A — relay protocol over real WebSockets. Two real
Level B — the shipped plugin loops as the courier. Real Electron desktop (dev server + CDP, worktree backend via
PASS criteria were strict: the new envelope id's reply file (a first run matched a stale reply file from Level A — invalidated, dirs swept, rerun), correct answer + codeword in the reply, and the unique DM present in the receiving profile's state.db. Field notes from the run: desktop resolves its local backend via PATH |
…tempfile Widen the DM tempfile-leak fix (#91902/#92407) to the sibling sites PR #92784 introduced: - tools/bot_relay.py: expose the 6h stale sweep as cleanup_bot_relay_artifacts() (cleanup_*_cache contract) and wire it into gateway housekeeping — previously it ran only when the Desktop drained the outbox, so plaintext envelopes/replies queued while the Desktop was away could sit on disk forever. - tui_gateway/methods_bot_relay.py: move the payload write inside the try/finally so a failed write no longer leaks hermes-relay-dm-*.txt. - tools/bot_mode_dm.py: _spawn_delivery takes dm_file=None for relay waiter deliveries, which have no plaintext DM tempfile to reclaim.
…tempfile Widen the DM tempfile-leak fix (NousResearch#91902/NousResearch#92407) to the sibling sites PR NousResearch#92784 introduced: - tools/bot_relay.py: expose the 6h stale sweep as cleanup_bot_relay_artifacts() (cleanup_*_cache contract) and wire it into gateway housekeeping — previously it ran only when the Desktop drained the outbox, so plaintext envelopes/replies queued while the Desktop was away could sit on disk forever. - tui_gateway/methods_bot_relay.py: move the payload write inside the try/finally so a failed write no longer leaks hermes-relay-dm-*.txt. - tools/bot_mode_dm.py: _spawn_delivery takes dm_file=None for relay waiter deliveries, which have no plaintext DM tempfile to reclaim.
Summary
Bots on every Desktop connection can now message each other:
message_agentreaches agents on any gateway the Desktop holds a socket to (local, remote URL, SSH, Hermes Cloud, docker) — connections ARE the peer set (Teknium ruling, Aug 2026). Also fixes a gate bug where profiles with the legacy plugin-appended protocol in SOUL.md silently lost themessage_agenttool.Root cause of the reported dead end (user screenshot: cloud bot @-mentioned, agent replies "agent messaging isn't available here"): cross-connection roster rows were visible and taggable but had no delivery route —
message_agentonly knew local profiles andhermes peertargets, and the injection gate keyed on protocol-section non-emptiness, which the legacy-SOUL dedupe empties.How it works
The Desktop already holds a persistent authenticated line to every connected gateway. Two plugin loops use those lines:
bot_relay.roster.sync) → Bot Chat prompts list them ("Teammates on OTHER connected machines"), capability epoch refreshes eternal chats on change.bot_relay.outbox.drain), delivers each on the target connection's own socket (bot_relay.deliver— same one-turn Bot Chat transport local DMs use), posts the reply back (bot_relay.reply) where a waiter wakes the sender via the standard completion-notification path.Gateways never see each other's credentials; the Desktop is the courier. Ambiguous handles disambiguate as
handle@connection(the tool's error teaches the exact forms). Older backends without the RPCs are skipped per-call.Changes
tools/bot_relay.py(new): roster store + validation, target resolution, atomic outbox claim, reply files, waiter command.tools/bot_mode_dm.py: relay route inmessage_agent(peer → local → relay, incl. same-name-on-other-connection); gates now key onis_bot_mode_managed()— fixes the legacy-SOUL tool loss.tools/bot_mode_probe.py:is_bot_mode_managed(); remote-teammates paragraph in the protocol section; relay roster in the capability fingerprint.tui_gateway/methods_bot_relay.py(new) +server.py: 4 RPCs, all on the RPC pool (deliver runs a full agent turn).apps/desktop/src/plugins/hermes-bots/plugin.js: relay worker loops (start/stop via register/onDispose, feature-detected, degrade gracefully); mention-middleware note now names the cross-connectionmessage_agenttarget.website/docs/user-guide/bot-mode.md— new "Messaging across connected machines (the Desktop relay)" section; @mention + cross-machine sections updated.Validation
message_agentTests: 50 Python (13 relay + 5 RPC new; all bot_mode_dm/probe suites green), 368 plugin source-shape tests (5 new). E2E: real two-install round-trip — roster sync →
message_agentack → envelope claim → deliver RPC (argv contract) → reply → real waiter subprocess prints "Reply from @ops on ssh-b: … All green"; error path prints the delivery failure distinctly.Infographic