Skip to content

Repository files navigation

Hermes Mobile

Your Hermes agent on your phone. One HTML file, on your own private network.

A single-file client for a self-hosted Hermes gateway. Installs from Safari or Chrome as a real home-screen app, talks straight to the dashboard API, and adds no state of its own — the gateway's database stays the single source of truth across CLI, Telegram, desktop and phone. Start a conversation at your desk, carry it to the sofa, finish it from another country.

Mobile-first, and it expands to a proper desktop layout above 700px.

Chat home screen Diary tab — the week ahead, grouped by day Alerts tab — a source asking permission to notify you Profile picker — every profile, with its model and skill count

Chat · the week ahead · a source asking permission to notify you · every profile with its model

It never touches the public internet

This is not an app you expose to the world, and it is not an app that phones home. It runs over Tailscale, on your own private network, and that is a requirement rather than a suggestion.

What that buys you:

  • No open ports. No domain. No certificates. No reverse proxy. Nothing on your server is reachable from the internet, so there is no login page for anyone to find, and no attack surface to harden.
  • Your conversations stay between your devices and your box. Traffic is end-to-end encrypted between machines you own. No third party sees your prompts, your files, your diary or your agent's output — because none of it crosses anyone else's infrastructure.
  • No account with us, because there is no us. This is a static HTML file on your own server. There is no service behind it, no telemetry, and nothing to sign up for.

The whole install is six lines and no networking knowledge (see below). If you'd rather use your own reverse proxy, you can — but then TLS, authentication and exposure are yours to get right, and getting them wrong puts an authenticated agent with shell access on the open internet. Tailscale is the recommended path for a reason.

Your agent, wherever you are

Streaming replies with a live thinking indicator and tool-call chips, so you can see what it's doing rather than watching a spinner. Attach images, PDFs or files. Dictate with the mic — including the iOS quirk where Safari re-commits dictated text over a cleared box, which took three attempts to beat. Have answers read back to you.

Steer without interrupting. Mid-run, injects a note into the agent's next tool result instead of stopping it — the difference between "no, the other repo" and losing five minutes of work.

A context gauge that isn't decorative. The gateway computes real occupancy, compression-aware; the client just shows it. Grey, amber at 70%, red at 88%, and tapping it gives exact token counts. It never invents a number when the engine reports none.

Eight conversations at once

Tabs across the top, each with its own live WebSocket state. Background chats keep streaming while you read another; a pulsing dot means running, green means it finished while you were away. Desktop Hermes doesn't do this.

Every profile, one list

Hermes keeps sessions in a separate database per profile, so anything that only reads the default profile is blind to most of your history. This client aggregates across all of them: one recent list, tagged by profile, full-text search fanned out across every database, paged 60 at a time, and any session resumable in the profile it belongs to.

That last part is fiddlier than it sounds — resuming a session means telling the gateway which profile's home to load, or the agent comes back without its skills, model or prompt. Handled, including across the WebSocket drops that phones do constantly. A resume that genuinely fails says so and offers Retry; it never silently starts a fresh conversation somewhere else.

Your week, one tap

The 📅 tab shows the next seven days grouped under day headers — "Today — Wed 15 Jul", "Tomorrow", then dates — with a freshness stamp and a refresh button, because a diary that quietly serves you stale data is worse than one that admits it's stale.

The calendar itself is yours to plug in: drop a calendar_source.py next to the sidecar exposing one function, fetch_calendar(). A worked Google example ships in examples/. Without one the tab says so politely and everything else carries on.

Notifications that ask permission first

The part worth stealing. Any script, cron or Hermes skill can POST /notify — but an unknown sender doesn't reach your phone. It's held pending, you get one meta-push a day ("「backup-monitor」 wants to send you alerts"), and you approve or mute it in the app, exactly like an iOS permission prompt. Approving flushes the queue; muting discards it silently.

An Alerts tab gives you a master switch and one toggle per sender. Master off drops approved senders and registers new ones quietly, so turning it back on doesn't bury you.

Beyond that: a "day ahead" push each morning, a reminder before each timed event, and a mount-aware service worker that focuses your existing window instead of opening a fourth copy.

Switch model mid-conversation

Tap the model pill and the conversation you're in changes model — not the next one. The gateway has no model.set RPC, so this drives the same /model --session path the CLI uses, deliberately scoped to the session: a tap on your phone must never rewrite the global default in config.yaml. Reasoning effort is set per new chat and labelled as such.

