Files
iris_x_hermes/docs/14-milestones.md
T
ARIA 59acf66c89 M0: toolchain, monorepo scaffold, gateway plugin skeleton, CMP app
- gateway-plugin/: android platform plugin (plugin.yaml + adapter.py
  register(ctx) + no-op AndroidAdapter) + stub modules for M1-M5
- app/: Compose Multiplatform project (shared KMP + androidApp +
  desktopApp) with Gradle wrapper; builds :androidApp:assembleDebug
  and :desktopApp:compileKotlin
- scripts/guard_hermes_agent.sh + pre-commit hook: fail if hermes-agent/
  is staged (read-only reference, never committed)
- .gitignore excludes hermes-agent/; docs/ reference library
2026-08-19 11:27:02 +02:00

138 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 14 — Milestones (M0–M7)
Phased delivery. Each milestone ends with a **demo** (on-device where noted) and
has explicit **acceptance criteria**. Work top-to-bottom; don't skip M0/M1.
---
## M0 — Toolchain & scaffolding
**Goal:** everything builds; the plugin is discoverable; the repo is safe.
- [ ] Install JDK 17, Android SDK, set `ANDROID_HOME` (`12-toolchain.md`).
- [ ] `cd hermes-agent && uv sync` (hermes venv works).
- [ ] Create monorepo scaffold (`02-monorepo.md`): `gateway-plugin/`, `app/`
(CMP: `shared`, `androidApp`, `desktopApp`), root `.gitignore`
(**excludes `hermes-agent/`**), root `README.md`.
- [ ] `git init` + a pre-commit/CI guard that fails if `hermes-agent/` is staged.
- [ ] CMP project builds empty: `./gradlew :androidApp:assembleDebug`,
`./gradlew :desktopApp:run` (blank window).
- [ ] Plugin skeleton: `plugin.yaml` + `adapter.py` with `register(ctx)` + a
no-op `AndroidAdapter` → `hermes gateway status` lists **android**.
- **Demo:** `hermes gateway status` shows `android`; `./gradlew
:androidApp:installDebug` installs a blank app on the MIX 2S.
- **Accept:** blank app installs + launches on-device; plugin visible in
`hermes gateway status`; `hermes-agent/` is git-ignored (verify with
`git status --ignored`).
## M1 — Gateway core loop (text round-trip)
**Goal:** pair + send a text message + get a (non-streaming) reply.
- [ ] WS server (`ws_server.py`): bind, `hello` auth (constant-time),
`hello.ack`, heartbeat, connection registry.
- [ ] `AndroidAdapter.send()` → `message` frame; inbound `message.send` →
`MessageEvent` → `handle_message`.
- [ ] Pairing store + `ANDROID_TOKEN`; QR payload in `interactive_setup`.
- [ ] App: Connect screen (URL+token, real `hello` test), `GatewayClient`
(connect + reconnect), ChatScreen sends + renders `message`.
- [ ] `ws_probe.py` harness drives a real turn.
- **Demo (on-device):** pair the phone, send "hello", see the agent's reply.
- **Accept:** text round-trip works on-device; wrong token is rejected;
reconnect after gateway restart re-pairs.
## M2 — Streaming + reasoning + tools + commentary
**Goal:** the "agent transparency" features.
- [ ] Map consumer `send`/`edit_message` → `message.start/update/stop`.
- [ ] Reasoning: set `show_reasoning` for android; adapter splits prefix →
`reasoning` field. **Verify format with `ws_probe.py`.**
- [ ] Tool events: classify tool-progress `send()`s → structured
`tool.start/progress/end` (turn-state machine). **Verify with probe.**
- [ ] Commentary → `commentary` frames. Typing → `typing`.
- [ ] App: live bubble (coalesced updates), `ReasoningBlock` (collapse + copy),
`ToolCard` with **Everything/Truncated/Nothing** setting, dimmed
`commentary` bubble.
- **Demo (on-device):** a multi-step prompt streams, shows reasoning above the
answer, tool cards (toggle verbosity), and an intermediate beat.
- **Accept:** all four render correctly; tool verbosity setting changes
rendering; reasoning copy button works; frame shapes match `04-wire-protocol`.
## M3 — Channels/threads + cron + search
**Goal:** organization + cron delegation + search.
- [ ] Channel directory (SQLite): default channel ensured; `channel.create/
rename/set_default/delete` + `channel.*` frames.
- [ ] Threads: toggle in default chat; `thread_id` lanes; `create_handoff_thread`.
- [ ] `parse_target_ref_fn` + `cron_deliver_env_var` → cron
`deliver=android:<chat>[:<thread>]` works.
- [ ] `search.py` FTS5 bridge; `search` frame (all / this-chat) → results.
- [ ] App: channel list (drawer/rail), thread toggle + topic switcher, "new
channel" + "set as cron target", SearchScreen with scope toggle + jump.
- **Demo (on-device):** create "Cron Reports"; create a cron job delivering to
it; it fires into that channel; search finds a message (both scopes).
- **Accept:** cron output lands in the chosen channel (not default); threads
isolate context; search scoping correct; channel list reconciles on events.
## M4 — Media
**Goal:** attach + receive + play media.
- [ ] Inbound: `media.upload` chunked → `cache_*_from_bytes` → `media_urls`;
size limit + sha256 + MIME re-sniff.
- [ ] Outbound: `send_*` → `media.offer`; `media.pull` chunked; delivery-path
security.
- [ ] App: SAF pickers + preview chips + upload; `media.pull` → cache;
**ExoPlayer** inline (audio mini-player, video fullscreen/PiP); image/doc
viewers.
- **Demo (on-device):** attach a photo + video (agent sees them); ask agent to
send an image/video → plays live in-app.
- **Accept:** both directions work; over-limit rejected; playback is live;
only allowed files are servable.
## M5 — Push + offline (FCM + ntfy)
**Goal:** reach the phone when backgrounded; catch up on reconnect.
- [ ] Outbox (SQLite) + sync cursor; `sync`/`sync.done`; retention prune.
- [ ] `push.py`: `FcmBackend` (HTTP v1 + service account, httpx) +
`NtfyBackend`; selected by `ANDROID_PUSH_BACKEND`.
- [ ] Fire push on no-live-subscriber; data payload for silent sync.
- [ ] App: FCM service (foreground banner + background foreground-service sync),
`onNewToken` → `fcm.register`; per-chat notification channels; deep-link.
ntfy listener fallback.
- [ ] In-app `notification` banners (channel_renamed, approval, cron, …).
- **Demo (on-device):** background the app → trigger a message → notification
appears → tap → syncs + opens the chat. Repeat with ntfy backend.
- **Accept:** push arrives when backgrounded (both backends); reconnect syncs
with no loss/dup; banners show for foreground events.
## M6 — Desktop app
**Goal:** the same app on a big screen.
- [ ] `desktopMain`: tray + OS notifications; `MediaPlayer` actual (mpv/WebView);
`MediaPicker` actual (file dialog); `SecureStore` actual; window mgmt.
- [ ] Two-pane default layout; keyboard shortcuts; optional inspector pane.
- [ ] Parity pass vs Android feature checklist (`11-desktop-app.md`).
- [ ] jpackage builds (Linux first; macOS/Windows as available).
- **Demo:** desktop app pairs to the same gateway; full feature parity; tray
notifications; media plays.
- **Accept:** all Android features work on desktop; tray + shortcuts work;
native binary launches.
## M7 — Polish + E2E + docs
**Goal:** ship-quality.
- [ ] Telegram-style layout pass (per reference image): header, bubbles, date
separators, ✓✓, model/token footer, banner, bottom bar.
- [ ] Theming (dark default, accent), onboarding/pairing UX, empty/loading/
reconnecting/degraded states with honest copy.
- [ ] Full E2E suite (`13-testing.md` scenarios 1–12) automated where possible.
- [ ] Docs: `docs/protocol/frames.schema.json` finalized; `docs/setup.md`
(user-facing pairing + FCM/ntfy + remote access); root README.
- [ ] Security hardening checklist (`09-pairing-security.md`) verified.
- **Demo:** end-to-end on phone + desktop simultaneously; cron into a channel;
push; media; search.
- **Accept:** all feature-checklist items pass on-device; E2E green; docs
complete; `hermes-agent/` still never committed.
---
## Sequencing notes
- **M1/M2 depend on the `ws_probe.py` harness** to lock frame shapes early —
build it in M1.
- **M3 (cron) and M5 (push) both touch the outbox** — build the outbox in M3,
extend for push in M5.
- **M6 (desktop) reuses M1–M5 shared code** — do it after the Android features
are stable so the shared module is settled.
- Parallelizable: plugin (Python) and app (Kotlin) can be worked on
concurrently once the protocol (`04-wire-protocol.md`) is agreed; the probe
harness is the integration seam.