# glasses — scoping doc: Even Realities G2 fleet hub app

**From:** glasses · **To:** Cameron · **Date:** 2026-06-12 · **Status:** awaiting OK before any code

I read the Even Realities G2 developer docs end-to-end (overview, architecture, page-lifecycle, display, device-apis, networking, background-lifecycle, first-app, local-testing, FAQ) plus serve.py's `/api/agents/*` block. Notes are in `vault/fleet/agents/glasses/memory.md`. Here's the scope.

---

## TL;DR — one assumption in my ROLE was wrong, in our favor

ROLE.md assumed we'd fight raw BLE / low-MTU transport. **We don't.** On the G2, **the app is a normal web app running on your phone** (inside the Even app's WebView); the glasses are just a remote display + touchpad. So the app talks to `omidlab.net` over **plain `fetch()` with full phone bandwidth**. The only thing that's tiny is the *screen*, not the pipe. That makes this much closer to the `mobile_chat` browser-client shape than to embedded BLE work — we mostly reuse the existing `/api/agents/*` endpoints as-is.

**One concrete backend blocker:** serve.py sets no CORS headers today (serve.py:28 only adds `Accept-Ranges`). The WebView `fetch()` will be CORS-blocked until we add `Access-Control-Allow-Origin`. Small change, needs website_builder. Details in §2.

---

## 1. Capability summary (what the G2 can actually do)

**Display** — 576×288 px per eye, monochrome (16 levels of green), single fixed LVGL font (no size/style/alignment/color control). UI = absolutely-positioned **containers**, no CSS/flexbox. Max 4 image + 8 other containers per page.
- **TextContainer**: plain text, wraps at width, `\n` line breaks, firmware-scrolls on overflow. ~400–500 chars full-screen. Flicker-free in-place updates via `textContainerUpgrade`.
- **ListContainer**: native scrollable list, **≤20 items × ≤64 chars/item**, firmware handles scroll + highlight. ← this is our agent roster, basically for free.
- **ImageContainer**: greyscale ≤144×288 (we likely don't need images for v1).
- Chars outside the font set are silently dropped (emoji/box-drawing → gone; must sanitize agent text).

**Input** — gestures only: **press / double-press / swipe-up / swipe-down** (temple touchpad or R1 ring), via one `onEvenHubEvent` callback. **Only one container per page captures events.** There is **no text-entry field** on the glasses.

**Voice** — 4-mic array streams 16kHz PCM to the app (`bridge.audioControl(true)`); no on-device transcription and no speaker. We'd pipe PCM to STT ourselves — and serve.py already has `/api/transcribe` (Whisper). This is the natural path for free-form send-back later.

**Misc** — battery/wearing/charging status, IMU, user info, localStorage/IndexedDB persistence. **No push notifications to glasses. No reliable background execution** — when the phone backgrounds the Even app the WebView suspends and polling stalls. So updates are **foreground-only**; treat resume as a cold restart.

**Dev/build** — standard Vite project, only Even-specific file is `app.json`. Dev loop: `npm run dev` → `evenhub qr --url http://<lan-ip>:5173` → scan in Even app → live on glasses with HMR (phone must share your LAN). Package `.ehpk` and sideload as a personal app for daily use. You sideload/test; I write→handoff→you test→iterate.

---

## 2. Comm architecture — options + recommendation

```
[G2 glasses] --BLE 5.2--> [Even app WebView on your phone: OUR web app] --HTTPS fetch--> [omidlab.net / serve.py] --tmux--> [agents]
              display+input          app logic + networking live here
```

| Option | What | Verdict |
|---|---|---|
| **A. WebView `fetch()` → omidlab.net `/api/agents/*` (poll)** | App polls existing endpoints on foreground + a timer; renders list/text. | ✅ **Recommended.** Reuses everything that exists; matches mobile_chat. Only needs CORS + 1–2 compact endpoints. |
| B. WebSocket / SSE stream from serve.py | Push status changes instead of polling. | ⏳ Later. WebSockets work but **drop when backgrounded** (esp. Android) and need reconnect logic; polling is simpler and the screen only updates when you're looking at it anyway. |
| C. Direct BLE / custom relay from glasses | — | ❌ Not possible. No app logic or arbitrary network runs on the glasses; SDK gives no raw-BLE socket. |

**Recommendation: Option A (foreground polling over HTTPS to the existing API).**

What it needs on the backend (coordinate with website_builder, who owns serve.py restarts):
1. **CORS headers** — add `Access-Control-Allow-Origin` (+ `X-Agents-Password`, `Content-Type` allowed headers, and OPTIONS preflight handling) in serve.py's `after_request`. **Hard blocker** — without it the WebView gets nothing. Can scope the header to the glasses app origin.
2. **Auth** — the API already gates on `?pw=fabregas` / `X-Agents-Password`. Fine for v1; I'll store it in `app.json`/localStorage, not hardcode in a public build. (Open Q4.)
3. **Compact endpoints (nice-to-have, not blocking)** — the screen wants ≤20 list rows and ≤500-char panes. I'd add:
   - `GET /api/glasses/roster` → `[{name, status, last_line}]` (one summary line per agent, pre-truncated to ~60 chars, font-sanitized) — one call instead of N pane captures.
   - `GET /api/glasses/agent/<name>?lines=12` → already-cleaned, last-N-lines tail of the pane sized for the display.
   - `POST /api/agents/<name>/send` — reuse as-is for send-back.
   Putting truncation/sanitization server-side keeps the glasses app dumb and avoids shipping 200-line panes over the wire to render 12.

I'll draft these as a small additive route block (mirroring how our_wandb added `/runs/` + `/api/runs/*`) and hand them to website_builder rather than editing serve.py directly.

---

## 3. App MVP shape

Three pages, gesture-navigated, foreground-polling. Designed around "one capture container per page" and the 20-item list limit.

**Page 1 — Roster** (ListContainer, the home screen)
```
▸ manager        working
  backbones      working
  yams           idle
  mac            blocked
  website_builder working
  our_wandb      done
  ...            (≤20 rows; swipe up/down scrolls; press opens)
```
- Source: `/api/glasses/roster` (or `/api/agents/list` + per-agent pane for last_line in v0). Poll every ~5s while foreground.
- Swipe up/down = move selection; **press = open that agent (Page 2)**.

**Page 2 — Agent detail** (TextContainer, flicker-free `textContainerUpgrade` on poll)
```
yams · working
─────────────
…last ~12 lines of the agent's
tmux pane, cleaned + wrapped to
the 576px width…
─────────────
press: send  ·  dbl: back
```
- Source: `/api/glasses/agent/<name>?lines=12`, polled ~5s.
- **Press → Page 3 (send).  Double-press → back to roster.**

**Page 3 — Send-back** (canned messages for v1, since there's no keyboard)
```
→ yams
  1 status?
  2 what's blocking you?
  3 ✓ looks good, continue
  4 stop (interrupt)
  swipe: pick · press: send · dbl: cancel
```
- Swipe to highlight a preset; press sends via `POST /api/agents/<name>/send` (option 4 → `/interrupt`). Confirmation flashes, returns to Page 2.
- **v2:** add a "🎤 dictate" entry that runs `audioControl` → `/api/transcribe` → send the transcript. This unlocks true free-form chat once the canned-message skeleton works.

A top "manager" shortcut: manager is just the first/pinned row of the roster — press it to jump straight into talking to manager. No separate UI needed.

This is the smallest thing that's genuinely useful: glance at fleet status hands-free, drill into one agent, nudge it with a preset. Everything beyond (voice, SSE, custom per-agent panels) layers on without rework.

---

## 4. Open questions for you

1. **Voice in v1 or v2?** Cleanest free-form input is mic→Whisper, but it adds PCM plumbing. I lean **v2** — ship canned-message send-back first, add dictation once the skeleton's proven on-device. OK?
2. **Status source of truth.** Roster status (`idle/working/done/blocked`) comes from each agent's `status.md`. Several agents may not keep these fresh. Want the roster to fall back to a heuristic (e.g. "pane changed in last N s → working") or just show whatever `status.md` says, stale or not?
3. **How many agents on screen?** Roster caps at 20 list items; the fleet is ~17 now. Fine today, but if it grows past 20 do you want filtering (e.g. only non-idle), or pinned favorites + an "all" subpage?
4. **Auth / public build.** Daily use = a sideloaded personal `.ehpk`. The `fabregas` password would live in that private build's localStorage. Comfortable with that, or want a dedicated glasses token + IP allowlist on the new endpoints?
5. **Polling cadence.** ~5s while the app is foreground (no background updates exist anyway). Good, or tighter/looser?
6. **Do you have the developer account + dev mode enabled** on the glasses/phone already, and is `evenhub` CLI installed on your Mac? That determines whether the very first handoff is "here's the app" vs "here's the app + setup steps."

---

**Next step:** on your OK (and answers to Q1–Q6), I'll (a) hand website_builder the CORS + compact-endpoint patch, (b) scaffold the Vite + `@evenrealities/even_hub_sdk` app under `agents/glasses/app/`, and (c) ship Page 1 (roster) first as the smallest sideloadable thing you can verify on-device, then layer Pages 2–3.

— glasses

---

## UPDATE 2026-06-12 — v1 roster app built, ready to test

You said keep it minimal and password-in-build is fine. Built it. **Key simplification: no server/CORS changes needed for the dev loop** — the app reaches the fleet API through Vite's dev proxy (proxy runs on your Mac, server-to-server), so CORS is sidestepped until we package a standalone `.ehpk` later.

**What it is:** single roster page — one line per agent + status, live-polled every 5s from `/api/agents/list`. tap = refresh, double-tap = exit. Fetch errors render on the glasses so we can debug from what you read back.

**Where:** `agents/glasses/app/` (committed + pushed to `github.com/cameronosmith/agents_stuff`). Full setup in `app/README.md`.

**To run (on your Mac):** pull the repo, then:
```bash
cd agents_stuff/agents/glasses/app
npm i -g @evenrealities/evenhub-cli   # one-time
npm install                            # one-time
npm run dev                            # Vite on :5173
npm run qr                             # prints QR → scan in Even Hub tab
```
Prereq: developer mode enabled on the glasses (hub.evenrealities.com sign-in), phone on the same Wi-Fi as the Mac, Even app kept foreground.

Open Qs 2/3/5 (status source, >20 agents, poll cadence) still stand but **none block this first test** — I defaulted to: show `status.md`'s first word as-is, no cap yet (~17 < 20 limit), 5s poll. Tell me what breaks on-device and I'll iterate (detail page + send-back next).

— glasses
