Skip to content

Vanilla Hermes Chat Transport

Hermes-Relay talks to your Hermes server's own surfaces for chat — no Hermes-Relay relay plugin is ever in the chat path. By default it prefers the dashboard gateway (/api/ws, the same tui_gateway transport hermes-desktop and the TUI speak) when your Manage sign-in is ready, because that's the only Vanilla Hermes path with live thinking/reasoning as it streams. When the gateway isn't available — no dashboard auth yet, an older server, or a forced override — it falls back to the API server's SSE routes.

How It Works

Phone (WS)       → Hermes dashboard (:9119)    [preferred — gateway chat, live thinking]
Phone (HTTP/SSE) → Hermes API Server (:8642)   [fallback — sessions / runs / completions]

Both paths are vanilla upstream Hermes surfaces. The dashboard gateway /api/ws is not the Hermes-Relay relay (:8767); it's a vanilla dashboard endpoint, reached with a short-lived ticket minted from your Manage dashboard session. The optional Relay plugin is never involved in chat — it only adds terminal, device control, media, and the like.

When it falls back, the app uses the Hermes /api/sessions REST API:

MethodEndpointPurpose
GET/api/sessionsList sessions
POST/api/sessionsCreate session
GET/api/sessions/{id}/messagesGet message history
POST/api/sessions/{id}/chat/streamStream chat (SSE)
PATCH/api/sessions/{id}Rename session
DELETE/api/sessions/{id}Delete session
GET/healthHealth check

Authentication

If the Hermes server is configured with API_SERVER_KEY, the app sends:

Authorization: Bearer <API_SERVER_KEY>

The API key field in Settings is technically optional because Hermes can run an open local API server. For phone-reachable LAN, VPN, or public deployments, set API_SERVER_KEY and enter the same value in Android.

When provided, the key is stored in Android's EncryptedSharedPreferences using AES-256-GCM encryption backed by the Android Keystore.

SSE Streaming (fallback path)

On the API-server fallback, chat responses stream via Server-Sent Events with these Hermes-native event types. (On the preferred gateway path the same lifecycle arrives over the /api/ws WebSocket instead, with live reasoning.delta/thinking.delta as the model reasons — the API-server SSE surface only surfaces reasoning after the fact via tool.progress and the final run.completed messages.)

EventDescriptionKey Fields
session.createdSession initializedsession_id, run_id, title?
run.startedAgent run beginssession_id, run_id, user_message (object)
message.startedAssistant message beginssession_id, run_id, message (object with id, role)
assistant.deltaText content chunksession_id, run_id, message_id, delta
tool.progressReasoning/thinking chunksession_id, run_id, message_id, delta
tool.pendingTool queued for executionsession_id, run_id, tool_name, call_id
tool.startedTool execution startedsession_id, run_id, tool_name, call_id, preview?, args
tool.completedTool finished successfullysession_id, run_id, tool_call_id, tool_name, args, result_preview
tool.failedTool execution failedsession_id, run_id, call_id, tool_name, error
assistant.completedResponse finishedsession_id, run_id, message_id, content, completed, partial, interrupted
run.completedEntire agent run finishedsession_id, run_id, message_id, completed, partial, interrupted, api_calls?
errorError occurredmessage, error
doneStream closedsession_id, run_id, state: "final"

Why two paths?

Chat always rides vanilla upstream Hermes — never the Hermes-Relay relay plugin. The gateway /api/ws path is preferred because it's the only Vanilla Hermes surface with live reasoning streaming and full attachment support, matching what hermes-desktop and the Hermes TUI use. The API-server SSE path is the resilient fallback: it needs only the API server (no dashboard sign-in), works on older builds, and aligns with how other Hermes frontends talk to the API. The app probes both and picks the best available on each connect, so you get live thinking when your server can serve it and a working chat either way.