From 5760f261b3dcbe0df8ca0b3d03f521eb76e112d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Martins?= Date: Wed, 18 Feb 2026 11:22:50 +1100 Subject: [PATCH 1/5] docs: add multi-client session attach RFD Proposes session/attach method to allow multiple ACP clients to connect to and interact with the same live agent session simultaneously. Key features: - Controller/observer client roles - First-writer-wins permission routing - Proxy-based architecture (builds on proxy-chains RFD) - WebSocket/SSE transport for multi-client support This enables unified notification dashboards, pair programming with agents, cross-device continuity, and graceful client recovery without requiring changes to existing agents. --- docs/rfds/multi-client-session-attach.mdx | 352 ++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 docs/rfds/multi-client-session-attach.mdx diff --git a/docs/rfds/multi-client-session-attach.mdx b/docs/rfds/multi-client-session-attach.mdx new file mode 100644 index 00000000..66d5ddd8 --- /dev/null +++ b/docs/rfds/multi-client-session-attach.mdx @@ -0,0 +1,352 @@ +--- +title: "Multi-Client Session Attach" +--- + +- Author(s): [@joaommartins](https://github.com/joaommartins) +- Champion: + +## Elevator pitch + +Allow multiple ACP clients to connect to and interact with the same live agent session simultaneously, enabling a unified notification and approval UI across concurrent coding agent workflows. A developer running several agent sessions (e.g., Claude Code, Codex, Gemini) could respond to permission requests, answer agent questions, and monitor progress from a single dashboard client — even if the sessions were started from different frontends. + +## Status quo + +ACP currently assumes a **1:1 relationship** between a client and an agent session. The transport layer (stdio) is inherently single-client: one process pipe per connection, no shared state between clients. This creates several problems for developers running multiple agent sessions: + +1. **No unified oversight** — Each agent session is bound to the frontend that started it. If you start Claude Code in terminal A, you can only respond to its permission requests from terminal A. +2. **Permission requests go unanswered** — When an agent blocks on `request_permission`, the developer must find the correct terminal/tab/window to respond. With multiple concurrent sessions, this becomes a significant context-switching tax. +3. **No observation without control** — There is no way to passively monitor an active session from a secondary client (e.g., a mobile device, a web dashboard, or a notification center). +4. **Session handoff is sequential, not concurrent** — `session/load` allows a new client to resume a previous session, but the original client must have disconnected first. There is no mechanism for a second client to "attach" to a live, in-progress session. + +### Current workarounds + +- **Toad** and **Agentastic.dev** solve part of this by being the single frontend that starts all sessions — but you must commit to one UI upfront. +- **`ai-agents-notifier`** and **`anot`** provide desktop notifications for permission requests but are one-way (notify only, no reply). +- **`tmux`/`screen`** can share a terminal session, but this shares the entire terminal, not the structured ACP session. + +### Related RFDs + +This proposal builds on and intersects with several existing draft RFDs: + +- **[Agent Extensions via ACP Proxies](./proxy-chains)** — The proxy/multiplexer architecture pattern is central to the recommended implementation approach for multi-client sessions +- **[Session List](./session-list)** — Discovering existing sessions (prerequisite for attach) +- **[Resuming of existing sessions](./session-resume)** (`session/resume`) — Sequential handoff between clients +- **Session Info Update** — Real-time metadata propagation (draft) +- **Session Fork** — Branching sessions (related but distinct — fork creates a copy, attach shares the original) (draft) + +## What we propose to do about it + +### Core concept: Session Attach + +Add a `session/attach` method that allows a client to connect to an **active, in-progress session** owned by another client. The attaching client receives: + +1. A replay of conversation history (like `session/load`) +2. A live stream of `session/update` notifications going forward +3. The ability to respond to `request_permission` prompts +4. Optionally, the ability to send `session/prompt` messages + +### Capability negotiation + +Agents (or proxies) advertise multi-client support during initialization: + +```json +{ + "agentCapabilities": { + "sessionCapabilities": { + "attach": { + "maxClients": 3, + "roles": ["controller", "observer"] + } + } + } +} +``` + +### Client roles + +- **Controller** — Can send prompts, respond to permission requests, cancel operations. The original client is always a controller. +- **Observer** — Receives `session/update` notifications and can see pending `request_permission` prompts, but cannot act on them. + +A client specifies its requested role when attaching: + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "session/attach", + "params": { + "sessionId": "sess_abc123def456", + "role": "controller", + "clientInfo": { + "name": "notification-dashboard", + "version": "1.0.0" + } + } +} +``` + +The response follows standard ACP patterns, returning session metadata: + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "result": { + "sessionId": "sess_abc123def456", + "role": "controller", + "history": [ + { + "type": "prompt", + "content": "Add authentication to the API", + "timestamp": "2026-02-18T14:22:00Z" + }, + { + "type": "permission_request", + "toolCallId": "call_abc", + "status": "pending" + } + ], + "connectedClients": [ + { + "name": "Claude Code", + "version": "1.0.0", + "role": "controller" + } + ] + } +} +``` + +### Permission request routing + +When an agent emits `request_permission`, it is broadcast to **all connected controller clients**. The first client to respond wins (first-writer-wins semantics). The agent then notifies all other clients that the permission was resolved: + +```json +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123def456", + "update": { + "type": "permission_resolved", + "toolCallId": "call_xyz", + "chosenOptionId": "allow_once", + "resolvedBy": { + "name": "notification-dashboard", + "version": "1.0.0" + } + } + } +} +``` + +### Transport considerations + +The current stdio transport is inherently single-client. Multi-client attach requires a **network-capable transport**. We propose this works over: + +- **WebSocket** — For persistent, bidirectional connections from multiple clients +- **HTTP + SSE** — For observer-only clients that only need to receive updates + +The agent (or an ACP proxy) would listen on a network port. This is consistent with the direction of the **[Proxy Chains RFD](./proxy-chains)**, where a proxy mediates between clients and agents. + +### Proxy architecture (recommended implementation pattern) + +Rather than requiring every agent to implement multi-client support natively, the recommended pattern is an **ACP proxy/multiplexer**, as described in the [Agent Extensions via ACP Proxies RFD](./proxy-chains): + +``` +Agent (Claude Code) ←(stdio)→ ACP Proxy ←(WebSocket)→ Client A (Toad/IDE) + ←(WebSocket)→ Client B (dashboard) + ←(WebSocket)→ Client C (mobile) +``` + +The proxy: +- Holds the single stdio connection to the agent +- Fans out `session/update` notifications to all connected clients +- Broadcasts `request_permission` to all controllers +- Routes the first response back to the agent +- Tracks client roles and connection state + +This means existing agents work **unchanged** — the proxy handles all multi-client logic. This approach aligns perfectly with the proxy-chains architecture, where proxies sit between clients and agents to extend functionality without modifying the core agent implementation. + +### Detach and lifecycle + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "method": "session/detach", + "params": { + "sessionId": "sess_abc123def456" + } +} +``` + +Lifecycle rules: +- Clients can detach voluntarily via `session/detach` +- If the original (owning) client detaches, the session continues as long as at least one controller remains +- If all clients detach, the agent MAY keep the session alive for a configurable timeout (allowing reconnection) +- Agents notify remaining clients when a peer disconnects via a `client_disconnected` session update: + +```json +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123def456", + "update": { + "type": "client_disconnected", + "client": { + "name": "Claude Code", + "version": "1.0.0", + "role": "controller" + }, + "timestamp": "2026-02-18T15:30:00Z" + } + } +} +``` + +## Shiny future + +With multi-client session attach: + +1. **Unified notification dashboard** — A single web or mobile app shows all pending permission requests across all your running agent sessions. You tap "approve" on your phone while the agent continues in your terminal. +2. **Pair programming with agents** — Two developers attach to the same agent session. One focuses on reviewing the agent's output while the other provides guidance. +3. **IDE + terminal coexistence** — Start a session in Toad or your terminal, but have your IDE also attached for inline diff display and file navigation. +4. **Monitoring and audit** — Observer clients can log all agent actions for compliance or debugging without interfering with the workflow. +5. **Graceful client recovery** — If your terminal crashes, you attach from a new terminal and pick up exactly where you were — no `session/load` replay delay because the session never stopped. +6. **Cross-device continuity** — Start a coding session on your desktop, get a permission request notification on your phone, approve it while commuting, then review the results on your laptop when you get home. + +## Implementation details and plan + +### Phase 1: Protocol specification +1. Add `session/attach` and `session/detach` methods to schema.json +2. Define `attach` capability in `sessionCapabilities` with `maxClients` and `roles` fields +3. Define client roles (controller/observer) and their semantics +4. Specify `permission_resolved` and `client_disconnected` session update variants +5. Document interaction with existing `session/load` and `session/resume` + +### Phase 2: Reference proxy implementation +6. Build a reference multi-client proxy (potentially extending `sacp-conductor` from the proxy-chains RFD) that: + - Spawns an agent subprocess over stdio + - Accepts multiple client connections over WebSocket + - Implements attach/detach and permission routing logic + - Handles MCP-over-ACP for any MCP servers the agent provides +7. Test with Claude Code, Gemini CLI, and Codex as backend agents + +### Phase 3: Client integration +8. Add attach support to at least one ACP client (Toad, Zed, or a purpose-built dashboard) +9. Build a minimal "notification center" client that demonstrates the core use case + +### Compatibility considerations +- **Fully backward compatible** — Agents that don't advertise `attach` capability work exactly as today +- **Proxy-based approach** means no changes required to existing agents +- **Optional for clients** — Clients that don't need multi-client support ignore the capability +- **Works with session/list** — Clients can discover sessions via `session/list` (if supported) before attempting to attach + +### Security considerations +- **Authentication** — Attaching clients MUST authenticate with the same identity as the session owner (or be explicitly authorized). The proxy is responsible for enforcing this policy. +- **Role enforcement** — Observer clients MUST NOT be able to send prompts or respond to permissions. The proxy enforces role restrictions. +- **Audit trail** — Permission resolutions SHOULD include which client responded (for accountability), as shown in the `resolvedBy` field. +- **Transport security** — WebSocket connections MUST use TLS in non-localhost scenarios. +- **Authorization model** — Future extensions could support fine-grained permissions (e.g., allow specific users to attach as observers but not controllers). + +## Frequently asked questions + +### Why not just use `session/load`? + +`session/load` is designed for sequential handoff — Client A stops, Client B starts. It replays the full history, which can be slow for long sessions. More importantly, `session/load` doesn't support the core use case: responding to a permission request that was issued to a *different* client while the session is still active. + +`session/attach` enables **concurrent access** to a live session, with real-time streaming of events and collaborative control over permissions. + +### Why not require agents to implement this natively? + +Most agents are simple process-based CLIs that communicate over stdio. Requiring them to manage WebSocket connections and multiple clients would be a large implementation burden. The proxy pattern means the ecosystem can adopt this with zero agent changes. + +As described in the [Agent Extensions via ACP Proxies RFD](./proxy-chains), proxies are designed to extend agent functionality without modifying agent implementations. Multi-client session attach is a perfect use case for this pattern. + +### How does this relate to the Proxy Chains RFD? + +This RFD depends heavily on the [Agent Extensions via ACP Proxies RFD](./proxy-chains). The proxy-chains RFD establishes: +- The general architecture of ACP proxies that sit between clients and agents +- The conductor component that orchestrates proxy chains +- MCP-over-ACP for tool integration + +Multi-client session attach specifies one concrete capability a proxy should support: multiplexing connections from multiple clients to a single agent session. The two RFDs are complementary: +- **Proxy Chains** provides the infrastructure for extension mechanisms +- **Multi-Client Attach** defines a specific extension use case + +The reference implementation would likely extend the `sacp-conductor` from the proxy-chains work to add multi-client multiplexing capabilities. + +### What happens if two controllers respond to the same permission request? + +First-writer-wins. The agent (or proxy) accepts the first valid response and ignores subsequent ones. All clients receive a `permission_resolved` notification indicating the outcome and which client provided the response. This is a simple, well-understood concurrency model that avoids complex conflict resolution. + +### Could observer clients be promoted to controllers? + +Yes, this could be supported via a `session/promote` method (future extension). For the initial proposal, roles are fixed at attach time to keep the design simple. + +A future `session/promote` request might look like: + +```json +{ + "jsonrpc": "2.0", + "id": 7, + "method": "session/promote", + "params": { + "sessionId": "sess_abc123def456", + "targetClient": { + "name": "mobile-app", + "version": "1.0.0" + }, + "newRole": "controller" + } +} +``` + +This would require authorization from an existing controller. + +### How does this work with MCP servers? + +The proxy (which implements multi-client logic) can provide MCP servers via [MCP-over-ACP transport](./mcp-over-acp). When the proxy advertises MCP capabilities, all attached clients can see and use the tools provided by those MCP servers. + +The conductor (from the proxy-chains RFD) handles bridging for agents that don't support native ACP transport, so proxy authors don't need to worry about agent compatibility. + +### What about rate limiting or preventing spam? + +The proxy can implement rate limiting policies to prevent a misbehaving client from overwhelming the agent with requests. This is an implementation detail left to the proxy, but common patterns might include: +- Limiting the number of permission responses per client per minute +- Throttling prompt submissions from any single client +- Implementing backpressure when the agent is overloaded + +### Can this support mobile clients? + +Yes! Mobile clients would connect via WebSocket (likely over TLS for security). A mobile app could: +1. Discover sessions via `session/list` (if the proxy supports it) +2. Attach as an observer to monitor progress +3. Receive push notifications for `request_permission` events +4. Upgrade to controller role (via future `session/promote`) to respond + +This enables the "approve on phone" use case described in the shiny future section. + +### What about latency for real-time collaboration? + +WebSocket connections provide low-latency bidirectional communication, typically in the 10-100ms range for most network conditions. This is sufficient for human-paced interactions like responding to permission requests or reviewing agent output. + +For use cases requiring sub-millisecond latency (e.g., real-time cursor sharing), additional optimizations may be needed, but these are beyond the scope of basic multi-client attach. + +### How does this interact with `session/list` and `session/resume`? + +Multi-client attach works naturally with these related features: +- **`session/list`** — Clients use this to discover existing sessions before attaching +- **`session/resume`** — If an agent only supports `session/resume` (not `session/load`), the proxy can use `session/resume` to reconnect to the session while providing the history from its own storage +- **`session/load`** — Full-featured agents that support `session/load` can provide richer history, but attach doesn't require it + +The typical flow for a dashboard client would be: +1. Call `session/list` to see all active sessions +2. Call `session/attach` to connect to a selected session +3. Stream `session/update` notifications in real-time + +## Revision history + +- **2026-02-18**: Initial proposal From 239c369ee4b66599b10686af821dbb16ad0db8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Martins?= Date: Wed, 18 Feb 2026 14:40:46 +1100 Subject: [PATCH 2/5] docs(rfds): refine multi-client session attach proposal Address review feedback on the initial RFD draft: - Add status: "proposal" to frontmatter - Add historyPolicy parameter (full/pending_only/none) to session/attach, giving lightweight clients control over history replay on connect - Document historyPolicy effect on the history field in the response - Add error response examples for session/attach failure cases (session not found, not authorised, capability absent) - Add session/detach success response example - Remove maxClients from core proposal; defer to Shiny Future as a possible later capability for operators to cap concurrent connections - Fix observer role description to mention future session/promote path - Renumber error codes to remove gap left by removed maxClients error - Fix implementation plan wording ("with a `roles` field") --- docs/rfds/multi-client-session-attach.mdx | 74 +++++++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/docs/rfds/multi-client-session-attach.mdx b/docs/rfds/multi-client-session-attach.mdx index 66d5ddd8..f483bc48 100644 --- a/docs/rfds/multi-client-session-attach.mdx +++ b/docs/rfds/multi-client-session-attach.mdx @@ -1,5 +1,6 @@ --- title: "Multi-Client Session Attach" +status: "proposal" --- - Author(s): [@joaommartins](https://github.com/joaommartins) @@ -40,7 +41,7 @@ This proposal builds on and intersects with several existing draft RFDs: Add a `session/attach` method that allows a client to connect to an **active, in-progress session** owned by another client. The attaching client receives: -1. A replay of conversation history (like `session/load`) +1. A replay of conversation history, controlled by the `historyPolicy` parameter (see below) 2. A live stream of `session/update` notifications going forward 3. The ability to respond to `request_permission` prompts 4. Optionally, the ability to send `session/prompt` messages @@ -54,7 +55,6 @@ Agents (or proxies) advertise multi-client support during initialization: "agentCapabilities": { "sessionCapabilities": { "attach": { - "maxClients": 3, "roles": ["controller", "observer"] } } @@ -65,9 +65,9 @@ Agents (or proxies) advertise multi-client support during initialization: ### Client roles - **Controller** — Can send prompts, respond to permission requests, cancel operations. The original client is always a controller. -- **Observer** — Receives `session/update` notifications and can see pending `request_permission` prompts, but cannot act on them. +- **Observer** — Receives `session/update` notifications and can see pending `request_permission` prompts, but cannot act on them unless promoted via a future `session/promote` extension. -A client specifies its requested role when attaching: +A client specifies its requested role and history policy when attaching: ```json { @@ -77,6 +77,7 @@ A client specifies its requested role when attaching: "params": { "sessionId": "sess_abc123def456", "role": "controller", + "historyPolicy": "full", "clientInfo": { "name": "notification-dashboard", "version": "1.0.0" @@ -85,7 +86,13 @@ A client specifies its requested role when attaching: } ``` -The response follows standard ACP patterns, returning session metadata: +The `historyPolicy` parameter controls what history is replayed on attach: + +- `"full"` (default) — Replay the complete conversation history, matching the behaviour of `session/load`. Best for IDE and desktop clients. +- `"pending_only"` — Replay only events that require action (e.g., pending `request_permission` prompts). Best for notification clients. +- `"none"` — No history replay; receive only future `session/update` events. Best for lightweight observer or logging clients. + +The response follows standard ACP patterns, returning session metadata. When `historyPolicy` is `"none"`, the `history` field is omitted. When `"pending_only"`, only items with `"status": "pending"` are included: ```json { @@ -117,6 +124,47 @@ The response follows standard ACP patterns, returning session metadata: } ``` +#### Error responses + +The proxy returns a JSON-RPC error when attach cannot be completed: + +```json +// Session not found or already terminated +{ + "jsonrpc": "2.0", + "id": 5, + "error": { + "code": -32001, + "message": "Session not found", + "data": { "sessionId": "sess_abc123def456" } + } +} +``` + +```json +// Client not authorised to attach +{ + "jsonrpc": "2.0", + "id": 5, + "error": { + "code": -32002, + "message": "Not authorised to attach to this session" + } +} +``` + +```json +// Session does not support attach (capability absent) +{ + "jsonrpc": "2.0", + "id": 5, + "error": { + "code": -32003, + "message": "Session does not support multi-client attach" + } +} +``` + ### Permission request routing When an agent emits `request_permission`, it is broadcast to **all connected controller clients**. The first client to respond wins (first-writer-wins semantics). The agent then notifies all other clients that the permission was resolved: @@ -181,6 +229,19 @@ This means existing agents work **unchanged** — the proxy handles all multi-cl } ``` +On success, the proxy confirms the detach: + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "result": { + "sessionId": "sess_abc123def456", + "status": "detached" + } +} +``` + Lifecycle rules: - Clients can detach voluntarily via `session/detach` - If the original (owning) client detaches, the session continues as long as at least one controller remains @@ -216,12 +277,13 @@ With multi-client session attach: 4. **Monitoring and audit** — Observer clients can log all agent actions for compliance or debugging without interfering with the workflow. 5. **Graceful client recovery** — If your terminal crashes, you attach from a new terminal and pick up exactly where you were — no `session/load` replay delay because the session never stopped. 6. **Cross-device continuity** — Start a coding session on your desktop, get a permission request notification on your phone, approve it while commuting, then review the results on your laptop when you get home. +7. **Configurable client limits** — Proxies could advertise a `maxClients` cap in their `attach` capability, allowing operators to limit concurrent connections per session for resource or security reasons. ## Implementation details and plan ### Phase 1: Protocol specification 1. Add `session/attach` and `session/detach` methods to schema.json -2. Define `attach` capability in `sessionCapabilities` with `maxClients` and `roles` fields +2. Define `attach` capability in `sessionCapabilities` with a `roles` field 3. Define client roles (controller/observer) and their semantics 4. Specify `permission_resolved` and `client_disconnected` session update variants 5. Document interaction with existing `session/load` and `session/resume` From 7375612a51e87b08a36eb4667ec25eece26dd59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Martins?= Date: Wed, 25 Feb 2026 00:03:48 +0000 Subject: [PATCH 3/5] docs(rfds): address PR feedback on multi-client session attach Add three features requested by reviewers: - `after_message` history policy with `afterMessageId` for delta sync on reconnect, so clients only replay messages since their last known position (references Message ID RFD / PR #536) - `turn_complete` session update broadcast so secondary clients know when the agent finishes a turn - `prompt_received` session update echo so all clients see prompts sent by other controllers Also updates proxy responsibilities, implementation plan, related RFDs list, and revision history. --- docs/rfds/multi-client-session-attach.mdx | 85 ++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/docs/rfds/multi-client-session-attach.mdx b/docs/rfds/multi-client-session-attach.mdx index f483bc48..30bcf635 100644 --- a/docs/rfds/multi-client-session-attach.mdx +++ b/docs/rfds/multi-client-session-attach.mdx @@ -32,6 +32,7 @@ This proposal builds on and intersects with several existing draft RFDs: - **[Agent Extensions via ACP Proxies](./proxy-chains)** — The proxy/multiplexer architecture pattern is central to the recommended implementation approach for multi-client sessions - **[Session List](./session-list)** — Discovering existing sessions (prerequisite for attach) - **[Resuming of existing sessions](./session-resume)** (`session/resume`) — Sequential handoff between clients +- **[Message ID](./message-id)** — Stable message identifiers enabling `after_message` history replay and correlation of `turn_complete`/`prompt_received` notifications - **Session Info Update** — Real-time metadata propagation (draft) - **Session Fork** — Branching sessions (related but distinct — fork creates a copy, attach shares the original) (draft) @@ -91,8 +92,31 @@ The `historyPolicy` parameter controls what history is replayed on attach: - `"full"` (default) — Replay the complete conversation history, matching the behaviour of `session/load`. Best for IDE and desktop clients. - `"pending_only"` — Replay only events that require action (e.g., pending `request_permission` prompts). Best for notification clients. - `"none"` — No history replay; receive only future `session/update` events. Best for lightweight observer or logging clients. +- `"after_message"` — Replay history starting after the message identified by `afterMessageId`. Best for reconnecting clients that already have partial history and only need the delta. Requires the [Message ID RFD](./message-id) to be adopted. -The response follows standard ACP patterns, returning session metadata. When `historyPolicy` is `"none"`, the `history` field is omitted. When `"pending_only"`, only items with `"status": "pending"` are included: +When using `"after_message"`, the client includes an `afterMessageId` field: + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "session/attach", + "params": { + "sessionId": "sess_abc123def456", + "role": "controller", + "historyPolicy": "after_message", + "afterMessageId": "ea87d0e7-beb8-484a-a404-94a30b78a5a8", + "clientInfo": { + "name": "notification-dashboard", + "version": "1.0.0" + } + } +} +``` + +If the proxy does not recognise the provided `afterMessageId` (e.g., the message has been evicted from its buffer), it SHOULD fall back to `"full"` replay and indicate this in the response via the `historyPolicy` field. + +The response follows standard ACP patterns, returning session metadata. When `historyPolicy` is `"none"`, the `history` field is omitted. When `"pending_only"`, only items with `"status": "pending"` are included. When `"after_message"`, only events after the specified message are included: ```json { @@ -188,6 +212,59 @@ When an agent emits `request_permission`, it is broadcast to **all connected con } ``` +### Turn completion notification + +When a controller sends a `session/prompt` and the agent finishes processing that turn, the proxy broadcasts a `turn_complete` notification to all connected clients. This is essential for multiplexed clients — without it, secondary clients have no reliable way to know when the agent has finished responding and is ready for the next prompt. + +```json +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123def456", + "update": { + "type": "turn_complete", + "stopReason": "end_turn", + "messageId": "ea87d0e7-beb8-484a-a404-94a30b78a5a8" + } + } +} +``` + +The `stopReason` field mirrors the `stopReason` from the `session/prompt` response (e.g., `"end_turn"`, `"max_tokens"`). The optional `messageId` field references the final agent message of the turn (requires the [Message ID RFD](./message-id)). + +Note: The primary client (the one that sent the prompt) already receives this signal via the `session/prompt` response. The `turn_complete` notification is for all *other* connected clients that only see `session/update` events. + +### Prompt echoing + +When a controller sends a `session/prompt`, the proxy echoes that prompt to all **other** connected clients via a `prompt_received` session update. Without this, secondary clients would see the agent's response stream without knowing what question or instruction triggered it. + +```json +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123def456", + "update": { + "type": "prompt_received", + "messageId": "4c12d49b-729c-4086-bfed-5b82e9a53400", + "prompt": [ + { + "type": "text", + "text": "Add authentication to the API" + } + ], + "sentBy": { + "name": "Claude Code", + "version": "1.0.0" + } + } + } +} +``` + +The prompt is echoed *before* the agent begins streaming its response, so clients can display the user message in the correct position in the conversation. The `sentBy` field identifies which client sent the prompt, and the optional `messageId` allows clients to correlate the prompt with subsequent response chunks (requires the [Message ID RFD](./message-id)). + ### Transport considerations The current stdio transport is inherently single-client. Multi-client attach requires a **network-capable transport**. We propose this works over: @@ -212,7 +289,10 @@ The proxy: - Fans out `session/update` notifications to all connected clients - Broadcasts `request_permission` to all controllers - Routes the first response back to the agent +- Echoes `session/prompt` from one controller to all other clients as `prompt_received` +- Broadcasts `turn_complete` to all non-prompting clients when a turn finishes - Tracks client roles and connection state +- Buffers message history for `after_message` replay on reconnect This means existing agents work **unchanged** — the proxy handles all multi-client logic. This approach aligns perfectly with the proxy-chains architecture, where proxies sit between clients and agents to extend functionality without modifying the core agent implementation. @@ -285,7 +365,7 @@ With multi-client session attach: 1. Add `session/attach` and `session/detach` methods to schema.json 2. Define `attach` capability in `sessionCapabilities` with a `roles` field 3. Define client roles (controller/observer) and their semantics -4. Specify `permission_resolved` and `client_disconnected` session update variants +4. Specify `permission_resolved`, `client_disconnected`, `turn_complete`, and `prompt_received` session update variants 5. Document interaction with existing `session/load` and `session/resume` ### Phase 2: Reference proxy implementation @@ -411,4 +491,5 @@ The typical flow for a dashboard client would be: ## Revision history +- **2026-02-24**: Added `after_message` history policy with `afterMessageId` for delta sync on reconnect; added `turn_complete` notification for secondary clients; added `prompt_received` echoing so all clients see prompts sent by other controllers - **2026-02-18**: Initial proposal From a9d721b6940cdbbdbc618015b47653493877dabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Martins?= Date: Mon, 2 Mar 2026 02:39:36 +0000 Subject: [PATCH 4/5] docs(rfds): add clientId and pending permission replay to multi-client RFD Address reviewer feedback: add self-assigned clientId field for disambiguating multiple instances of the same client, and clarify how pending permission requests become actionable for attaching controllers via proxy re-issued RPC calls. --- docs/rfds/multi-client-session-attach.mdx | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/rfds/multi-client-session-attach.mdx b/docs/rfds/multi-client-session-attach.mdx index 30bcf635..9f901324 100644 --- a/docs/rfds/multi-client-session-attach.mdx +++ b/docs/rfds/multi-client-session-attach.mdx @@ -79,6 +79,7 @@ A client specifies its requested role and history policy when attaching: "sessionId": "sess_abc123def456", "role": "controller", "historyPolicy": "full", + "clientId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "clientInfo": { "name": "notification-dashboard", "version": "1.0.0" @@ -87,6 +88,8 @@ A client specifies its requested role and history policy when attaching: } ``` +The optional `clientId` is a self-assigned opaque identifier (e.g., a UUID) that uniquely identifies this specific client connection. When provided, the proxy uses it to distinguish between multiple instances of the same client — for example, two mobile ACP frontends both named `"acp-mobile"`. The `clientId` is propagated in `connectedClients`, `resolvedBy`, `sentBy`, and lifecycle notifications so other clients can correlate actions to a specific peer. If omitted, the proxy MAY assign one and return it in the response. + The `historyPolicy` parameter controls what history is replayed on attach: - `"full"` (default) — Replay the complete conversation history, matching the behaviour of `session/load`. Best for IDE and desktop clients. @@ -106,6 +109,7 @@ When using `"after_message"`, the client includes an `afterMessageId` field: "role": "controller", "historyPolicy": "after_message", "afterMessageId": "ea87d0e7-beb8-484a-a404-94a30b78a5a8", + "clientId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "clientInfo": { "name": "notification-dashboard", "version": "1.0.0" @@ -137,8 +141,10 @@ The response follows standard ACP patterns, returning session metadata. When `hi "status": "pending" } ], + "clientId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "connectedClients": [ { + "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Claude Code", "version": "1.0.0", "role": "controller" @@ -204,6 +210,7 @@ When an agent emits `request_permission`, it is broadcast to **all connected con "toolCallId": "call_xyz", "chosenOptionId": "allow_once", "resolvedBy": { + "clientId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "name": "notification-dashboard", "version": "1.0.0" } @@ -212,6 +219,14 @@ When an agent emits `request_permission`, it is broadcast to **all connected con } ``` +### Pending permission replay for attaching controllers + +When a controller attaches with `historyPolicy: "full"` or `"pending_only"`, pending `request_permission` items appear in the history replay. However, history entries are informational — they give the client display context but do not provide a mechanism to *respond*. + +To make pending permissions actionable, the proxy re-issues any unresolved `request_permission` calls as standard JSON-RPC requests to the newly-attached controller **after** the `session/attach` response is sent. From the client's perspective, these are indistinguishable from fresh permission requests — no special handling is required. + +The proxy continues to enforce first-writer-wins semantics: if another client resolves the permission before the new controller responds, the proxy discards the late response and sends the new client a `permission_resolved` notification. This two-step approach (history for display context, re-issued RPC for actionability) keeps client implementation simple while ensuring no pending permission goes unactionable. + ### Turn completion notification When a controller sends a `session/prompt` and the agent finishes processing that turn, the proxy broadcasts a `turn_complete` notification to all connected clients. This is essential for multiplexed clients — without it, secondary clients have no reliable way to know when the agent has finished responding and is ready for the next prompt. @@ -255,6 +270,7 @@ When a controller sends a `session/prompt`, the proxy echoes that prompt to all } ], "sentBy": { + "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Claude Code", "version": "1.0.0" } @@ -288,10 +304,11 @@ The proxy: - Holds the single stdio connection to the agent - Fans out `session/update` notifications to all connected clients - Broadcasts `request_permission` to all controllers -- Routes the first response back to the agent +- Re-issues pending `request_permission` calls to newly-attached controllers +- Routes the first response back to the agent (first-writer-wins) - Echoes `session/prompt` from one controller to all other clients as `prompt_received` - Broadcasts `turn_complete` to all non-prompting clients when a turn finishes -- Tracks client roles and connection state +- Tracks client roles, `clientId`s, and connection state - Buffers message history for `after_message` replay on reconnect This means existing agents work **unchanged** — the proxy handles all multi-client logic. This approach aligns perfectly with the proxy-chains architecture, where proxies sit between clients and agents to extend functionality without modifying the core agent implementation. @@ -337,6 +354,7 @@ Lifecycle rules: "update": { "type": "client_disconnected", "client": { + "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Claude Code", "version": "1.0.0", "role": "controller" @@ -438,6 +456,7 @@ A future `session/promote` request might look like: "params": { "sessionId": "sess_abc123def456", "targetClient": { + "clientId": "f7e8d9c0-b1a2-3456-7890-abcdef123456", "name": "mobile-app", "version": "1.0.0" }, @@ -491,5 +510,6 @@ The typical flow for a dashboard client would be: ## Revision history +- **2026-02-27**: Added optional `clientId` field for disambiguating multiple instances of the same client; clarified pending permission request replay mechanism for attaching controllers - **2026-02-24**: Added `after_message` history policy with `afterMessageId` for delta sync on reconnect; added `turn_complete` notification for secondary clients; added `prompt_received` echoing so all clients see prompts sent by other controllers - **2026-02-18**: Initial proposal From 98e098b7ca7c289066d20f73411ce649081d378b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Martins?= Date: Fri, 27 Mar 2026 10:14:28 +1100 Subject: [PATCH 5/5] docs(rfds): remove role distinction and capability negotiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address anna239's feedback: drop controller/observer roles so the proxy broadcasts every event to all clients without per-event filtering. Clients that don't want to handle certain events simply ignore them. This avoids needing to revisit routing logic for each new event type (e.g. elicitation). Also removes capability negotiation from agentCapabilities since the proxy — not the agent — implements multi-client logic. --- docs/rfds/multi-client-session-attach.mdx | 128 +++++++--------------- 1 file changed, 40 insertions(+), 88 deletions(-) diff --git a/docs/rfds/multi-client-session-attach.mdx b/docs/rfds/multi-client-session-attach.mdx index 9f901324..163ec6d2 100644 --- a/docs/rfds/multi-client-session-attach.mdx +++ b/docs/rfds/multi-client-session-attach.mdx @@ -16,7 +16,7 @@ ACP currently assumes a **1:1 relationship** between a client and an agent sessi 1. **No unified oversight** — Each agent session is bound to the frontend that started it. If you start Claude Code in terminal A, you can only respond to its permission requests from terminal A. 2. **Permission requests go unanswered** — When an agent blocks on `request_permission`, the developer must find the correct terminal/tab/window to respond. With multiple concurrent sessions, this becomes a significant context-switching tax. -3. **No observation without control** — There is no way to passively monitor an active session from a secondary client (e.g., a mobile device, a web dashboard, or a notification center). +3. **No secondary access** — There is no way to monitor or interact with an active session from a secondary client (e.g., a mobile device, a web dashboard, or a notification center). 4. **Session handoff is sequential, not concurrent** — `session/load` allows a new client to resume a previous session, but the original client must have disconnected first. There is no mechanism for a second client to "attach" to a live, in-progress session. ### Current workarounds @@ -45,30 +45,13 @@ Add a `session/attach` method that allows a client to connect to an **active, in 1. A replay of conversation history, controlled by the `historyPolicy` parameter (see below) 2. A live stream of `session/update` notifications going forward 3. The ability to respond to `request_permission` prompts -4. Optionally, the ability to send `session/prompt` messages +4. The ability to send `session/prompt` messages -### Capability negotiation +### No role distinction -Agents (or proxies) advertise multi-client support during initialization: +All connected clients are equal participants — any client can send prompts, respond to permission requests, or passively observe. There is no role distinction enforced by the proxy. Clients that do not wish to handle certain events (e.g., permission requests or elicitation) simply ignore them. This keeps the proxy simple: it broadcasts every event to every client without needing per-event filtering rules, and avoids the need to revisit routing logic whenever a new event type is added to the protocol. -```json -{ - "agentCapabilities": { - "sessionCapabilities": { - "attach": { - "roles": ["controller", "observer"] - } - } - } -} -``` - -### Client roles - -- **Controller** — Can send prompts, respond to permission requests, cancel operations. The original client is always a controller. -- **Observer** — Receives `session/update` notifications and can see pending `request_permission` prompts, but cannot act on them unless promoted via a future `session/promote` extension. - -A client specifies its requested role and history policy when attaching: +A client specifies its history policy when attaching: ```json { @@ -77,7 +60,6 @@ A client specifies its requested role and history policy when attaching: "method": "session/attach", "params": { "sessionId": "sess_abc123def456", - "role": "controller", "historyPolicy": "full", "clientId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "clientInfo": { @@ -94,7 +76,7 @@ The `historyPolicy` parameter controls what history is replayed on attach: - `"full"` (default) — Replay the complete conversation history, matching the behaviour of `session/load`. Best for IDE and desktop clients. - `"pending_only"` — Replay only events that require action (e.g., pending `request_permission` prompts). Best for notification clients. -- `"none"` — No history replay; receive only future `session/update` events. Best for lightweight observer or logging clients. +- `"none"` — No history replay; receive only future `session/update` events. Best for lightweight monitoring or logging clients. - `"after_message"` — Replay history starting after the message identified by `afterMessageId`. Best for reconnecting clients that already have partial history and only need the delta. Requires the [Message ID RFD](./message-id) to be adopted. When using `"after_message"`, the client includes an `afterMessageId` field: @@ -106,7 +88,6 @@ When using `"after_message"`, the client includes an `afterMessageId` field: "method": "session/attach", "params": { "sessionId": "sess_abc123def456", - "role": "controller", "historyPolicy": "after_message", "afterMessageId": "ea87d0e7-beb8-484a-a404-94a30b78a5a8", "clientId": "d290f1ee-6c54-4b01-90e6-d701748f0851", @@ -128,7 +109,6 @@ The response follows standard ACP patterns, returning session metadata. When `hi "id": 5, "result": { "sessionId": "sess_abc123def456", - "role": "controller", "history": [ { "type": "prompt", @@ -146,8 +126,7 @@ The response follows standard ACP patterns, returning session metadata. When `hi { "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Claude Code", - "version": "1.0.0", - "role": "controller" + "version": "1.0.0" } ] } @@ -184,7 +163,7 @@ The proxy returns a JSON-RPC error when attach cannot be completed: ``` ```json -// Session does not support attach (capability absent) +// Proxy does not support multi-client attach { "jsonrpc": "2.0", "id": 5, @@ -197,7 +176,7 @@ The proxy returns a JSON-RPC error when attach cannot be completed: ### Permission request routing -When an agent emits `request_permission`, it is broadcast to **all connected controller clients**. The first client to respond wins (first-writer-wins semantics). The agent then notifies all other clients that the permission was resolved: +When an agent emits `request_permission`, it is broadcast to **all connected clients**. The first client to respond wins (first-writer-wins semantics). The agent then notifies all other clients that the permission was resolved: ```json { @@ -219,17 +198,17 @@ When an agent emits `request_permission`, it is broadcast to **all connected con } ``` -### Pending permission replay for attaching controllers +### Pending permission replay on attach -When a controller attaches with `historyPolicy: "full"` or `"pending_only"`, pending `request_permission` items appear in the history replay. However, history entries are informational — they give the client display context but do not provide a mechanism to *respond*. +When a client attaches with `historyPolicy: "full"` or `"pending_only"`, pending `request_permission` items appear in the history replay. However, history entries are informational — they give the client display context but do not provide a mechanism to *respond*. -To make pending permissions actionable, the proxy re-issues any unresolved `request_permission` calls as standard JSON-RPC requests to the newly-attached controller **after** the `session/attach` response is sent. From the client's perspective, these are indistinguishable from fresh permission requests — no special handling is required. +To make pending permissions actionable, the proxy re-issues any unresolved `request_permission` calls as standard JSON-RPC requests to the newly-attached client **after** the `session/attach` response is sent. From the client's perspective, these are indistinguishable from fresh permission requests — no special handling is required. -The proxy continues to enforce first-writer-wins semantics: if another client resolves the permission before the new controller responds, the proxy discards the late response and sends the new client a `permission_resolved` notification. This two-step approach (history for display context, re-issued RPC for actionability) keeps client implementation simple while ensuring no pending permission goes unactionable. +The proxy continues to enforce first-writer-wins semantics: if another client resolves the permission before the new client responds, the proxy discards the late response and sends the new client a `permission_resolved` notification. This two-step approach (history for display context, re-issued RPC for actionability) keeps client implementation simple while ensuring no pending permission goes unactionable. ### Turn completion notification -When a controller sends a `session/prompt` and the agent finishes processing that turn, the proxy broadcasts a `turn_complete` notification to all connected clients. This is essential for multiplexed clients — without it, secondary clients have no reliable way to know when the agent has finished responding and is ready for the next prompt. +When a client sends a `session/prompt` and the agent finishes processing that turn, the proxy broadcasts a `turn_complete` notification to all connected clients. This is essential for multiplexed clients — without it, secondary clients have no reliable way to know when the agent has finished responding and is ready for the next prompt. ```json { @@ -252,7 +231,7 @@ Note: The primary client (the one that sent the prompt) already receives this si ### Prompt echoing -When a controller sends a `session/prompt`, the proxy echoes that prompt to all **other** connected clients via a `prompt_received` session update. Without this, secondary clients would see the agent's response stream without knowing what question or instruction triggered it. +When a client sends a `session/prompt`, the proxy echoes that prompt to all **other** connected clients via a `prompt_received` session update. Without this, secondary clients would see the agent's response stream without knowing what question or instruction triggered it. ```json { @@ -286,7 +265,7 @@ The prompt is echoed *before* the agent begins streaming its response, so client The current stdio transport is inherently single-client. Multi-client attach requires a **network-capable transport**. We propose this works over: - **WebSocket** — For persistent, bidirectional connections from multiple clients -- **HTTP + SSE** — For observer-only clients that only need to receive updates +- **HTTP + SSE** — For clients that only need to receive updates The agent (or an ACP proxy) would listen on a network port. This is consistent with the direction of the **[Proxy Chains RFD](./proxy-chains)**, where a proxy mediates between clients and agents. @@ -303,12 +282,12 @@ Agent (Claude Code) ←(stdio)→ ACP Proxy ←(WebSocket)→ Client A (Toad/IDE The proxy: - Holds the single stdio connection to the agent - Fans out `session/update` notifications to all connected clients -- Broadcasts `request_permission` to all controllers -- Re-issues pending `request_permission` calls to newly-attached controllers +- Broadcasts `request_permission` to all clients +- Re-issues pending `request_permission` calls to newly-attached clients - Routes the first response back to the agent (first-writer-wins) -- Echoes `session/prompt` from one controller to all other clients as `prompt_received` +- Echoes `session/prompt` from one client to all other clients as `prompt_received` - Broadcasts `turn_complete` to all non-prompting clients when a turn finishes -- Tracks client roles, `clientId`s, and connection state +- Tracks `clientId`s and connection state - Buffers message history for `after_message` replay on reconnect This means existing agents work **unchanged** — the proxy handles all multi-client logic. This approach aligns perfectly with the proxy-chains architecture, where proxies sit between clients and agents to extend functionality without modifying the core agent implementation. @@ -341,7 +320,7 @@ On success, the proxy confirms the detach: Lifecycle rules: - Clients can detach voluntarily via `session/detach` -- If the original (owning) client detaches, the session continues as long as at least one controller remains +- If the original (owning) client detaches, the session continues as long as at least one client remains - If all clients detach, the agent MAY keep the session alive for a configurable timeout (allowing reconnection) - Agents notify remaining clients when a peer disconnects via a `client_disconnected` session update: @@ -356,8 +335,7 @@ Lifecycle rules: "client": { "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Claude Code", - "version": "1.0.0", - "role": "controller" + "version": "1.0.0" }, "timestamp": "2026-02-18T15:30:00Z" } @@ -372,44 +350,42 @@ With multi-client session attach: 1. **Unified notification dashboard** — A single web or mobile app shows all pending permission requests across all your running agent sessions. You tap "approve" on your phone while the agent continues in your terminal. 2. **Pair programming with agents** — Two developers attach to the same agent session. One focuses on reviewing the agent's output while the other provides guidance. 3. **IDE + terminal coexistence** — Start a session in Toad or your terminal, but have your IDE also attached for inline diff display and file navigation. -4. **Monitoring and audit** — Observer clients can log all agent actions for compliance or debugging without interfering with the workflow. +4. **Monitoring and audit** — Clients can passively log all agent actions for compliance or debugging without interfering with the workflow. 5. **Graceful client recovery** — If your terminal crashes, you attach from a new terminal and pick up exactly where you were — no `session/load` replay delay because the session never stopped. 6. **Cross-device continuity** — Start a coding session on your desktop, get a permission request notification on your phone, approve it while commuting, then review the results on your laptop when you get home. -7. **Configurable client limits** — Proxies could advertise a `maxClients` cap in their `attach` capability, allowing operators to limit concurrent connections per session for resource or security reasons. +7. **Configurable client limits** — Proxies could limit concurrent connections per session for resource or security reasons. ## Implementation details and plan ### Phase 1: Protocol specification 1. Add `session/attach` and `session/detach` methods to schema.json -2. Define `attach` capability in `sessionCapabilities` with a `roles` field -3. Define client roles (controller/observer) and their semantics -4. Specify `permission_resolved`, `client_disconnected`, `turn_complete`, and `prompt_received` session update variants -5. Document interaction with existing `session/load` and `session/resume` +2. Specify `permission_resolved`, `client_disconnected`, `turn_complete`, and `prompt_received` session update variants +3. Document interaction with existing `session/load` and `session/resume` ### Phase 2: Reference proxy implementation -6. Build a reference multi-client proxy (potentially extending `sacp-conductor` from the proxy-chains RFD) that: +4. Build a reference multi-client proxy (potentially extending `sacp-conductor` from the proxy-chains RFD) that: - Spawns an agent subprocess over stdio - Accepts multiple client connections over WebSocket - Implements attach/detach and permission routing logic - Handles MCP-over-ACP for any MCP servers the agent provides -7. Test with Claude Code, Gemini CLI, and Codex as backend agents +5. Test with Claude Code, Gemini CLI, and Codex as backend agents ### Phase 3: Client integration -8. Add attach support to at least one ACP client (Toad, Zed, or a purpose-built dashboard) -9. Build a minimal "notification center" client that demonstrates the core use case +6. Add attach support to at least one ACP client (Toad, Zed, or a purpose-built dashboard) +7. Build a minimal "notification center" client that demonstrates the core use case ### Compatibility considerations -- **Fully backward compatible** — Agents that don't advertise `attach` capability work exactly as today +- **Fully backward compatible** — Agents that don't support multi-client attach work exactly as today - **Proxy-based approach** means no changes required to existing agents -- **Optional for clients** — Clients that don't need multi-client support ignore the capability +- **Optional for clients** — Clients that don't need multi-client support simply don't call `session/attach` - **Works with session/list** — Clients can discover sessions via `session/list` (if supported) before attempting to attach ### Security considerations - **Authentication** — Attaching clients MUST authenticate with the same identity as the session owner (or be explicitly authorized). The proxy is responsible for enforcing this policy. -- **Role enforcement** — Observer clients MUST NOT be able to send prompts or respond to permissions. The proxy enforces role restrictions. +- **Equal participation** — All connected clients can send prompts and respond to permissions. Clients that do not wish to handle certain events simply ignore them. - **Audit trail** — Permission resolutions SHOULD include which client responded (for accountability), as shown in the `resolvedBy` field. - **Transport security** — WebSocket connections MUST use TLS in non-localhost scenarios. -- **Authorization model** — Future extensions could support fine-grained permissions (e.g., allow specific users to attach as observers but not controllers). +- **Authorization model** — Future extensions could support fine-grained permissions (e.g., restricting which users can attach to a given session). ## Frequently asked questions @@ -438,35 +414,10 @@ Multi-client session attach specifies one concrete capability a proxy should sup The reference implementation would likely extend the `sacp-conductor` from the proxy-chains work to add multi-client multiplexing capabilities. -### What happens if two controllers respond to the same permission request? +### What happens if two clients respond to the same permission request? First-writer-wins. The agent (or proxy) accepts the first valid response and ignores subsequent ones. All clients receive a `permission_resolved` notification indicating the outcome and which client provided the response. This is a simple, well-understood concurrency model that avoids complex conflict resolution. -### Could observer clients be promoted to controllers? - -Yes, this could be supported via a `session/promote` method (future extension). For the initial proposal, roles are fixed at attach time to keep the design simple. - -A future `session/promote` request might look like: - -```json -{ - "jsonrpc": "2.0", - "id": 7, - "method": "session/promote", - "params": { - "sessionId": "sess_abc123def456", - "targetClient": { - "clientId": "f7e8d9c0-b1a2-3456-7890-abcdef123456", - "name": "mobile-app", - "version": "1.0.0" - }, - "newRole": "controller" - } -} -``` - -This would require authorization from an existing controller. - ### How does this work with MCP servers? The proxy (which implements multi-client logic) can provide MCP servers via [MCP-over-ACP transport](./mcp-over-acp). When the proxy advertises MCP capabilities, all attached clients can see and use the tools provided by those MCP servers. @@ -484,9 +435,9 @@ The proxy can implement rate limiting policies to prevent a misbehaving client f Yes! Mobile clients would connect via WebSocket (likely over TLS for security). A mobile app could: 1. Discover sessions via `session/list` (if the proxy supports it) -2. Attach as an observer to monitor progress +2. Attach to monitor progress 3. Receive push notifications for `request_permission` events -4. Upgrade to controller role (via future `session/promote`) to respond +4. Respond to permission requests or send prompts as needed This enables the "approve on phone" use case described in the shiny future section. @@ -510,6 +461,7 @@ The typical flow for a dashboard client would be: ## Revision history -- **2026-02-27**: Added optional `clientId` field for disambiguating multiple instances of the same client; clarified pending permission request replay mechanism for attaching controllers -- **2026-02-24**: Added `after_message` history policy with `afterMessageId` for delta sync on reconnect; added `turn_complete` notification for secondary clients; added `prompt_received` echoing so all clients see prompts sent by other controllers +- **2026-03-27**: Removed controller/observer role distinction and capability negotiation — all clients are equal participants, simplifying proxy routing and future extensibility +- **2026-02-27**: Added optional `clientId` field for disambiguating multiple instances of the same client; clarified pending permission request replay mechanism for attaching clients +- **2026-02-24**: Added `after_message` history policy with `afterMessageId` for delta sync on reconnect; added `turn_complete` notification for secondary clients; added `prompt_received` echoing so all clients see prompts sent by other clients - **2026-02-18**: Initial proposal