ARIA 2c20b1c8a5
CI / Kotlin tests (android host + desktop) (pull_request) Successful in 8m18s
CI / Gateway plugin tests (pull_request) Failing after 15m7s
fix(http): don't let a half-open TLS connection wedge the accept loop
A client that completes TCP but vanishes mid-TLS-handshake (e.g. a
phone losing its network/VPN while traveling) blocked
ssl.SSLSocket.accept() inside serve_forever forever: the gateway
stopped accepting any new device connections (the app could not
reconnect), and on the next restart httpd.shutdown() froze the whole
event loop until the shutdown watchdog killed the process (ARIA
journal 2026-09-11 / 2026-09-23).

- Move the TLS handshake out of the accept loop: it now runs in the
  per-connection thread under a hard timeout (HANDSHAKE_TIMEOUT_S,
  10 s); a failed/timed-out handshake just closes the socket.
- stop() no longer blocks the event loop: shutdown()/server_close()/
  join run in an executor under asyncio.wait_for(10 s); if the bound
  expires the daemon threads are abandoned.
- Regression test: a silent half-open TCP connection must not stop
  fresh TLS connections from being served, and stop() must stay
  bounded.
2026-09-23 15:10:16 +02:00

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 the hermes gateway process 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_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) — the backend for truly private communication. ⚠️ By default it uses the public https://ntfy.sh cloud service — push metadata (topic, notification title) passes through ntfy.sh's servers. Set NTFY_SERVER_URL to 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:

  1. Server URL — http://<gateway-ip>:8791 (printed by hermes gateway setup).
  2. Pairing token — from the setup output, or IRIS_TOKEN in ~/.hermes/.env on the gateway host.
  3. Test & Connect.

Notes:

  • Android has a Scan QR button that reads the QR printed by hermes gateway setup and 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, set IRIS_HTTP_HOST to 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:

  1. Read the docs. The full reference library is in docs/ — start with 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): tests/ws_probe.py and tests/e2e.py — see 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.

S
Description
Iris × Hermes — native Android + Desktop client for hermes-agent via a gateway platform plugin. Compose Multiplatform app (Kotlin) + Python WebSocket gateway adapter. See docs/ for the full reference library.
Readme Apache-2.0
7.8 MiB
0 Stars 1 Watchers 0 Forks
Latest
2026-08-31 21:44:41 +00:00
Languages
Kotlin 52.2%
Python 47.4%
Shell 0.4%