Skip to content

feat(mcp): optional hermes domain to read and message the owner's Hermes - #1516

Merged
Juliusolsson05 merged 5 commits into
batch/hermes-gaps-2026-10-15from
feat/hermes-mcp-domain
Oct 15, 2026
Merged

Juliusolsson05 merged 5 commits into
batch/hermes-gaps-2026-10-15from
feat/hermes-mcp-domain

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Fixes #1512

Problem

The owner runs a personal Hermes agent on a VPS, and Agent Code already holds the signed-in connection to it. Coding agents (Claude, Codex, OpenCode, Grok, Pi) had no way to ask Hermes something or to read what it said.

What changed

A new built-in MCP domain, hermes:

  • hermes_conversations: the connection, whether a Telegram thread exists, the agent's own conversation id, and recent conversations.
  • hermes_read: the last N messages as text, with one line per tool Hermes ran.
  • hermes_send: one queued submit, which never interrupts a running turn. By default it waits for the stored reply (5 min default, 15 max) and returns the reply plus the tools Hermes ran. Other outcomes:
    • awaiting_approval, with the request's own words, when Hermes asks for approval. The request is left for the owner and never answered.
    • held, when the user's phone turn is busy on the Telegram thread. Nothing is sent.
    • still_running on timeout.

Defaults (UNCONFIRMED product calls; see the plan):

  • Off by default. It is enabled per provider in Settings → MCP. It is parent-held-only and not confirmation-gated.
  • Own conversation. Each agent writes to a conversation of its own, titled Agent Code · <its Goal>, never the Telegram thread unless asked. The agent→conversation map lives in main (hermes/agent-conversations.json), and a deleted conversation is adopted again.
  • One server only. It never guesses between two signed-in servers. No credential reaches the model: main dials with the stored sign-in.

Design

  • One short-lived client per call. Each call opens one HermesHeadless (the client the panes use) and stops it, so queued submits, lane holds and history reconciliation behave exactly as in a pane. Nothing stays connected between calls.
  • Reply from committed rows. The reply is read from the rows committed after the agent's own user row, by position, because tool rows carry no row_id.

Tests (recorded Hermes v0.22.0 sessions replayed by a fake server)

  • S4: send → own conversation created with the agent's title → queued: true → the stored reply "All three tools ran." and 3 tools. A new bridge instance then reads it back through session.resume, not a second create.
  • S10: awaiting_approval carries "delete in root path: rm -r …". The bridge never answers it.
  • Tool boundary, through a real MCP client:
    • the three tools appear only with the domain on, and instructions are withheld without a bridge;
    • errors are curated, with raw errors hidden;
    • the domain policy is pinned: configurable, parent-held-only, not shipped on, not offered to Hermes itself.

Checks:

  • npm run typecheck: passes.
  • vitest src/mcp src/main/hermes: 171/171.

Verification boundary

The app was not launched, and the owner's real VPS was not called.

Review round (two reviewers, one round)

All findings are fixed. The full list is in docs/plans/2026-10-14-hermes-mcp-domain.md.

  • Reply binding. hermes_send returns only its own reply. It finds its user row by id, or by text when Hermes queued it (recorded S20), and stops at the next message.
  • Waits fit inside MCP clients' own timeouts: 25 s default, 50 s cap, then still_running. A second send to one conversation reports busy after 10 s. A turn that ends without an answer reports ended.
  • No junk conversations. Unknown or deleted ids are checked over REST (latest-descendant) before any socket opens. That lookup also follows compression.
  • Map safety. The map is keyed by the agent's reload-stable identity. Writes are serialized with unique temp files (a reproduced race). A corrupt map is kept, not overwritten.
  • Privacy and attribution. Credentials are scrubbed from Hermes' tool lines and approval text. Every message is marked [Sent by an Agent Code agent: …] (UNCONFIRMED).
  • Side-effect-free Telegram lookup.

Tests: 44/44 in src/main/hermes + hermesTools. Every new guard is mutation-checked (6/6 caught). Not replay-tested: a lane hold (T4) and an interrupted turn (S7, interrupted by the client).

🤖 Generated with Claude Code

Juliusolsson05 and others added 5 commits October 14, 2026 15:06
Refs #1512

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r's Hermes

Agents get hermes_conversations, hermes_read and hermes_send through a
main-side bridge over the stored Hermes sign-in, so no credential reaches a
model. Off by default and parent-held-only. An agent writes to a conversation
of its own unless told otherwise. A send is queued, waits for the stored reply,
and reports an approval request instead of answering it. Tests replay recorded
Hermes sessions S4 and S10.

Refs #1512

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round on #1516. A send now finds its own user row by id, or by text
when Hermes queued it, and returns only the answer up to the next message.
Waits stay under MCP clients' own 60 s timeouts (25 s default, 50 s cap).
A turn that ends without text reports ended. Unknown or deleted ids are
checked over REST before any socket opens, so no junk conversations are
created. Map writes are serialized and corruption-safe, and the map is
keyed by the identity that survives an agent reload. Hermes' tool lines are
scrubbed of credentials, and every message says an agent sent it. The
Telegram lookup no longer has side effects.

Refs #1512

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Final disposition (merging via batch/hermes-gaps-2026-10-15, owner-approved 2026-10-15). Tested head ed6e16c2e: quality-gate and minimum-node-fixture-gate green. One review round (Claude + Codex), with all valid findings fixed before that head: reply binding for queued submits, bounded waits, serialized map writes, secret redaction. Caveat: the batch PR's own CI run against current main is the integration gate.

@Juliusolsson05
Juliusolsson05 changed the base branch from main to batch/hermes-gaps-2026-10-15 October 15, 2026 00:29
@Juliusolsson05
Juliusolsson05 merged commit b252521 into batch/hermes-gaps-2026-10-15 Oct 15, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the feat/hermes-mcp-domain branch October 15, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): optional hermes domain so agents can read and message the owner's Hermes agent

1 participant