Reviewed-on: #16
Iris × Hermes
A chat app for hermes-agent: a native Android app and a desktop app (Linux, macOS, Windows) — both built from one shared Kotlin codebase (Compose Multiplatform).
Iris pairs with your running hermes gateway over a private, token-authenticated connection and gives you a Telegram-quality chat experience with your personal agent: streaming replies, visible reasoning, structured tool activity, channels, threads, media, search, and push notifications.
Features
- Native Hermes-Gateway integration — your hermes → gateway → Iris app
- Absolute Privacy! — chat stays on your own infrastructure
(push: ntfy by default, but the default ntfy server is the public
ntfy.sh— self-host ntfy to keep push metadata on your own machine; FCM is opt-in and routes push metadata via Google — see Push notifications) - 100 MB file uploads by default — configurable on the gateway via
max_upload_bytes(see Media §7.7); all limits are set on the gateway side (hermes), not in the app - No 4,096-character message limit like Telegram — messages travel over your own gateway (hard frame-body cap: 1 MiB)
- Full markdown support — tables, checkmarks, bold, inline code, code blocks + syntax highlighting…
- HTML Artifact Preview — agent-sent HTML/CSS/JS rendered in an in-app WebView
- All settings live in the app, not in hermes
config.yml! Change everything on the fly. - Channels — create channels to keep track of your reports, cronjobs, webhook calls
- Pin channels, rename them, change the color/icon
- Customize how your chat should look like — color? Check! Images? Check!
- Reasoning collapse/expandable in the chat bubble
- Change the default behavior of tool & reasoning verbosity
- Threads — create your own, or let the AI create them with a title
- Search messages from everywhere
How it works
hermes-agent ──> hermes gateway ──(HTTP :8791)──> Iris app (Android / Desktop)
gateway-plugin/is a hermes platform plugin (android). It runs inside thehermes gatewayprocess and opens an HTTP server the apps connect to. Zero new Python dependencies, zero hermes-core changes.app/is one Compose Multiplatform Gradle project::shared(KMP, most of the code),:androidApp(native Kotlin + Jetpack Compose client),:desktopApp(the same app, tweaked for a big screen).- The app is a first-class hermes messaging platform, so everything the gateway
already does just works: slash commands, cron delivery,
send_messagerouting, coexistence with Telegram/Discord/etc. - Push notifications: ntfy (default) or FCM (opt-in).
Push notifications
Push wakes a backgrounded/offline device; on reconnect the app syncs the outbox, so nothing is lost.
- ntfy (default) — the backend for truly private communication.
⚠️ By default it uses the public
https://ntfy.shcloud service — push metadata (topic, notification title) passes through ntfy.sh's servers. SetNTFY_SERVER_URLto a self-hosted ntfy to keep push metadata on your own infrastructure (recommended; public ntfy.sh SSE is also flaky). - FCM (opt-in,
IRIS_PUSH_BACKEND=fcm) — standard/reliable, but FCM push metadata (notification title, device token) is routed through Google's servers. If you want truly private communication, use ntfy instead.
Setup: docs/install.md; details:
docs/08-push.md.
Install the gateway
Three commands on the machine where hermes runs:
cd hermes-agent && uv sync # 1. hermes with its venv (separate project, not this repo)
# 2. install the Iris plugin — the #gateway-plugin suffix points the
# installer at the plugin subfolder of this monorepo
hermes plugins install git@gitea.zephyre.one:ARIA/iris_x_hermes.git#gateway-plugin
# 3. generate the pairing token + server URL, then run the gateway
hermes gateway setup
hermes gateway
hermes gateway setup prints the server URL and pairing token / QR
the app needs on its Connect screen.
All options (LAN binding, TLS, push, device allowlist) and the full app
pairing walkthrough: docs/install.md.
Build from source
Prerequisites
| Where | You need |
|---|---|
| Gateway host | hermes-agent with its venv (uv sync) |
| Android build machine | JDK 17, Android SDK (sdk.dir in app/local.properties or ANDROID_HOME), ADB with a connected device |
| Desktop build machine | JDK 17 only |
No system Gradle needed — both apps use the project wrapper (./gradlew).
1. Gateway (on the gateway host)
If you're developing from a checkout, skip hermes plugins install and
symlink the plugin so it always tracks your working tree:
cd hermes-agent && uv sync
mkdir -p ~/.hermes/plugins
ln -s "$PWD/gateway-plugin" ~/.hermes/plugins/iris
hermes gateway status # should list the Iris platform
hermes gateway setup # generates IRIS_TOKEN, prints server URL + pairing QR
hermes gateway # run the gateway
(Otherwise see Install the gateway above.)
2. Android app
cd app
./gradlew :androidApp:installDebug # build + install on the connected ADB device
# or just build the APK:
./gradlew :androidApp:assembleDebug # → app/androidApp/build/outputs/apk/debug/
3. Desktop app
cd app
./gradlew :desktopApp:run # dev run
./gradlew :desktopApp:jpackage # native app-image (bundles the JRE)
4. Pair
On the app's Connect screen:
- Server URL —
http://<gateway-ip>:8791(printed byhermes gateway setup). - Pairing token — from the setup output, or
IRIS_TOKENin~/.hermes/.envon the gateway host. - Test & Connect.
Notes:
- Android has a Scan QR button that reads the QR printed by
hermes gateway setupand pre-fills URL + token; desktop uses manual entry. - The default bind is
127.0.0.1(desktop on the same machine only). For a phone on the LAN, setIRIS_HTTP_HOSTto the gateway's LAN IP. - Remote access: Tailscale/WireGuard, or a reverse proxy/tunnel with TLS
(
IRIS_HTTP_CERT/IRIS_HTTP_KEY).
Full walkthrough, push setup (ntfy/FCM), TLS, and troubleshooting:
docs/install.md.
Contributing
Contributions are welcome! Before you start:
-
Read the docs. The full reference library is in
docs/— start withdocs/00-overview.md, then follow the numbered docs (architecture, wire protocol, plugin design, testing, …). -
Know the layout.
Path What gateway-plugin/Python hermes platform plugin ( android);protocol.pyis the frame source of truthapp/sharedKMP module with most of the client code (shared by Android + Desktop) app/androidAppThin Android shell (package dev.iris.app)app/desktopAppThin desktop shell docs/Numbered reference library -
Run the tests.
- Python (gateway plugin):
cd hermes-agent && scripts/run_tests.sh tests/gateway/test_android.py(never barepytest— hermes's runner sandboxesHERMES_HOME). - Kotlin:
cd app && ./gradlew :shared:testDebugUnitTest - Live check (gateway must be running):
tests/ws_probe.pyandtests/e2e.py— seetests/README.md.
- Python (gateway plugin):
-
Keep the protocol in sync.
gateway-plugin/protocol.py,app/shared/.../protocol/Protocol.kt, anddocs/protocol/frames.schema.jsonmust always agree. -
Pre-commit hooks are configured (
.pre-commit-config.yaml); runpre-commit installonce after cloning.
Open an issue first for anything big, then send a pull request.
License
Apache License 2.0 — see LICENSE.