- IRIS_PUSH_BACKEND now defaults to ntfy (keeps push metadata on your own infrastructure); FCM is opt-in via IRIS_PUSH_BACKEND=fcm - build_push_backend(): ntfy for empty/unknown names, FCM only on explicit 'fcm' - gateway setup: warn when FCM is chosen (metadata routed via Google's servers) - README: privacy note + dedicated push section; new docs/playstore-listing.md with the FCM/ntfy privacy note for the Play Store listing - docs: 00/02/03/08/12/16 + setup.md updated to ntfy-default wording - tests: default-backend assertion updated (86/86 pass)
5.2 KiB
5.2 KiB
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 — ntfy default, FCM optional (issue #10: FCM metadata — title, device token — is routed via Google's servers, contradicting the privacy claim; ntfy keeps it on your own infrastructure) | IRIS_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 iris), 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 | 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. |
| QR encoder | Pure-stdlib (gateway-plugin/qr.py) |
No qrcode/segno/Pillow; byte mode, EC M→L, v1–10, ISO penalty masking. Keeps the plugin's zero-new-dep rule. |
| QR scanner | ML Kit barcode (not zxing-android-embedded) | On-device, no Google Play services; better accuracy/latency, Google-maintained. Android-only (desktop has no camera). |
Open questions (resolve during implementation)
These are implementation-time details, not blockers. Each has a default we will proceed with unless you say otherwise.
- Tool-progress vs commentary classification (M2). The exact signal that
distinguishes a tool-progress
send()from a regularsend()/commentary in the legacy path. Default: per-chat turn-state machine + line-format heuristic, verified empirically with thews_probe.pyharness against the live gateway. If a clean metadata marker exists, prefer it. - Reasoning prefix format stability (M2). We split on the
code-style💭 **Reasoning:**\n```\n…\n```\n\nprefix. Default: setreasoning_style: codefor android and split on that; fallback = no reasoning field (full text) if the prefix isn't found. Verify in M2. - Per-device tokens vs shared token (M1/M5). Default (v1): shared
IRIS_TOKEN+ optionalIRIS_ALLOWED_USERSdevice allowlist. Per-device revocable tokens are a stretch. - Desktop video backend (M6). Default:
libmpv/mpv-backed Compose surface, WebView fallback. Confirmmpvavailability on target OSes during M6. - Remote access default (M1). Default: document Tailscale as the recommended remote path; WSS + reverse proxy as alternatives. No public bind by default.
- Streaming cadence (M2). If live updates look chunky, tune
display.platforms.iris.streaming/ consumer edit interval. Default: follow global streaming config. - App package name / branding. Default: applicationId
dev.iris.app, app name "Iris". Confirm final product name + package + icon. - 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.
- Auto-update for desktop (M7). Default: out of scope (manual download). Revisit post-v1.
- 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.