Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions docs/adr/agent-consoles.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,38 +73,44 @@ Selecting an agent (from the roster or the registry) opens its console with two

The management console keeps its current shape (roster + fleet control + its own chat); an agent console is a **focused, single-agent** view reached by selection. Both share the tab chrome; the agent console is a new `<section>` + tab in the vanilla-TS `console` (render + wiring in `render.ts`/`main.ts`), keyed by the selected agent.

## 5. Decision — Part D: the remote file editor (an MCP files server, management-plane)
## 5. Decision — Part D: the remote file editor (Studio-brokered fs — exec-backed for owned runtimes, MCP-server for federation)

Studio needs to **list / read / write** files on a remote agent's filesystem. **Decision: fs is an MCP surface — a files server the target agent exposes — not a bespoke `fs/*` method set on `/acp`.** (This resolves Part D's original open question in favour of the agent-served MCP variant.)
Studio needs to **list / read / write** files on a remote agent's filesystem. **Decision: fs is a Studio-brokered capability with two interchangeable backends behind one surface. The default backend for a runtime Studio controls is the platform's exec channel (ECS `ExecuteCommand`, k8s `pods/exec`); the fallback for a runtime Studio does *not* control (a foreign agent reachable only over `/acp`) is an fs MCP server the target agent exposes.** The `oab` tool + the UI source are identical across both.

The reason is the *actor*. fs is not only Studio's UI reading a disk; the real driver is the **management agent reaching into a target agent to manage it**, of which editing a file is one operation. That is the ordinary MCP direction — an agent consuming tools — and the management→target control channel has to exist regardless. A bespoke `fs/*` RPC would build that channel a second time, just for files. (An earlier draft preferred bespoke `fs/*` by scoping fs as *Studio-reads-agent*, which is the reverse MCP direction; once the actor is the management agent, that objection dissolves.)
Two things are fixed regardless of backend; only the backend varies:

- **Mechanism — an fs MCP server on the target agent.** Each agent exposes a small files server (`list` / `read` / `write` / `stat` tools), roots-scoped (below). It is consumed by the **management agent** as tools, and by **Studio's UI** for the read-only browser — one fs surface, one policy point, no second wire. (Whether the target *runtime* ships this server or it is injected is coordinated with openab, the way streaming/`tool_call` are; but the shape is now settled as MCP, not native ACP methods.)
- **Topology — Studio-brokered (decided).** The management agent does **not** hold every target's bearer. Its fs/management call arrives as a Studio reverse-MCP `oab` tool; Studio then dials the target's `/acp` with the token it already holds in `agents.toml` and relays to that agent's fs server. **Bearers and policy stay in Studio; the `oab` grant stays management-only** (least privilege, Part A). Two MCP hops — the management agent's MCP into Studio, Studio's relay into the target's MCP ("MCP in MCP"). The rejected alternative — the management agent connecting to each target directly — spreads N bearers out of Studio and breaks least privilege.
- **Editable-root scoping (agent-declared).** The agent advertises its editable root(s) via the files server's roots capability; Studio / `oab` never assume `/`. `writable` defaults **off**; writes outside the declared root are refused server-side. This bounds a very powerful capability (arbitrary write into a running agent = arbitrary persona/behaviour change) — now enforced as **tool-level gating in the fs server**, not in a bespoke wire.
- **Apply semantics.** `write` persists the file. Some files hot-reload (the agent watches); others need a restart to take effect. Studio's **Apply** = write; for restart-required files, offer an explicit **"restart instance"** action reusing the control-plane scale-cycle (scale→0→1, ADR-2). Studio does not guess reload semantics — it writes and, on request, cycles.
- **Topology — Studio-brokered (unchanged, decided).** The management agent never holds a target's exec credential or bearer. Its fs call arrives as a Studio reverse-MCP `oab` tool; **Studio** performs the operation with the credential *it* holds. **Credentials and policy stay in Studio; the `oab` grant stays management-only** (least privilege, Part A). The rejected alternative — handing the agent the exec cred / each target's bearer directly — concentrates cluster-grade RCE in an LLM and breaks least privilege.
- **Actor — unchanged.** fs is not only Studio's UI reading a disk; the driver is the **management agent reaching into a target to manage it**, editing a file being one op. Both consumers (the agent as an `oab` tool, the UI as the read-only browser) hit the same Studio surface — one fs policy point.

**The deciding axis is: does Studio own the target's runtime?**

- **Backend 1 — Studio platform-exec (primary; every agent in the fleet today).** Studio reaches the target's filesystem via the orchestrator's exec channel — ECS `ExecuteCommand` (SSM), k8s `pods/exec` — using a control-plane credential Studio already holds (it is the plane that scales these services). **Ships now; no openab dependency.** This covers 100% of the current fleet (all agents run on our own ECS/k8s).
- **Backend 2 — target-hosted fs MCP server (fallback; un-owned runtimes / federation).** For an agent Studio can reach only over `/acp` but whose platform it does **not** control, fs rides a small files server the agent exposes (`list`/`read`/`write`/`stat`), relayed through the `oab` tool over the token in `agents.toml`. This is the previously-decided MCP mechanism; it **depends on openab** and is **deferred until such an agent exists** — exec cannot reach a platform Studio doesn't own, so this is the escape hatch, not the day-1 path.

