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
32 changes: 18 additions & 14 deletions docs/adr/agent-consoles.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,38 @@ 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
## 5. Decision — Part D: the remote file editor (an MCP files server, management-plane)

The new mechanism this ADR introduces. Studio needs to **list / read / write** files on a remote agent's filesystem, over the **same `/acp` endpoint** (uniform for local and remote agents — no direct kube/docker API access, which would need orchestrator credentials and break the "just expose the `/acp` port" model).
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.)

- **Transport — a file-ops surface on the `/acp` session (⚠️ upstream coordination with openab).** The cleanest wire is a small method set on the existing session — `fs/list` (dir → entries), `fs/read` (path → text + metadata), `fs/write` (path + text → ok), `fs/stat` — symmetric to how the reverse-MCP tunnel already rides one socket. **openab's `/acp` does not expose this today** (as with `tool_call`/streaming, this is an upstream capability, not a Studio-only slice). Studio builds a **transport-agnostic file client** (`crates/acp-tunnel`) + the UI now; the exact method names/params are settled with openab. Until the wire lands, the agent console ships **chat + read-only config display**, with the editor arriving when the fs methods do.
- **Editable-root scoping (agent-declared).** The agent declares which path(s) are editable (e.g. `~`, or an allowlist); Studio never assumes free rein over `/`. Writes outside the declared root are refused server-side. This bounds a very powerful capability (arbitrary file write into a running agent = arbitrary persona/behaviour change).
- **Apply semantics.** `fs/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.
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.)

- **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.

## 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 wire lands:** the remote file editor's write path — depends on the openab `/acp` `fs/*` method set (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/*` file-ops surface (Part D) is a new item in this bucket.
- ⚠️ **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.
- 🚫 **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 `fs/*` client.
- ⚠️ **Security surface grows materially.** (a) The registry holds **N bearer secrets**, not one. (b) Remote **file write into a live agent** is among the most powerful actions Studio can take — hence agent-declared editable roots, explicit Apply, and no `/`-wide default. (c) Reverse-MCP fleet-control tools 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-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).
- ⚠️ `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 `fs/*` wire; the write path can't ship until that exists. The architecture and read-only path are independent and land first.
- ⚠️ 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.

## 8. Open questions

1. **`fs/*` wire** — the exact method set / params on `/acp`, and whether openab models it as native ACP methods or an agent-served MCP "files" server Studio consumes. Coordinate with openab (parallels the streaming/tool_call gaps). **This is the gating dependency for the write path.**
2. **Editable-root declaration** — does the agent advertise its editable root in the `session/new` result, a dedicated `fs/roots` call, or config Studio already holds? Leaning: agent-advertised, so Studio can't overreach.
3. **Registry vs `remote.toml`** — adopt `agents.toml` as the new file (with `remote.toml` back-compat shim), or grow `remote.toml` into an array in place. Leaning: new `agents.toml`, shim the old one.
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.
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 editor read path; (4) write/apply path once the openab `fs/*` wire exists.
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.
Loading