💜 Enjoying Hermes for Android? ☕ Support development on Ko-fi → — donations fund new features and keep the project alive. (GitHub Sponsors coming soon.)
A native Android client for the Hermes agent gateway — a phone-friendly companion to the Hermes Desktop app. It connects to a remote Hermes gateway over Tailscale, another private VPN, or your local network, and gives you full chat plus the management surface: sessions, models, profiles, scheduled jobs, usage analytics, messaging integrations, and settings.
A three-tab bottom bar (Chats · Agent Activity · You) sits on a ground-up Material 3 design system where each tenant profile has its own accent colour, so you always see which client you're acting as. The Agent Activity tab is a live Mission Control feed you can swipe between tenants.
Built with Kotlin and Jetpack Compose (Material 3).
No build tools or developer setup needed — just grab the signed APK from the latest release and install it on your phone:
- On your Android phone, open the release link above and tap the
.apkfile to download it. - When prompted, allow installs from your browser or Files app ("Install unknown apps").
- Open the downloaded
.apkand tap Install. - Launch Hermes and complete the one-time setup — gateway URL plus either a session token (loopback) or a username/password (password-protected dashboard).
The APK is signed with the project's release key. Because it's installed outside the Play Store, Android may warn about an app from an unknown developer — that's expected.
- Navigation & design — a three-tab bottom bar (Chats · Agent Activity · You) on a ground-up Material 3 design system: every tenant profile gets its own accent colour that tints the app chrome (a glanceable "which client am I in" signal), Rounded iconography, motion, and consistent shared components.
- Chat — a hybrid transcript (compact bubbles for you, full-width document-style agent
turns) with streaming responses and an animated typing indicator, model picker, slash
commands (
/…) with an inline command palette,@file mentions/path completion, image attachments, and tool-call approval/clarification prompts. Opening a thread lands on the newest message (and re-lands after rotation). - Mission Control (the Agent Activity tab) — a live, time-grouped activity feed (Live now · Upcoming · Recent) merging conversations and cron for the active profile, with quick links to Cron/Messaging/Usage/Agents; completed runs open their output. Profile-spatial: swipe left/right to move between tenants' activity, each painted in its own accent.
- Sessions — grouped by workspace with collapsible headers, search (instant title filter + gateway message-content search), pinning, and archive / unarchive / delete (swipe a row to archive; an Archived view from the top bar), plus a session-admin view. Opening a session lands on the newest reply.
- Profiles — switch the active tenant from a shared chip switcher on both the Chats and Agent Activity tabs, or manage them from the You tab; the active profile tints the chrome.
- Models — switch the active model on the fly from the chat top bar.
- Cron — list, create, edit, pause/resume, run-now, and delete scheduled jobs, with next-run times and run history.
- Usage — daily token chart and per-model breakdown.
- Messaging — enable/disable platform integrations with a guided setup flow for the required credentials.
- Settings — Server & token (edit the gateway URL and token, or username/password for a password-protected dashboard, then reconnect), appearance (System/Light/Dark + tool-call verbosity), memory, MCP servers, API keys/environment variables, and Diagnostics (a toggleable, token-redacted, shareable debug log). Configuration edits are written to the live gateway config.
- Reliability — automatic reconnect with offline banner and manual retry; an expired session re-authenticates automatically (password mode) or routes back to Setup (token mode).
| Layer | Notes |
|---|---|
| UI | Jetpack Compose + Material 3, ModalNavigationDrawer wrapping a Navigation-Compose NavHost |
| State | MVVM — ViewModel + StateFlow, collectAsStateWithLifecycle |
| DI | Hilt |
| Networking | OkHttp — REST + a WebSocket JSON-RPC stream to the gateway. Auth is either the loopback session token, or (gated dashboard) a cookie-jar session with 401 re-login and per-socket WS tickets |
| Local storage | EncryptedSharedPreferences (credentials: URL, token, username/password); DataStore (theme, tool-call display, diagnostics toggle) |
Package layout under app/src/main/java/com/hermes/client/:
data/ network clients, repositories, DTOs
di/ Hilt modules
domain/ app models (ChatMessage, Role, …)
ui/ one package per screen (chat, sessions, cron, usage,
messaging, models, profiles, settings, setup, admin, tools, nav, theme)
The launcher icon is an adaptive icon at app/src/main/res/mipmap-anydpi-v26/: the
Hermes Agent mascot (black line art on white, matching the desktop app). The foreground
is a PNG mipmap (mipmap-{m,h,xh,xxh,xxxh}dpi/ic_launcher_foreground.png) rather than a
vector — the source artwork is too path-dense for the runtime vector parser, which fails
to inflate it and falls back to the default system icon. The background is a solid-white
vector in app/src/main/res/drawable/.
- Android Studio (bundled JBR / JDK 21)
- Android SDK with
compileSdk36 / build-tools 36.x - A reachable Hermes gateway and credentials (a session token, or username/password for a password-protected dashboard)
Toolchain is pinned in gradle/libs.versions.toml: AGP 8.13.2, Kotlin 2.2.21,
Gradle 8.14.5. minSdk 26, targetSdk 36. These are the newest mutually-compatible
versions — AGP 9.x's built-in Kotlin integration is incompatible with applying the standalone
kotlin-android Kotlin Gradle plugin this project uses, and Hilt 2.59 requires AGP 9.x, so the
set is held here on purpose.
The root build.gradle.kts forces patched versions of a few transitive build-classpath
dependencies (gRPC/Google-Cloud tooling the Android Gradle Plugin bundles — netty,
protobuf, jose4j, jdom2, bouncycastle) to close known advisories. These are build-time only
and never shipped in the APK; the forces touch no app runtime dependency.
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
./gradlew :app:assembleDebug
# → app/build/outputs/apk/debug/app-debug.apkRelease signing reads a gitignored keystore.properties at the repo root:
storeFile=keystore/hermes-release.jks
storePassword=********
keyAlias=hermes
keyPassword=********If that file is absent (fresh clone, CI without secrets) the release build still succeeds but is left unsigned. To create a keystore:
keytool -genkeypair -v \
-keystore keystore/hermes-release.jks \
-alias hermes -keyalg RSA -keysize 2048 -validity 10000Then build:
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
./gradlew :app:assembleRelease
# → app/build/outputs/apk/release/app-release.apkThe keystore, keystore.properties, and all *.apk/*.aab/*.jks artifacts are
gitignored — signing secrets never enter the repo.
adb install -r app/build/outputs/apk/release/app-release.apkOn first launch the app shows a Setup screen. You can change any of these later from Settings → Server & token (URL, token, or username/password), then Save & reconnect.
- Gateway URL — where your Hermes dashboard is reachable from the phone, e.g.
http://100.x.x.x:9119(its Tailscale address) orhttp://192.168.x.x:9119on the same Wi-Fi. See Connecting below. - Auth — depends on how the dashboard is bound:
- Token mode — a dashboard bound to loopback (
--host 127.0.0.1) accepts the session token (sent asX-Hermes-Session-Token). Leave username/password blank. See Getting your token. - Password mode (gated) — a dashboard bound to a network address (e.g. its
Tailscale IP) refuses the loopback token and requires an auth provider, so set
Username + Password. The app logs in (
POST /auth/password-login), holds the rotating session cookie, and mints a per-connection WebSocket ticket automatically. Leave the token blank.
- Token mode — a dashboard bound to loopback (
Credentials are stored encrypted on the device. On an expired/invalid session (HTTP 401) the app re-authenticates automatically in password mode, or routes back to Setup in token mode.
For a loopback dashboard, the app authenticates with the dashboard session token. By default the gateway mints a new random token every time it starts, which is awkward to copy. For a stable token, set it yourself before starting the dashboard:
export HERMES_DASHBOARD_SESSION_TOKEN="choose-a-long-random-string"
# then start the Hermes dashboard / web gateway as usualUse that value as the Token in Setup. Treat it like a password — anyone with the token and network access to the gateway can drive your agent.
Recent Hermes refuses to expose the dashboard on a non-loopback address without a configured
auth provider. Enable the basic-auth provider in ~/.hermes/config.yaml
(dashboard.basic_auth.username + password_hash), bind the dashboard to your private
address, then enter that username/password in the app. The app handles the cookie/refresh and
WebSocket-ticket flow for you.
📖 Full Hermes installation, gateway, and token docs: hermes-agent.nousresearch.com/docs
Reach the gateway from your phone over Tailscale (recommended — works from anywhere), another VPN that puts the phone and gateway on the same private network (e.g. a self-hosted WireGuard or ZeroTier mesh), or your local network (LAN) when both are on the same Wi-Fi.
Note: this means a mesh / private VPN that actually routes to your gateway. A commercial exit VPN (e.g. Proton VPN, Mullvad) only tunnels your traffic to the public internet and cannot reach a private gateway — it's not a substitute for a private network connection.
./gradlew :app:testDebugUnitTest # JVM unit tests
./gradlew :app:connectedDebugAndroidTest # instrumented (device/emulator)Branches
| Branch | Role | Distribution |
|---|---|---|
main |
Production / stable. Always releasable. | Stable releases (v1.2.3) |
dev |
Beta / integration. New features land here first. | Beta pre-releases (v1.2.3-beta.1) |
feature/* |
Short-lived work branches. | — (open a PR into dev) |
Day-to-day flow:
feature/x ──PR──▶ dev ──(stabilize)──▶ main
│ │
beta pre-release stable release
v0.2.0-beta.1 v0.2.0
- Branch from
dev:git switch dev && git switch -c feature/my-change. - Open a PR into
dev. CI (.github/workflows/ci.yml) builds and unit-tests every push/PR. - Cut a beta for testers: tag a commit on
dev, e.g.git tag v0.2.0-beta.1 && git push --tags. - When stable, merge
dev→mainand tag the release:git tag v0.2.0 && git push --tags.
Automated releases (.github/workflows/release.yml) — pushing a v* tag builds a
signed APK and publishes a GitHub Release:
vX.Y.Z→ production release (assembleRelease), shown as Latest.vX.Y.Z-beta.N→ beta pre-release (assembleBeta), not marked Latest, soreleases/latestkeeps pointing at the stable build.
The beta build installs side-by-side with production: it has a separate application id
(com.hermes.client.beta) and the label "Hermes Beta", so testers can keep the stable
app and try betas without losing it. Anyone who wants the newest changes grabs the latest
pre-release APK from the Releases page;
everyone else gets the stable latest release.
CI signing secrets — the release workflow signs in CI using these repository secrets (Settings → Secrets and variables → Actions):
| Secret | Value |
|---|---|
KEYSTORE_BASE64 |
base64 -i keystore/hermes-release.jks (the whole keystore, base64-encoded) |
KEYSTORE_PASSWORD |
keystore (store) password |
KEY_ALIAS |
key alias (e.g. hermes) |
KEY_PASSWORD |
key password |
Until those are set, tag-triggered builds will fail to sign — build/release locally with
./gradlew :app:assembleRelease (or :app:assembleBeta) instead, which reads the
gitignored keystore.properties.
Contributions are welcome — see CONTRIBUTING.md for setup, the
architecture/test conventions, and the branch workflow (new work lands on dev via PR).
All participation is governed by the Code of Conduct.
Found a vulnerability? Please report it privately — see SECURITY.md for how to report and the app's security posture (encrypted credential storage, the token/basic-auth modes, the private-network/cleartext design, and supply-chain hygiene).
If Hermes for Android is useful to you, please consider supporting development:
Every contribution helps fund new features and ongoing maintenance. GitHub Sponsors is coming soon — until then, Ko-fi is the best way to chip in. Thank you! 🙏
GNU General Public License v3.0. You are free to use, study, share, and modify this software under the terms of the GPL-3.0 — see the LICENSE file for the full text.
Hermes Agent and the Hermes mascot are property of their respective owners; this is an independent, unofficial Android client.



