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).
94 lines
4.8 KiB
Markdown
94 lines
4.8 KiB
Markdown
# 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).
|