Files
iris_x_hermes/docs/00-overview.md
T
ARIA 7faaf2aa1c
CI / Gateway plugin tests (push) Successful in 5m5s
CI / Kotlin tests (android host + desktop) (push) Successful in 6m50s
Per-device tokens with revocation (issue #11)
Auth previously used the shared IRIS_TOKEN as the security principal:
a leaked token meant access to all devices, and a compromised device
could not be isolated.

Gateway:
- pairing.py: devices.token column (in-place migration) + revoked
  denylist table; issue_token (idempotent, 64 hex), token_for,
  reissue_token, revoke/unrevoke/is_revoked/list_revoked. The token
  never leaks into device dicts (push fan-out / listings).
- http_server.py: auth accepts the shared token (bootstrap/legacy) OR
  the device's own token (both constant-time); a revoked device_id is
  rejected with 401 before either comparison. On SSE open (pairing)
  the per-device token is minted and returned in hello.ack.
- protocol.py: hello_ack(..., device_token).
- adapter.py: setup flow (hermes gateway setup -> Iris) now offers
  'Remove a paired device?' on an existing setup: numbered select
  menu (last option = exit the removal loop), confirmation, back to
  the menu for further removals.
- tools/iris_devices.py: operator CLI (list / revoke / unrevoke /
  reissue), stdlib only.

App:
- SecureStore.deviceToken (Android: EncryptedSharedPreferences;
  Desktop: second keyring slot iris-device-token / device_token.enc).
- HelloAckPayload.deviceToken; GatewayClient stores it on hello and
  presents it instead of the shared token from then on (live provider
  in HttpGateway); savePairing/clear wipe it for re-pairing.

Docs: 09 §9.3 stretch -> implemented (revocation semantics, both
control surfaces), 04 hello.ack example, frames.schema.json, M7 row 13.

Tests: 8 new Python tests (issuance, acceptance, revocation,
isolation, unrevoke, registry unit x2, setup-flow menu) - 94/94 pass;
2 new Kotlin wire tests - green. Live-verified against a running
gateway (hello.ack token matches devices.db; revoke -> 401 even with
shared token; unrevoke -> 200; setup TUI both paths).
2026-08-24 19:37:44 +02:00

4.8 KiB
Raw Blame History

00 — Overview

Vision

A native, Telegram-quality chat experience for a personal hermes-agent: install an app on your phone (and a desktop app on your PC), pair it to your running hermes gateway, and talk to your agent with streaming replies, visible reasoning, structured tool activity, channels/threads, media, search, and push notifications — with cron jobs able to post into any channel you create.

Goals

  • Native feel. Real Android app (Kotlin/Compose), not a WebView. Desktop app that is the same app, resized for a big screen.
  • First-class gateway citizen. The app is a hermes messaging platform, so everything the gateway already does "just works": slash commands, cron delivery, send_message routing, coexistence with Telegram/Discord/etc.
  • Full agent transparency. Streaming text, reasoning shown before the answer, structured tool events (the app chooses how much to show), and intermediate assistant beats.
  • Organized by default. A default chat with optional threads, plus user-created channels that cron jobs can target.
  • Reachable anywhere. Live over a WebSocket; background push via FCM (primary) or ntfy (fallback).

In scope (v1)

Everything in the feature checklist below.

Out of scope / stretch (v1)

  • Standalone-cron delivery while the gateway process is fully down — best-effort FCM/ntfy only (the outbox is served by the running gateway).
  • Multi-user / group chat — this is a personal 1-user agent.
  • End-to-end encryption — transport security (WSS) only.
  • iOS — Android + Desktop only (the protocol is transport-agnostic, so an iOS client is a future port, not a v1 goal).

Feature checklist → where it's handled

Requirement Gateway plugin App
Input box, auto-grow (max height) — Compose TextField + bounded heightIn
Menu button → all slash commands Dispatches /…; serves command catalog Bottom-sheet menu + / autocomplete
Tool output (app decides how much) Emits structured tool events App setting: everything / truncated / nothing
Reasoning shown before message Captures + splits reasoning Collapsible "Reasoning" block above message
Intermediate messages Forwards Commentary events Distinct dimmed bubble
Threading + channels; default chat; user channels for cron chat_id/thread_id model; cron deliver=iris:<chat>[:<thread>] Channel list, thread toggle, "new channel"
Search ("everywhere" / "this chat/channel") FTS5 session search bridge Search UI + scope toggle
Attach media (music/video/images/docs) Inbound cache; outbound send_* Pickers + chunked upload + preview
Push notifications FCM (primary) / ntfy (fallback) FCM token / ntfy topic + notification service
Live playback of AI-sent music/video Serves media bytes over WS ExoPlayer inline player

Locked decisions (from planning)

Decision Choice
Desktop app tech Compose Multiplatform (shares Android code; "tweaked" for big screen)
Push backend Both — ntfy default, FCM optional (IRIS_PUSH_BACKEND)
Media transport Over the WebSocket (chunked binary frames; no extra Python deps)
Phone default layout User-toggleable, single-pane default (auto two-pane on large screens)

Disclaimers (hard rules)

  1. hermes-agent/ is a read-only research reference. It lives next to this folder for study only. It is git-ignored and must never be committed, pushed, or included in any artifact. Our plugin is installed into a live hermes home (~/.hermes/plugins/iris); we never edit hermes core files.
  2. ADB is available and a device is connected (a5ca2a4b, Xiaomi MIX 2S, Android 10 / API 29). Use adb install / adb logcat / adb shell am start to install, launch, and debug the app on-device throughout the build.

Verified environment state (2026-08-19)

Item State
OS CachyOS (Arch-based), pacman present
JDK Not installed → Milestone M0 (pacman -S jdk17-openjdk)
Android SDK Not installed → M0 (cmdline-tools + sdkmanager)
Gradle Via project wrapper (gradlew), no system install
ADB Installed; device a5ca2a4b (MIX 2S, API 29) connected
Python 3.14.7; uv 0.12.3 present
hermes venv Not created → M0 (cd hermes-agent && uv sync)
hermes core deps websockets==15.0.1 and httpx are core deps → plugin needs zero new Python deps
Disk / RAM 522 GB free / 62 GB RAM — ample

Naming

  • Product/effort name: Iris × Hermes (folder iris_x_hermes).
  • hermes platform name: iris (the plugin registers Platform("iris")).
  • WS default port: 8790 (configurable).
  • Default chat id: default (the home channel).