The rest

Sessions browse, search and page, with a two-tap delete. Crons across every profile (read-only — the gateway's cron RPC can't scope mutations to a profile, so an editor here would be a trap). The file browser, gateway status, and tool-approval prompts with once / session / always / deny.

Screen wake lock while an answer is streaming, so iOS auto-lock stops killing the socket mid-reply.

How this compares

You have options, and it's worth knowing them before you install anything.

Hermex is a native iPhone app on the App Store — free, polished, and genuinely good at what it does: chat, kanban boards, attachments, workspace browsing, LaTeX, multi-server switching, offline caching of recent sessions. If you want a native app from the App Store and you're on iOS 18 or later, use it. It's not affiliated with Nous Research, and it isn't open source.

Official mobile doesn't exist yet. There are three competing pull requests proposing React Native shells around the desktop renderer, open since June 2026, awaiting an architecture decision. It'll land eventually and it'll probably be good.

This is a different trade. Where it differs, and why you might want it:

Hermes Mobile Native app
Can you read it? One HTML file, ~1,900 lines, no build step, no framework, and it loads nothing from the internet A compiled binary you point at a server where your agent has shell access
What's exposed? Nothing. Tailnet-only by design Needs your server "reachable" — usually the public internet
What runs it? Any modern browser: iPhone, Android, Windows, Mac, Linux iOS 18+, iPadOS, visionOS
Install Copy six files, six lines of tailscale serve App Store
Notifications Per-source permission gate, master switch, urgent-only routing
Diary Week ahead from your own calendar source, morning push, event reminders
Parallel chats Up to 8, each streaming in the background
Profiles Every profile's sessions in one searchable list

The honest summary: a native app will always feel more native. This is for people who'd rather run something they can audit in one sitting, on a network nobody else can reach, and who want the notification and diary machinery that nothing else has.

What it deliberately isn't

  • No accounts, no backend of its own, no build step, no framework. One HTML file you can audit in a sitting.
  • No offline mode — it's a thin client; the agent is the product.
  • No public internet exposure, ever. Tailnet-only is the design, not a default you can casually override.

Requirements

Four things the app needs. Miss one and the failure is usually silent, so they're worth reading once:

  1. Same origin as the gateway. Every request is credentials: "same-origin" against the dashboard's /api/*, and the WebSocket is built from location.host. The PWA and the gateway must be one origin. Any reverse proxy can arrange this; Tailscale serve is simply the shortest way.
  2. One mount path, and not the site root if you want push. Serve the six files together under a single path. Everything derives that path at runtime, so /m, /mobile or /phone all work (see below). The sidecar's routes live at <mount>/api, which collides with the gateway's own /api if the mount is / — so with push enabled, mount somewhere.
  3. HTTPS. Service workers and Web Push require a secure context. Over plain http:// the chat works and push silently does not. Tailscale serve gives you TLS for free; http://localhost also counts as secure for local testing.
  4. An auth provider on the gateway (e.g. HERMES_DASHBOARD_BASIC_AUTH_USERNAME / _PASSWORD) — the dashboard refuses to start unauthenticated off loopback anyway.

Mounting somewhere other than /m

