The plugin is named 'iris' (IrisAdapter, IRIS_HOME_CHANNEL, label Iris), but several docs still referred to it as the android platform/plugin and to the product as 'the Android app'. Rename name-mentions to iris/IRIS and product-mentions to 'Iris app'; keep legitimate OS references (androidApp, Android SDK, Android 10, androidx, test_android.py, ...). Also includes pi-lens markdown-lint autofixes (table spacing, trailing newlines) in the touched files.
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 WebSocket 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! — everything stays on your own infrastructure (push: ntfy by default; 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 ──(WebSocket :8790)──> Iris app (Android / Desktop)
gateway-plugin/is a hermes platform plugin (android). It runs inside thehermes gatewayprocess and opens a WebSocket 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) — push metadata stays on your own infrastructure (self-hosted ntfy recommended). This is the backend for truly private communication.
- 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/setup.md §4; details:
docs/08-push.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)
# hermes-agent is a separate project (not part of this repo)
cd hermes-agent && uv sync
# install the Iris plugin into the live hermes home
mkdir -p ~/.hermes/plugins
ln -s "$PWD/gateway-plugin" ~/.hermes/plugins/android
hermes gateway status # should list "android"
hermes gateway setup # generates ANDROID_TOKEN, prints the server URL
hermes gateway # run the gateway
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 —
ws://<gateway-ip>:8790/ws(printed byhermes gateway setup). - Pairing token — from the setup output, or
ANDROID_TOKENin~/.hermes/.envon the gateway host. - Test & Connect.
Notes:
- The app has no QR scanner — pairing is manual URL + token entry.
- The default bind is
127.0.0.1(desktop on the same machine only). For a phone on the LAN, setANDROID_WS_HOSTto the gateway's LAN IP. - Remote access: Tailscale/WireGuard, or a reverse proxy with CA-signed WSS
(
ANDROID_WS_CERT/ANDROID_WS_KEY).
Full walkthrough, push setup (ntfy/FCM), and troubleshooting:
docs/setup.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):
gateway-plugin/tests/ws_probe.pyandgateway-plugin/tests/e2e.py— seegateway-plugin/tests/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.