Dashboard — the glass cockpit·
dashboard_server.py is a FastAPI app that serves a vanilla-JS PWA from docs/ (yes, that folder — it predates this site),
proxies the rover's cameras and telemetry, hosts the agent behind a WebSocket, and owns the passkey gate. It is the only
service the Cloudflare tunnel exposes.


The gate·
Everything under /api/* and both WebSockets require a passkey session (a short-lived JWT the browser gets after a
WebAuthn login). A global middleware enforces it; the public allow-list is exactly:
GET /api/health— for uptime checks,/auth/*— the WebAuthn ceremony itself,- the page shells
/,/replay,/field-card,/trust,/caand the static/css,/js.
Anonymous /api/* → 401; anonymous WebSocket → close 4401. Multi-admin: enrol more passkeys from the ⚙️ drawer,
rename or delete them — the last one cannot be removed. Full story in Auth; the route-by-route table is
generated from the code in the Dashboard API.
What's on screen·
| region | what it does | wired to |
|---|---|---|
| cameras | big front view, small rear PiP; tap to swap; 💡 lamp; 📷 snapshot | GET /api/frame/{front\|rear} polled (~700 ms), POST /api/lamp, GET /api/screenshot |
| HUD | battery, GPS fix, signal, heading, speed, lamp | GET /api/telemetry (the SDK's /data) |
| joystick / WASD | continuous /control frames while held, e-stop on release, blur or Space |
POST /api/control {linear, angular, duration} |
| Ask | chat with the agent; tokens, tool receipts, reasoning stream back | WS /ws/chat |
| 🎙 browser voice | your phone's mic ↔ bidi model ↔ phone speaker (PCM16 frames) | WS /ws/voice |
| 🎭 persona pills | rover-voice · thinker · telegram · rec — ON/OFF live, plus 🕹 thinker-drive in the sheet | GET/POST /api/personas[/name] |
| ⚙️ drawer | system prompt, model id, voice provider/name, masked .env keys, passkeys — saves rebuild the agent |
GET/POST /api/config, /auth/credentials* |
🎞 replay (/replay) |
dataset browser: episodes, per-episode video windows, reasoning trace, memory search | /api/replay/* |
The agent behind the socket·
One agent instance per dashboard process, rebuilt when config changes, one turn at a time (_agent_busy lock). Each turn
the server attaches the latest front/rear frames to the prompt (build_turn_input), streams the Strands callback events as
token / tool / reasoning / done messages, and — if the turn issued at least one action — the auto-recorder saves it as a
dataset episode.
POST /api/chat is the synchronous twin of the socket, built for machines: it is what tiny.technology's endpoint proxy calls
when a sibling robot asks Scout something. That path builds the agent with fleet=True, i.e. no fleet tools, which is how
the one-hop depth cap is enforced. → Fleet
Running it outside docker·
make dashboard # http://localhost:8080 (auth still on; passkeys need HTTPS → use dashboard-tls)
make dashboard-tls # https on :8443 with a self-signed cert so WebAuthn works on the LAN
make field-card # printable QR card with the URL + CA fingerprint for a demo venue
The page's WebSocket target is a parameter (?ws=… → localStorage → page origin), so the static shell can be hosted anywhere
and pointed at any rover.
Keyboard cheat-sheet·
| keys | action |
|---|---|
| W S / A D | forward/back · turn left/right (hold several for arcs) |
| Space | stop |
| [ ] · 1–5 | speed down/up · presets |
| Shift | turbo while held |
| Enter in Ask | send |