- **Editable-root scoping — same intent, different enforcement locus.** `writable` defaults **off**; no `/`-wide default; browsing/writes are bounded to declared root(s). **Enforcement locus differs by backend and this is the material trade the exec backend buys:** the MCP server enforces roots *at the resource* (the agent declares roots, the server refuses outside them); **exec has no OS root-fence, so with the exec backend `roots`/`writable` become Studio-side path validation + safe argv-form command construction — advisory, not resource-enforced.** A Studio path-validation bug is therefore a full-container write. Mitigations: hard path canonicalization + prefix checks, argv-form exec (never shell-string interpolation), `writable` default-off, explicit Apply. We accept this for owned runtimes because Studio is *already* the control plane holding a fleet-management credential — exec is the same trust tier, not a new principal — and the agent still never receives that credential.
- **Apply semantics (unchanged).** `write` persists the file. Some files hot-reload (the agent watches); others need a restart. Studio's **Apply** = write; for restart-required files, offer an explicit **"restart instance"** action reusing the control-plane scale-cycle (scale→0→1, ADR-2). Studio does not guess reload semantics — it writes and, on request, cycles.

## 6. Scope

- ✅ **In (this ADR's target):** the two-console architecture; the per-agent endpoint registry (`agents.toml`, backward-compatible with `remote.toml`); the agent console shell (selector + config region + chat region); per-endpoint chat via the existing primitive; reverse-MCP publication gated to the management binding.
- ⚠️ **Sequenced after the fs server lands:** the remote file editor's write path — depends on the target agent's **fs MCP server** + Studio's `oab` relay tool (Part D). Ships read-only until then.
- ⛔ **Blocked on openab (upstream), unchanged from agent-chat-panel:** token streaming, `tool_call`/thought rendering (only `agent_message_chunk` today); the **fs MCP files server** on the target agent (Part D) is a new item in this bucket.
- **In, once the exec provider lands:** the remote file editor's read *and* write paths for **owned runtimes** — over Studio's platform-exec backend (ECS/k8s), consumed by the UI and the `oab` fs tool (Part D). No openab dependency; covers the whole current fleet.
- ⛔ **Blocked on openab (upstream), unchanged from agent-chat-panel:** token streaming, `tool_call`/thought rendering (only `agent_message_chunk` today). The **fs MCP files server** on the target agent (Part D, backend 2) is in this bucket but is now the **federation fallback**, not the gating dependency — deferred until a Studio-un-owned agent exists.
- 🚫 **Not a goal:** the multi-agent room (@mention routing, agent→agent relay, loop guard) — N independent consoles, no cross-agent surface; accessibility (single-operator internal console, per agent-chat-panel §5).

## 7. Consequences

- ✅ The operator can inspect **and reconfigure** any reachable agent, and converse with each — not just the one management agent.
- ✅ Reuses the chat primitive, the CodeMirror editor, and the reconnect/status machinery; the new weight is the registry, the per-endpoint keying of `RemoteState`, and the **`oab` fs-relay tool** (Studio → target fs MCP server).
- ✅ fs and management ride **one** channel (the management agent → `oab` → target), not two. Studio's file browser and the management agent consume the same fs server, so there is a single fs policy point instead of a bespoke wire duplicated per consumer.
- ⚠️ **Security surface grows materially.** (a) The registry holds **N bearer secrets**, not one — and they stay in Studio (brokered topology), not fanned out to the management agent. (b) Remote **file write into a live agent** is among the most powerful actions Studio can take — hence agent-declared editable roots, `writable` default-off, explicit Apply, and no `/`-wide default, enforced as tool-level gating in the fs server. (c) Reverse-MCP fleet-control tools — now including the fs relay — stay **least-privilege**, published only to the management binding, never to an arbitrary agent console. (d) Agent output rendered in chat keeps the mandatory DOMPurify sink + Tauri CSP (agent-chat-panel §6).
- ✅ Reuses the chat primitive, the CodeMirror editor, and the reconnect/status machinery; the new weight is the registry, the per-endpoint keying of `RemoteState`, and the **`oab` fs tool** (Studio → exec backend, or → target fs MCP server for federation).
- ✅ **The fleet's fs write ships without openab.** The exec backend uses a credential Studio already holds, so the remote file editor is no longer gated on an upstream fs server; that upstream item drops from "gating dependency for write" to "federation fallback, deferred." fs and management still ride **one** brokered channel (agent → `oab` → target), one fs policy point.
- ⚠️ **Security surface grows materially.** (a) The registry holds **N bearer secrets**, not one — and they stay in Studio (brokered topology), not fanned out to the management agent. (b) Remote **file write into a live agent** is among the most powerful actions Studio can take — hence `writable` default-off, explicit Apply, no `/`-wide default. **With the exec backend this gating is Studio-enforced (path validation + argv-form exec), not resource-enforced** — a real downgrade from the MCP server's server-side roots, accepted for owned runtimes because Studio is already the control plane and the credential never reaches the agent. (c) The exec backend puts a **control-plane / orchestrator credential in the fs path** — the very credential the earlier draft kept fs away from; it is held **only by Studio's broker**, never granted to an agent or an agent console. (d) Reverse-MCP fleet-control tools — including the fs tool — stay **least-privilege**, published only to the management binding. (e) Agent output rendered in chat keeps the mandatory DOMPurify sink + Tauri CSP (agent-chat-panel §6).
- ⚠️ `RemoteState` is no longer a singleton — it becomes a per-agent map of connections, each with its own status/reconnect. Bounded fan-out (operator opens a console at a time), but the lifecycle (open→dial, close→teardown) must be clean to avoid leaked sockets.
- ⚠️ Depends on openab for the target's **fs MCP server**; the write path can't ship until that exists. The architecture and read-only browser are independent and land first.
- ⚠️ The **new implementation weight is Studio's exec provider** — per-platform backends (ECS `ExecuteCommand` over SSM, k8s `pods/exec`), safe argv construction, and `ls`/`stat`/`cat`/`tee`-style op mapping with binary/large-file handling. The MCP-server backend (federation) reuses the `oab` relay and lands only when a Studio-un-owned agent needs it.

## 8. Open questions

1. ~~**`fs/*` wire** — native ACP methods or an agent-served MCP files server?~~ **Resolved: MCP files server, Studio-brokered (Part D).** Remaining sub-questions: the exact tool set/params of the fs server, and the shape of the `oab` fs-relay tool (one `fs.*` tool family fanning out by target name, vs. per-op tools). Coordinate with openab on whether the target *runtime* ships the server or Studio injects it. **Still the gating dependency for the write path.**
2. **Editable-root declaration** — the agent advertises its editable root(s) via the fs server's **roots capability** (standard MCP), so Studio / `oab` can't overreach. Open: whether roots are also surfaced to the UI up-front (to grey out non-editable paths) or only enforced on write.
1. ~~**`fs/*` wire** — native ACP methods or an agent-served MCP files server?~~ ~~Resolved: MCP files server, Studio-brokered.~~ **Re-resolved: Studio-brokered fs with two backends — platform-exec (primary, owned runtimes, ships now) + target-hosted fs MCP server (fallback, federation, deferred) (Part D).** Remaining sub-questions: the exact `oab` fs tool set/params (one `fs.*` family fanning out by target name, vs. per-op tools) — shared by both backends; and, for the federation backend, whether the target *runtime* ships the MCP server or Studio injects it. **No longer the gating dependency for write** — exec ships it.
2. **Enforcement locus for the exec backend** — with exec there is no server-side roots capability, so `roots`/`writable` are enforced by Studio: path canonicalization + prefix checks + argv-form exec. Open: how strict (a fixed allow-list of roots per agent in `agents.toml`? a `chroot`/`--workdir` confinement where the platform allows it?), and whether to surface roots to the UI up-front (grey out non-editable paths) or only enforce on write. For the federation (MCP) backend this is the server's **roots capability**, resource-enforced.
3. ~~**Registry vs `remote.toml`**~~ **Resolved & shipped:** new `agents.toml` with a `remote.toml` back-compat shim (slice 1, #66); the in-app editor now targets `agents.toml` (#69).
4. **Endpoint discovery** — purely manual (operator pastes url+token per agent) for now; could later be derived from the roster/control-plane if agents publish their `/acp` address. Out of scope here.

This is a direction-alignment ADR. Implementation lands in slices, each verified against the live gateway: (1) endpoint registry + `RemoteState` keying; (2) agent console shell + per-endpoint chat (reuses the Part C primitive); (3) remote file **read path** — the browser UI over an MCP-backed read (source-agnostic; the bespoke `fs/*` client explored in the slice-3 draft is replaced by the fs MCP server per Part D); (4) **write/apply path** — the target's fs MCP server + Studio's `oab` fs-relay tool, consumed by both the UI and the management agent.
This is a direction-alignment ADR. Implementation lands in slices, each verified against the live gateway: (1) endpoint registry + `RemoteState` keying; (2) agent console shell + per-endpoint chat (reuses the Part C primitive); (3) remote file **read path** — the browser UI over a source-agnostic read, shipped read-only against fixtures (#68; the bespoke `fs/*` client explored in the slice-3 draft was dropped per Part D); (4) **read+write/apply over the exec backend** — Studio's platform-exec provider (ECS/k8s) behind the `oab` fs tool, consumed by both the UI and the management agent; lights up slice 3's read on real owned endpoints and adds write/Apply. No openab dependency. (5, when needed) **federation backend** — the target-hosted fs MCP server over the `oab` relay, for a Studio-un-owned agent.
Loading