Skip to content

Architecture

Connection Model

The app maintains two independent connection paths — direct HTTP/SSE for chat, persistent WSS for relay channels.

PathProtocolServerPurpose
ChatHTTP/SSEAPI Server :8642Streaming conversations via Sessions API
TerminalWSSRelay Server :8767Remote shell via tmux (Phase 2)
BridgeWSSRelay Server :8767Device control via AccessibilityService + MediaProjection (Phase 3)
NotificationsWSSRelay Server :8767NotificationListenerService forwards posted notifications over a bounded channel

The bridge channel was consolidated onto the unified relay port :8767 in v0.3 — the legacy standalone android_relay.py service on port 8766 is retired.

Key Components

ComponentPurpose
HermesApiClientDirect HTTP/SSE client for Hermes API Server
ChatHandlerMessage state management and streaming event processing
ChatViewModelSession CRUD, message sending, personality selection
ConnectionViewModelDual connection model, API client lifecycle, settings
ConnectionManagerWebSocket connection for relay (bridge/terminal)
ChannelMultiplexerEnvelope routing for relay channels
AuthManagerAPI key and session token storage (encrypted)
ConnectivityObserverNetwork connectivity monitoring

Chat Message Flow

  1. User types a message in ChatScreen
  2. ChatViewModel creates a session (if needed) via POST /api/sessions
  3. Message sent via POST /api/sessions/{id}/chat/stream
  4. HermesApiClient receives SSE events on OkHttp thread pool
  5. Events dispatched to main thread via Handler
  6. ChatHandler updates StateFlows (messages, streaming, tools)
  7. Compose UI recomposes from StateFlow changes

SSE Event Pipeline

The Hermes API Server streams events using Server-Sent Events. Each event type maps to a specific UI update.

EventHandler Action
session.createdInitialize session context (session_id, run_id, title)
run.startedRecord run start, capture user_message object
message.startedCreate assistant message placeholder from message object (id, role)
assistant.deltaAppend text delta to streaming message
tool.progressAppend reasoning/thinking delta to message
tool.pendingCreate tool progress card (queued state)
tool.startedUpdate card with start time, preview, args
tool.completedMark card as done with result_preview
tool.failedMark card as failed with error
assistant.completedFinalize message (content, completed, partial, interrupted flags)
run.completedEnd streaming state (completed, partial, interrupted, api_calls)
errorDisplay error banner (message, error)
doneClose SSE connection (state: "final")

Relay Auth Flow

The relay connection (bridge/terminal) uses a pairing code for initial setup, then session tokens for persistence.

Pairing codes use the full A-Z / 0-9 alphabet (36 chars). The pair command (/hermes-relay-pair skill or hermes-pair shell shim) on the Hermes host mints the code and pre-registers it with the relay via a loopback-only /pairing/register endpoint before embedding it in the QR — so the phone never types a code by hand. Session tokens are stored in EncryptedSharedPreferences backed by Android Keystore.

Direct API vs Relay

AspectDirect API (Chat)Relay (Bridge/Terminal/Notifications)
ProtocolHTTP/SSEWSS
ConnectionPer-requestPersistent
AuthBearer token (optional)Pairing code + session token
ServerHermes API :8642Unified Relay :8767
StateStatelessChannel-multiplexed