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

7.5 KiB
Raw Blame History

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.

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.