From f25128f70acb8253f3e0540d725820024b753f63 Mon Sep 17 00:00:00 2001 From: Brett Chien Date: Sun, 9 Aug 2026 20:53:36 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs(adr):=20Studio=20desktop=20console=20?= =?UTF-8?q?=E2=80=94=20swappable=20skin=20over=20an=20MCP=20core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proposes the GUI architecture: skin<->core boundary is the existing oab-mcp surface (stdio local + streamable-HTTP remote), a Tauri web skin as the throwaway interim front-end (Rust backend = thin bridge), SwiftUI native skins later on macOS/iOS, and polling-first live updates. Builds on ADR-1/2. --- docs/adr/desktop-console.md | 145 ++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 docs/adr/desktop-console.md diff --git a/docs/adr/desktop-console.md b/docs/adr/desktop-console.md new file mode 100644 index 0000000..67975d7 --- /dev/null +++ b/docs/adr/desktop-console.md @@ -0,0 +1,145 @@ +# ADR: Studio Desktop Console — a swappable skin over an MCP core + +- **Status:** Proposed +- **Date:** 2026-08-09 +- **Author:** @brettchien +- **Reviewers:** _pending_ +- **Tracking issues:** builds on [deployment-control-plane](./deployment-control-plane.md) (ADR-2) and [agent-lifecycle](./agent-lifecycle.md) (ADR-1) + +> **Y-statement.** In the context of giving humans a director's console over the +> control plane, facing the need to ship a cross-platform GUI now yet rewrite +> the skin natively (SwiftUI) on Apple platforms later — and to serve mobile +> where a device must not hold cloud credentials — we decided to make the +> **skin ↔ core boundary the existing MCP surface** (`oab-mcp`), served over +> **stdio locally and streamable-HTTP remotely**, with a **Tauri web skin** as +> the interim front-end and a thin Rust bridge, **accepting that the interim web +> UI is throwaway** when the native skins arrive and that **live updates start +> as polling**, not push. + +--- + +## 1. Context & Problem + +ADR-2 gave us a control plane — a generic read/write model over the vendored +`oabctl`, exposed to agents through an MCP surface (`oab-mcp`, six `deploy_*` +tools). "Agents do control, humans direct" needs the *humans direct* half: a +**director's console** that surfaces the live deployment roster and lets a human +approve/drive the same actions. + +Constraints that shape the design: + +- **Native endgame is Swift.** On macOS/iOS the long-term skin is SwiftUI, not a + webview. Whatever we build now is an **interim** skin. +- **Mobile can't hold cloud creds and can't spawn subprocesses.** An iOS app + must be a thin client to a **remote** core; it cannot run the AWS-touching + control plane locally, nor speak to it over stdio. +- **The console is a dense, live dashboard.** Roster + per-instance 6-state + + counters now; per-agent diff and wave/phase orchestration later. +- **We must not fork control logic.** The console is *another front-end*, not a + second implementation of observe/apply. + +## 2. Decision Drivers + +- **One control surface for humans and agents** — the GUI drives the *same* MCP + tools an agent does; no parallel command path that can diverge. +- **Skin is swappable** — web now, SwiftUI later, with **zero change to core**. +- **Credentials stay server-side** — the device authenticates to a core service; + AWS keys never leave the cloud boundary. +- **Iteration speed for a visual dashboard** — reuse the mature web charting / + table / layout ecosystem for the first console. +- **Reuse what exists** — `studio-cp` (read/write model) and `oab-mcp` (tools). + +## 3. Decision + +### 3.1 Three layers, boundary at MCP + +``` + core studio-cp (Rust) read/write model over oabctl (ADR-2) + service oab-mcp (Rust) core exposed as MCP tools ── the contract + ├─ stdio local desktop (spawned child) + └─ streamable-HTTP remote / browser / mobile + skin ├─ web (Tauri desktop, or plain browser) ← now + └─ SwiftUI (macOS + iOS, native) ← later + every skin — and every agent — is a client of the SAME MCP surface +``` + +The **skin ↔ core contract is MCP**, a language-neutral wire protocol. A web +skin (JS), a SwiftUI skin (Swift), and an agent all speak it. Swapping the skin +is swapping an MCP client; the core and service are untouched. This is the +crux — we deliberately **do not** bind the skin to Rust (Tauri commands / FFI) +as the primary contract, because Swift and remote/mobile clients cannot use a +Rust-internal boundary. + +### 3.2 Transports + +`oab-mcp` today serves **stdio**. We add a **streamable-HTTP** transport (rmcp +supports it; openab's facade already uses it) so the identical tool surface is +reachable remotely. Desktop uses stdio (or local HTTP); browser and iOS use +HTTP to a remote core that holds the AWS credentials. + +### 3.3 Interim skin: Tauri + +The first skin is a **web front-end wrapped by Tauri**. Rationale: the interim +UI is throwaway (native endgame is Swift), so optimize for speed and polish on a +dense dashboard — where the web ecosystem wins. The same web UI **doubles as a +browser console** (talking MCP-over-HTTP), so we get desktop + browser from one +build. Tauri's Rust backend is a **thin bridge only** — it spawns/connects the +local `oab-mcp` (or embeds `studio-cp` and re-exposes the same MCP surface to the +webview); it does **not** introduce a second Rust command API that could drift +from MCP. + +### 3.4 Live updates: polling first + +The console **polls** `deploy_list` / `get_agent_states` on an interval to keep +the roster and 6-state live. A push/subscribe channel on `oab-mcp` is a later +increment, not a prerequisite for the console skeleton. + +### 3.5 First console scope + +Aligned to the reference director's console: **deployment roster**, each row's +**live 6-state** (ADR-1) plus `desired`/`current`/`ready`, and **per-instance +phase**. Deferred to later slices: per-agent diff, wave/phase orchestration, the +environment/action panel. + +## 4. Consequences + +**Positive** +- Humans and agents share one control surface; no divergent command path. +- Skin swap (web → SwiftUI) costs zero core change; interim throwaway is bounded + to UI code. +- Credentials never reach a device; mobile is a thin client by construction. +- Desktop **and** browser from a single web build. + +**Negative / costs** +- The interim web UI is discarded when native skins land (accepted). +- A new **streamable-HTTP** transport + a **remote-core hosting** story (auth, + where it runs) are now on the roadmap. +- Polling has latency/refresh-rate limits until streaming is added. + +**Neutral** +- Per-caller authorization stays deferred (inherits ADR-2's interim ceiling: the + AWS credential boundary). A human console makes this more pressing — see §6. + +## 5. Alternatives Considered + +- **Dioxus (Rust-native skin).** One Rust codebase for web/desktop/mobile, + shares types with core. Rejected as the *primary* path because the native + endgame is Swift (so a Rust cross-platform skin is still interim) and the Rust + UI ecosystem is thinner for a dense dashboard. Revisit only if the endgame + ever becomes "one Rust app, no Swift." +- **FFI (UniFFI / swift-bridge): Swift links the Rust core directly.** Tightest + native integration, but the contract becomes Rust-generated Swift bindings — + not language-neutral, unusable by a web or remote client, and a second + boundary to maintain. Keep as an *optimization* a native app may add later, + not the primary contract. +- **Electron.** Heavy, Node-centric bridge to a Rust core; no advantage over + Tauri here. + +## 6. Open Questions / Deferred + +- **Authorization.** A human-facing console sharpens the need for per-caller + authz beyond the AWS credential ceiling (ADR-2 §deferred). Where does identity + live — the core service, an existing broker, OIDC? +- **Remote-core hosting.** Where the streamable-HTTP core runs, and how a + browser/iOS client authenticates to it. +- **Streaming.** The subscribe/push channel that replaces polling. From 26600a926d83f2480064ced5f87ff89c7618bfba Mon Sep 17 00:00:00 2001 From: Brett Chien Date: Sun, 9 Aug 2026 21:36:22 +0800 Subject: [PATCH 2/3] docs(adr): refocus console on macOS + Windows; iOS an option Desktop (macOS/Windows) runs the core locally over stdio, so streamable-HTTP and remote-core hosting are deferred to when browser/iOS land. Tauri covers both desktop platforms now; macOS goes SwiftUI later, Windows-native open. --- docs/adr/desktop-console.md | 89 ++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/docs/adr/desktop-console.md b/docs/adr/desktop-console.md index 67975d7..ac07652 100644 --- a/docs/adr/desktop-console.md +++ b/docs/adr/desktop-console.md @@ -7,14 +7,13 @@ - **Tracking issues:** builds on [deployment-control-plane](./deployment-control-plane.md) (ADR-2) and [agent-lifecycle](./agent-lifecycle.md) (ADR-1) > **Y-statement.** In the context of giving humans a director's console over the -> control plane, facing the need to ship a cross-platform GUI now yet rewrite -> the skin natively (SwiftUI) on Apple platforms later — and to serve mobile -> where a device must not hold cloud credentials — we decided to make the -> **skin ↔ core boundary the existing MCP surface** (`oab-mcp`), served over -> **stdio locally and streamable-HTTP remotely**, with a **Tauri web skin** as -> the interim front-end and a thin Rust bridge, **accepting that the interim web -> UI is throwaway** when the native skins arrive and that **live updates start -> as polling**, not push. +> control plane, facing a **macOS + Windows desktop** target now — with a +> SwiftUI-native macOS skin later and iOS kept as a future option — we decided to +> make the **skin ↔ core boundary the existing MCP surface** (`oab-mcp`), served +> over **stdio** for a locally-run core (with **streamable-HTTP deferred** for +> browser/mobile), using a **Tauri web skin** across macOS/Windows now behind a +> thin Rust bridge, **accepting that the interim web UI is throwaway** on +> platforms that later go native and that **live updates start as polling**. --- @@ -28,11 +27,15 @@ approve/drive the same actions. Constraints that shape the design: -- **Native endgame is Swift.** On macOS/iOS the long-term skin is SwiftUI, not a - webview. Whatever we build now is an **interim** skin. -- **Mobile can't hold cloud creds and can't spawn subprocesses.** An iOS app - must be a thin client to a **remote** core; it cannot run the AWS-touching - control plane locally, nor speak to it over stdio. +- **Targets now: macOS + Windows desktop.** Both run the core locally. On macOS + the long-term skin is **SwiftUI**; Windows-native is open (it may stay on the + web skin, or gain a native skin later). Whatever we ship now is an **interim** + skin on platforms that later go native. +- **iOS is kept as a future option, not a driver.** We don't build for it now, + but the contract choice below must not preclude it — a phone can't hold cloud + creds or spawn a stdio subprocess, so it would later be a thin client to a + **remote** core over the network. Designing the boundary as a protocol (not a + Rust-internal binding) keeps that door open at no cost today. - **The console is a dense, live dashboard.** Roster + per-instance 6-state + counters now; per-agent diff and wave/phase orchestration later. - **We must not fork control logic.** The console is *another front-end*, not a @@ -56,10 +59,11 @@ Constraints that shape the design: ``` core studio-cp (Rust) read/write model over oabctl (ADR-2) service oab-mcp (Rust) core exposed as MCP tools ── the contract - ├─ stdio local desktop (spawned child) - └─ streamable-HTTP remote / browser / mobile - skin ├─ web (Tauri desktop, or plain browser) ← now - └─ SwiftUI (macOS + iOS, native) ← later + ├─ stdio local desktop core (macOS / Windows) ← now + └─ streamable-HTTP remote / browser / iOS ← deferred + skin ├─ web (Tauri: macOS + Windows; also plain browser) ← now + ├─ SwiftUI (macOS, native) ← later + └─ iOS / Windows-native ← optional, later every skin — and every agent — is a client of the SAME MCP surface ``` @@ -72,21 +76,25 @@ Rust-internal boundary. ### 3.2 Transports -`oab-mcp` today serves **stdio**. We add a **streamable-HTTP** transport (rmcp -supports it; openab's facade already uses it) so the identical tool surface is -reachable remotely. Desktop uses stdio (or local HTTP); browser and iOS use -HTTP to a remote core that holds the AWS credentials. +`oab-mcp` today serves **stdio**, which is all the macOS/Windows desktop needs: +each app spawns a local core that resolves AWS credentials from the standard +chain, exactly like a CLI. A **streamable-HTTP** transport (rmcp supports it; +openab's facade already uses it) is **deferred** — it lands when the browser or +iOS clients do, so the identical tool surface is reachable remotely against a +core that holds the credentials server-side. No HTTP work is required for the +current desktop scope. ### 3.3 Interim skin: Tauri -The first skin is a **web front-end wrapped by Tauri**. Rationale: the interim -UI is throwaway (native endgame is Swift), so optimize for speed and polish on a -dense dashboard — where the web ecosystem wins. The same web UI **doubles as a -browser console** (talking MCP-over-HTTP), so we get desktop + browser from one -build. Tauri's Rust backend is a **thin bridge only** — it spawns/connects the -local `oab-mcp` (or embeds `studio-cp` and re-exposes the same MCP surface to the -webview); it does **not** introduce a second Rust command API that could drift -from MCP. +The first skin is a **web front-end wrapped by Tauri**, which covers **macOS and +Windows** (and Linux) from one build. Rationale: the interim UI is throwaway on +platforms that go native (macOS → SwiftUI), so optimize for speed and polish on a +dense dashboard — where the web ecosystem wins — and get two desktop platforms +now for free. The same web UI **doubles as a browser console** later (talking +MCP-over-HTTP once that transport lands). Tauri's Rust backend is a **thin bridge +only** — it spawns/connects the local `oab-mcp` (or embeds `studio-cp` and +re-exposes the same MCP surface to the webview); it does **not** introduce a +second Rust command API that could drift from MCP. ### 3.4 Live updates: polling first @@ -107,13 +115,16 @@ environment/action panel. - Humans and agents share one control surface; no divergent command path. - Skin swap (web → SwiftUI) costs zero core change; interim throwaway is bounded to UI code. -- Credentials never reach a device; mobile is a thin client by construction. -- Desktop **and** browser from a single web build. +- **macOS + Windows** from a single Tauri build now; browser/iOS reachable later + over the same MCP surface without a core rewrite. +- Local desktop core resolves creds from the standard AWS chain — no new + credential-hosting story required for the current scope. **Negative / costs** -- The interim web UI is discarded when native skins land (accepted). -- A new **streamable-HTTP** transport + a **remote-core hosting** story (auth, - where it runs) are now on the roadmap. +- The interim web UI is discarded when a platform goes native (macOS → SwiftUI). + Windows may keep the web skin indefinitely — acceptable. +- Enabling browser/iOS later pulls in the deferred **streamable-HTTP** transport + and a **remote-core hosting** story (auth, where it runs). - Polling has latency/refresh-rate limits until streaming is added. **Neutral** @@ -139,7 +150,11 @@ environment/action panel. - **Authorization.** A human-facing console sharpens the need for per-caller authz beyond the AWS credential ceiling (ADR-2 §deferred). Where does identity - live — the core service, an existing broker, OIDC? -- **Remote-core hosting.** Where the streamable-HTTP core runs, and how a - browser/iOS client authenticates to it. + live — the core service, an existing broker, OIDC? Relevant even for the local + desktop scope once more than one operator uses it. +- **iOS / browser (deferred option).** Enabling these later requires the + streamable-HTTP transport, a **remote-core hosting** story, and client auth. + Tracked as an option, not scheduled. +- **Windows-native.** Whether Windows ever leaves the Tauri web skin for a native + toolkit, or keeps it long-term. - **Streaming.** The subscribe/push channel that replaces polling. From 6f581df16569197ac73a3f5ea1e5d82f90047661 Mon Sep 17 00:00:00 2001 From: Orca Date: Mon, 10 Aug 2026 12:36:20 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs(adr):=20factcheck=20fixes=20=E2=80=94?= =?UTF-8?q?=20Tauri=20per-platform=20build,=20scope=20server-side=20creds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'from one build' → one codebase built per-platform (Tauri can't cross-compile) - scope the 'credentials stay server-side' driver to the remote/mobile path; the desktop-now path resolves AWS creds on-device (§3.2) - normalize MCP-over-HTTP → streamable-HTTP Co-Authored-By: Claude Opus 4.8 --- docs/adr/desktop-console.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/adr/desktop-console.md b/docs/adr/desktop-console.md index ac07652..3a0da2f 100644 --- a/docs/adr/desktop-console.md +++ b/docs/adr/desktop-console.md @@ -46,8 +46,10 @@ Constraints that shape the design: - **One control surface for humans and agents** — the GUI drives the *same* MCP tools an agent does; no parallel command path that can diverge. - **Skin is swappable** — web now, SwiftUI later, with **zero change to core**. -- **Credentials stay server-side** — the device authenticates to a core service; - AWS keys never leave the cloud boundary. +- **Credentials can stay server-side (remote/mobile)** — a remote/mobile client + authenticates to a core service and holds no AWS keys; the **local desktop + resolves creds on-device, like a CLI** (§3.2). Keys leaving the boundary is + only avoided on the deferred remote path, not the desktop-now path. - **Iteration speed for a visual dashboard** — reuse the mature web charting / table / layout ecosystem for the first console. - **Reuse what exists** — `studio-cp` (read/write model) and `oab-mcp` (tools). @@ -86,12 +88,13 @@ current desktop scope. ### 3.3 Interim skin: Tauri -The first skin is a **web front-end wrapped by Tauri**, which covers **macOS and -Windows** (and Linux) from one build. Rationale: the interim UI is throwaway on +The first skin is a **web front-end wrapped by Tauri**, which covers **macOS, +Windows** (and Linux) from **one codebase** — each platform built on its own CI +runner (Tauri does not cross-compile; one `tauri build` does not emit both). Rationale: the interim UI is throwaway on platforms that go native (macOS → SwiftUI), so optimize for speed and polish on a dense dashboard — where the web ecosystem wins — and get two desktop platforms now for free. The same web UI **doubles as a browser console** later (talking -MCP-over-HTTP once that transport lands). Tauri's Rust backend is a **thin bridge +streamable-HTTP once that transport lands). Tauri's Rust backend is a **thin bridge only** — it spawns/connects the local `oab-mcp` (or embeds `studio-cp` and re-exposes the same MCP surface to the webview); it does **not** introduce a second Rust command API that could drift from MCP. @@ -115,7 +118,7 @@ environment/action panel. - Humans and agents share one control surface; no divergent command path. - Skin swap (web → SwiftUI) costs zero core change; interim throwaway is bounded to UI code. -- **macOS + Windows** from a single Tauri build now; browser/iOS reachable later +- **macOS + Windows** from one Tauri codebase now (built per-platform); browser/iOS reachable later over the same MCP surface without a core rewrite. - Local desktop core resolves creds from the standard AWS chain — no new credential-hosting story required for the current scope.