# 16 — Decisions & Open Questions ## Locked decisions (from planning, 2026-08-19) | # | Decision | Choice | Rationale | |---|---|---|---| | 1 | Desktop app tech | **Compose Multiplatform** | Desktop = "the Android app, tweaked"; share protocol/state/UI. | | 2 | Push backend | **Both — FCM primary, ntfy fallback** | FCM is standard/reliable; ntfy for self-hosters with no Firebase. `ANDROID_PUSH_BACKEND`. | | 3 | Media transport | **Over the WebSocket** | One transport, zero new Python deps; chunked binary frames. | | 4 | Phone default layout | **User-toggleable, single-pane default** | App-like on phones; auto two-pane on large screens; desktop defaults two-pane. | ## Additional decisions made during planning | Decision | Choice | Note | |---|---|---| | Connection point | **Messaging gateway** (platform plugin `android`), not `tui_gateway` | Makes cron/`send_message`/slash/coexistence native. | | Plugin style | **Community plugin** (`register(ctx)`) | Zero hermes-core changes. | | Python deps | **None new** (`websockets` + `httpx` are core) | Respects hermes pinning policy. | | Tool events | **Structured frames; app controls verbosity** | Gateway sends full data; app = everything/truncated/nothing. | | Reasoning | **Adapter splits the `show_reasoning` prefix** | Clean `reasoning` field → collapsible block above message. | | Channels/threads | **Map onto `chat_id`/`thread_id`** | Existing gateway primitives; cron targets them. | | Offline | **SQLite outbox + sync cursor** | Catch-up on reconnect; push on disconnect. | | Default chat id | `android:default` | Home channel + cron default. | | WS port | `8790` (default) | Configurable. | | minSdk | 26 (test device API 29) | Broad coverage. | | Frame routing | **Broadcast to all connected devices** (no per-chat subscribe) | Single-user model; simpler. | | Initial channel load | **`history` frame** (paginated) | `sync` only replays outbox; `history` loads full messages. | | Slash menu | **`commands.catalog` + `commands.complete`** frames | Gateway serves catalog; app renders bottom sheet + autocomplete. | | Agent lifecycle | **`agent.busy`/`agent.idle`** events + **`agent.stop`/`agent.steer`** requests | App shows thinking indicator; user can abort or steer mid-turn. | | Local DB (KMP) | **SQLDelight** (not Room) | Room is Android-only; SQLDelight works in commonMain for both platforms. | | Voice input | **Record → upload as audio media** (no client-side STT) | Agent's STT (if configured) handles transcription. | ## Open questions (resolve during implementation) These are **implementation-time** details, not blockers. Each has a default we will proceed with unless you say otherwise. 1. **Tool-progress vs commentary classification (M2).** The exact signal that distinguishes a tool-progress `send()` from a regular `send()`/commentary in the legacy path. *Default:* per-chat turn-state machine + line-format heuristic, **verified empirically** with the `ws_probe.py` harness against the live gateway. If a clean metadata marker exists, prefer it. 2. **Reasoning prefix format stability (M2).** We split on the `code`-style `šŸ’­ **Reasoning:**\n```\n…\n```\n\n` prefix. *Default:* set `reasoning_style: code` for android and split on that; fallback = no reasoning field (full text) if the prefix isn't found. Verify in M2. 3. **Per-device tokens vs shared token (M1/M5).** *Default (v1):* shared `ANDROID_TOKEN` + optional `ANDROID_ALLOWED_USERS` device allowlist. Per-device revocable tokens are a stretch. 4. **Desktop video backend (M6).** *Default:* `libmpv`/`mpv`-backed Compose surface, WebView fallback. Confirm `mpv` availability on target OSes during M6. 5. **Remote access default (M1).** *Default:* document Tailscale as the recommended remote path; WSS + reverse proxy as alternatives. No public bind by default. 6. **Streaming cadence (M2).** If live updates look chunky, tune `display.platforms.android.streaming` / consumer edit interval. *Default:* follow global streaming config. 7. **App package name / branding.** *Default:* applicationId `dev.iris.app`, app name "Iris". Confirm final product name + package + icon. 8. **ntfy in-app listener (M5).** *Default:* a foreground service maintaining the ntfy subscription (no extra native lib) — or a lightweight ntfy client lib if one is acceptable. Decide in M5. 9. **Auto-update for desktop (M7).** *Default:* out of scope (manual download). Revisit post-v1. 10. **iOS port.** Out of scope for v1 (protocol is transport-agnostic, so it's a future port). No action now. ## Explicit non-goals (v1) - Multi-user / group chat (personal 1-user agent). - End-to-end encryption (transport WSS only). - Standalone-cron delivery while the gateway process is fully down (best-effort push only). - iOS.