# 02 — Monorepo Structure One repository, three artifacts. `hermes-agent/` is **not** part of the repo (git-ignored reference). ## Top-level tree ``` iris_x_hermes/ ├── .gitignore # MUST exclude hermes-agent/ (see below) ├── README.md # Repo root readme (short; points to docs/) ├── docs/ # ← THIS reference library │ ├── hermes-agent/ # ⚠️ READ-ONLY REFERENCE — NEVER PUSHED (git-ignored) │ ├── gateway-plugin/ # ① Python plugin → installed to ~/.hermes/plugins/iris │ ├── plugin.yaml # manifest (kind: platform, env vars, home channel) │ ├── __init__.py │ ├── adapter.py # IrisAdapter(BasePlatformAdapter) + register(ctx) │ ├── ws_server.py # websockets server, connection registry, framing │ ├── protocol.py # frame schemas (source of truth, mirrored in Kotlin) │ ├── media.py # inbound cache + outbound chunked streaming │ ├── outbox.py # SQLite offline outbox + sync cursor │ ├── push.py # PushBackend: FcmBackend + NtfyBackend │ ├── pairing.py # token gen/verify, device registry │ ├── search.py # FTS5 session search bridge │ └── tests/ # pytest (run via hermes scripts/run_tests.sh) │ ├── app/ # ② + ③ Compose Multiplatform project (Kotlin) │ ├── settings.gradle.kts │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle/ gradlew gradlew.bat │ ├── shared/ # KMP module — the bulk of the code │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── commonMain/kotlin/iris/… # protocol, WS client, repo, state, Compose UI │ │ ├── androidMain/kotlin/… # FCM, ExoPlayer, SAF picker, notifications │ │ └── desktopMain/kotlin/… # tray, file dialog, player, window │ ├── androidApp/ # thin Android shell (Application, MainActivity) │ │ ├── build.gradle.kts │ │ └── src/main/… # AndroidManifest, res, Firebase options │ └── desktopApp/ # thin Desktop shell (main(), window) │ ├── build.gradle.kts │ └── src/main/kotlin/… │ └── (no other top-level code) ``` ## Module responsibilities ### `gateway-plugin/` (Python) - **`plugin.yaml`** — manifest: `name: iris-platform`, `kind: platform`, `requires_env` / `optional_env` (surfaced in `hermes config`/setup). - **`adapter.py`** — `IrisAdapter(BasePlatformAdapter)` + `register(ctx)`. The heart of the plugin. See `03-gateway-plugin.md`. - **`ws_server.py`** — `websockets` server, per-device connection registry, frame encode/decode, heartbeat, broadcast routing to all connected devices. - **`protocol.py`** — dataclasses/constants for every frame (single source of truth; `docs/protocol/frames.schema.json` is generated/mirrored from it). - **`media.py`** — inbound chunked upload → `cache_*_from_bytes`; outbound `media.offer`/`media.pull` chunked streaming. - **`outbox.py`** — SQLite outbox per `chat_id` + monotonic sync cursor. - **`push.py`** — `PushBackend` interface; `FcmBackend` (httpx, FCM HTTP v1) and `NtfyBackend` (reuses hermes ntfy publish). Selected by `IRIS_PUSH_BACKEND`. - **`pairing.py`** — token generation/verification (constant-time), device registry (SQLite), QR payload. - **`search.py`** — FTS5 query bridge over the hermes session store. ### `app/shared` (Kotlin KMP) - **`commonMain`** — protocol models (kotlinx-serialization), `GatewayClient` (OkHttp WS), repositories (Room), ViewModels (StateFlow), and the Compose UI (design system, screens). ~80% of app code. - **`androidMain`** — FCM service, ExoPlayer, SAF media picker, system notifications, `MediaPlayer` actual. - **`desktopMain`** — tray + OS notifications, desktop player, file dialog, window management, `MediaPlayer` actual. ### `app/androidApp` / `app/desktopApp` Thin shells: `Application`/`MainActivity` (Android) and `main()`/window (Desktop). They compose the `shared` UI and inject platform services. ## Build systems - **Python plugin:** no build step (pure Python, stdlib + hermes core deps). Installed by copying/symlinking into `~/.hermes/plugins/iris`. Tested with hermes's `scripts/run_tests.sh`. - **Kotlin/CMP:** Gradle (Kotlin DSL) with the Compose Multiplatform plugin. `./gradlew :androidApp:installDebug`, `./gradlew :desktopApp:run`, `./gradlew :shared:testDebugUnitTest`. ## `.gitignore` (root) — critical ```gitignore # hermes-agent is a read-only research reference — NEVER commit/push it /hermes-agent/ # Python __pycache__/ *.pyc .venv/ venv/ # Kotlin / Gradle .gradle/ build/ local.properties *.iml .idea/ # Android / Firebase app/androidApp/src/main/res/values/secrets.xml google-services.json *.jks keystore.jks # OS / misc .DS_Store *.log ``` > The `/hermes-agent/` line is non-negotiable. Add a pre-commit guard (or CI > check) that fails if any path under `hermes-agent/` is staged. ## Install layout (runtime) - **Plugin:** `~/.hermes/plugins/iris/` ← copy of `gateway-plugin/` (or a symlink for dev). Discovered by hermes's `PluginManager`. - **App (dev):** installed on-device via `./gradlew :androidApp:installDebug`. - **App (desktop, dev):** `./gradlew :desktopApp:run`.