index.html and sw.js work the mount path out for themselves (from location.pathname and the service worker's registration scope), and the manifest's icons are relative. Only manifest.json's start_url/scope need the literal value — they can't be relative, because a relative one resolves with a trailing slash and would then fail to match the page:

sed -i 's#"/m"#"/mobile"#g' manifest.json     # start_url + scope

Then mount the files under /mobile instead. Nothing else changes.

Install (Tailscale, ~6 lines, no domain, no certs, no reverse proxy)

On the host running your Hermes gateway (dashboard on 127.0.0.1:9119):

git clone https://github.com/Talaria-D/Hermes-Mobile-App /opt/hermes-mobile
tailscale serve --bg --set-path /              http://127.0.0.1:9119
tailscale serve --bg --set-path /m             /opt/hermes-mobile/index.html
tailscale serve --bg --set-path /m/sw.js       /opt/hermes-mobile/sw.js
tailscale serve --bg --set-path /m/manifest.json /opt/hermes-mobile/manifest.json
tailscale serve --bg --set-path /m/icon-180.png /opt/hermes-mobile/icon-180.png
tailscale serve --bg --set-path /m/icon-512.png /opt/hermes-mobile/icon-512.png

Then on your phone (with Tailscale connected): open https://<your-machine>.<tailnet>.ts.net/m, sign in with your Hermes dashboard credentials, Share → Add to Home Screen. Done — TLS, auth, and reachability are all Tailscale's problem, which is to say: solved.

The gateway needs an auth provider configured (e.g. HERMES_DASHBOARD_BASIC_AUTH_USERNAME / _PASSWORD) — an unauthenticated non-loopback dashboard refuses to start by design.

Optional: diary + push sidecar

cd /opt/hermes-mobile && python3 -m venv venv && venv/bin/pip install pywebpush
cp examples/calendar_gog.py calendar_source.py   # then adapt fetch_calendar() to YOUR calendar
PWA_PUSH_EMAIL=you@example.com venv/bin/python sidecar.py
tailscale serve --bg --set-path /m/api http://127.0.0.1:9210

VAPID keys generate themselves on first run. Wire POST /m/api/run to a cron (a Hermes script-cron keeps it visible in Hermes) for reminders. In the app: Alerts tab → Enable push.

Point any script at POST /notify {"source":"my-script","title":"…","body":"…"} — the first-use approval gate does the rest.

Without Tailscale

Any reverse proxy works, so long as it puts the gateway and the PWA on one origin. The shape, in nginx terms:

location /            { proxy_pass http://127.0.0.1:9119; }   # gateway (needs WebSocket upgrade headers)
location /m           { alias /opt/hermes-mobile/index.html; }
location /m/api/      { proxy_pass http://127.0.0.1:9210/; }  # sidecar, if used
location /m/          { alias /opt/hermes-mobile/; }             # sw.js, manifest, icons

The / proxy needs the usual proxy_set_header Upgrade/Connection pair, or the chat WebSocket won't connect. TLS is on you here — see requirement 3.

Browser support

Core — chat, sessions, profiles, crons, files — works anywhere modern. The extras are feature-detected individually and degrade quietly rather than breaking the app:

Chrome / Edge Safari Firefox
Chat, sessions, crons, files
Install as an app desktop only
Push notifications iOS 16.4+, installed to home screen only
Screen wake lock while answering 16.4+ ❌ (one-time note suggests Auto-Lock → Never)
Voice dictation ❌ (button hidden)
Read answers aloud

iOS is the strictest target, not the intended one — Android and desktop have fewer conditions on push, not more.

Compatibility

This client drives the Hermes dashboard's internal API (/api/sessions, /api/profiles/sessions, /api/ws JSON-RPC, …). Internal means it can change between Hermes releases without notice. Tested against Hermes v0.18.0 and v0.19.1 ("Quicksilver").

smoke.sh is the compatibility gate: it probes every endpoint the PWA depends on, unauthenticated (live routes answer 200/401/422; removed routes answer 404/405). Run it against a candidate gateway before upgrading Hermes:

./smoke.sh http://127.0.0.1:<candidate-port>

Exit 0 → safe to upgrade. Exit 1 → the contract moved; patch the PWA (or wait) first.

What's actually been tested

Worth being straight about, so bug reports land in the right place:

  • Tested in anger: iOS Safari as an installed home-screen PWA, against Hermes v0.18.0 and v0.19.1, served over Tailscale, gateway in Docker. Daily driver for months.
  • Should work, untested: Android, Windows, desktop browsers generally, non-Tailscale reverse proxies, mounts other than /m, and Hermes installed natively rather than in Docker. Nothing in the client knows or cares about any of that — it's one static file speaking HTTP to the dashboard, and the platform features above are all standards with feature detection around them — but "no reason it shouldn't" is not the same as "someone ran it". Reports welcome.

Security posture

  • Designed for tailnet-only serving; nothing here should ever face the public internet.
  • The PWA holds no secrets — auth is the gateway's session cookie, push keys live server-side with the sidecar.
  • The sidecar binds loopback (plus whatever you put in PWA_PUSH_BINDS, e.g. a docker bridge IP so in-container skills can notify) and stores push subscriptions in a local JSON file, git-ignored.
  • Single-file client: index.html is the entire attack surface reviewable in one sitting.

License

MIT — see LICENSE.

Releases

Packages

Contributors

Languages