ARIA 9286937e2d Runtime footer: app-controlled model/context/cwd/latency/cost under replies
Hermes can append a text "runtime footer" (model, context %, workdir,
latency, cost) to final replies, but only when display.runtime_footer is
enabled in the hermes config. We want the same info but controlled by the
APP, not the gateway config. So the gateway now ALWAYS sends the data as a
structured `runtime` object on final assistant messages, and the app decides
whether/what to show.

Gateway (gateway-plugin/):
- protocol.py: new runtime_footer() helper + `runtime` field on the
  message / message.stop frames. Keys (all optional, absent when the data
  is unavailable — e.g. no cost for local models): model (vendor prefix
  dropped), context_pct (0-100), cwd (home-relative), latency (seconds),
  cost (USD).
- adapter.py: a post_api_request plugin hook captures the turn's model +
  prompt tokens + start time (platform-filtered to android so other
  platforms don't pollute the buffer). _build_runtime_footer() resolves the
  model's context window (cached, best-effort, off the event loop via
  asyncio.to_thread with a timeout) and computes context_pct. The runtime
  object is attached on every final send (streaming message.stop and
  non-streaming message, plus the fallback paths).
- outbox.py: `runtime` preserved in history reconstruction so the footer
  survives a restart / first open.

App (app/shared/):
- Protocol.kt: RuntimeMeta data class + `runtime` on MessagePayload /
  MessageStopPayload / HistoryMessage.
- ChatStore.kt: `runtime` on MessageItem, wired through live + history
  reconciliation.
- SecureStore.kt (+ Android/Desktop actuals): runtimeFooterEnabled +
  runtimeFooterFields (persisted per device).
- IrisController.kt: StateFlows + toggleRuntimeFooter() /
  toggleRuntimeField(); RUNTIME_FIELD_KEYS / default set / parser.
- SettingsScreen.kt: "Runtime footer" switch; when on, an expandable chip
  menu (Model · Context % · Workdir · Latency · Cost) to pick fields.
- ChatScreen.kt: footer rendered on the SAME line as the timestamp (footer
  left, time right, Telegram-style), only for final non-streaming assistant
  answers; Inspector pane now shows the runtime fields too.

Docs: 04-wire-protocol.md + frames.schema.json document the `runtime`
object.

Verified end-to-end on device: final replies carry
`qwen3.8-27B-exl3-4.5bpw · 53% · ~ · 38s` with the time right-aligned on
the same line; 69/69 gateway tests pass, Kotlin builds + tests pass.
2026-08-21 19:32:05 +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 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
  • 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: FCM (primary) or ntfy (fallback).

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:

  1. Server URL — ws://<gateway-ip>: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 (FCM/ntfy), and troubleshooting: docs/setup.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): gateway-plugin/tests/ws_probe.py and gateway-plugin/tests/e2e.py — see 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.

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%