# Iris × Hermes A chat app for [hermes-agent](https://github.com/NousResearch/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](#push-notifications)) - **No file limit** - **No character limit** - **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 the `hermes gateway` process 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_message` routing, 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`](docs/setup.md) §4; details: [`docs/08-push.md`](docs/08-push.md). ## Build from source ### Prerequisites | Where | You need | | --- | --- | | Gateway host | [hermes-agent](https://github.com/NousResearch/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) ```bash # 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 ```bash 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 ```bash cd app ./gradlew :desktopApp:run # dev run ./gradlew :desktopApp:jpackage # native app-image (bundles the JRE) ``` ### 4. Pair On the app's **Connect** screen: 1. **Server URL** — `ws://:8790/ws` (printed by `hermes gateway setup`). 2. **Pairing token** — from the setup output, or `ANDROID_TOKEN` in `~/.hermes/.env` on the gateway host. 3. **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, set `ANDROID_WS_HOST` to 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`](docs/setup.md). ## Contributing Contributions are welcome! Before you start: 1. **Read the docs.** The full reference library is in [`docs/`](docs/README.md) — start with [`docs/00-overview.md`](docs/00-overview.md), then follow the numbered docs (architecture, wire protocol, plugin design, testing, …). 2. **Know the layout.** | Path | What | | --- | --- | | `gateway-plugin/` | Python hermes platform plugin (`android`); `protocol.py` is the frame source of truth | | `app/shared` | KMP module with most of the client code (shared by Android + Desktop) | | `app/androidApp` | Thin Android shell (package `dev.iris.app`) | | `app/desktopApp` | Thin desktop shell | | `docs/` | Numbered reference library | 3. **Run the tests.** - Python (gateway plugin): `cd hermes-agent && scripts/run_tests.sh tests/gateway/test_android.py` (never bare `pytest` — hermes's runner sandboxes `HERMES_HOME`). - Kotlin: `cd app && ./gradlew :shared:testDebugUnitTest` - Live check (gateway must be running): `gateway-plugin/tests/ws_probe.py` and `gateway-plugin/tests/e2e.py` — see [`gateway-plugin/tests/README.md`](gateway-plugin/tests/README.md). 4. **Keep the protocol in sync.** `gateway-plugin/protocol.py`, `app/shared/.../protocol/Protocol.kt`, and `docs/protocol/frames.schema.json` must always agree. 5. **Pre-commit hooks** are configured (`.pre-commit-config.yaml`); run `pre-commit install` once after cloning. Open an issue first for anything big, then send a pull request. ## License Apache License 2.0 — see [LICENSE](LICENSE).