Skip to content
Merged
Show file tree
Hide file tree
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
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,16 @@ Health and debug endpoints:
- **JSON-RPC envelope routing.** The mux parses only the envelope (`id`, `method`, `params`, `result`, `error`) unless a mux-owned policy needs a narrow payload check. Payloads are otherwise forwarded byte-for-byte. Policy primarily keys off the `method` string.
- **Per-session id translation.** Each subscriber's request `id` is rewritten to a per-session `mux_id` before forwarding; the response is rewritten back and sent only to the originator.
- **`initialize` / `session/new` caching.** First response is cached; later joiners are answered locally without re-sending to the agent.
- **Collaborative agent-initiated requests.** `session/request_permission` is fanned out live to every attached subscriber; any peer can reply. The mux also emits inert `amux/agent_request_opened` lifecycle metadata for replay/audit context. The first reply for a given id is forwarded to the agent and later replies for the same id are dropped, so the agent always sees exactly one response.
- **Collaborative agent-initiated requests.** `session/request_permission` is fanned out live to every attached subscriber; any peer can reply. The mux also emits inert `amux/agent_request_opened` lifecycle metadata for replay/audit context, and re-issues unresolved permission requests to clients that later call `session/attach` with actionable history. The first reply for a given id is forwarded to the agent and later replies for the same id are dropped, so the agent always sees exactly one response. amux-aware clients receive `amux/agent_request_resolved`; generic attach-only clients that lose the race do not receive a standard resolution notification yet.
- **RFD #533 attach/detach foundation.** In addition to the durable `amux/*` namespace, the mux handles proxy-local `session/attach` / `session/detach`. It intentionally does **not** fabricate RFD #533 lifecycle `session/update` siblings yet, and it does **not** inject `agentCapabilities.sessionCapabilities.attach` into upstream `initialize` responses; callers discover/use this proxy feature out of band.
- **Client-tool policy.** By default, agent-initiated `fs/*` and `terminal/*` client-tool requests are blocked in the mux, answered to the agent with JSON-RPC `-32000`, and not broadcast/replayed. `initialize.params.clientCapabilities.fs` and `.terminal` are stripped before the first initialize reaches the agent. `--unsafe-debug-client-tool-broadcast` explicitly restores the old raw fanout for diagnostics only.
- **Turn serialization.** Concurrent ordinary `session/prompt` while a turn is in flight is rejected with JSON-RPC `-32001`; active-turn controls go through explicit `amux/*` requests. `amux/steer_active_turn` is mux-owned steer/send state: when a turn is active it broadcasts intent, sends ACP `session/cancel`, waits for settlement, then starts a replacement `session/prompt` with prompt-injected context and `supersedesTurnId`; when idle it submits the steer text immediately as the next prompt with `mode: "prompt"`. A second hard steer while one is pending is rejected with `-32002`. `amux/queue_prompt` is mux-owned queue/send state capped at six pending items (`-32003` when full): it broadcasts/replays queue lifecycle, starts immediately when no turn is active, or starts the queued prompt as the next turn after active-turn settlement. `amux/unqueue_prompt` removes a still-pending queue item. The last attached subscriber to issue a substantive request is surfaced as the "driving subscriber" in `/debug/sessions` and `amux/turn_started` for UI attribution.
- **Opt-in request trace metadata.** With `--meta-propagate`, outbound subscriber → agent requests get mux-owned `params._meta.amux` fields (`peerId`, `peerName`, `role`, `muxId`, and `amuxTurnId` for prompts) for cross-client debugging. Default mode leaves request payload metadata unchanged.
- **Cold-start session discovery.** `GET /acp/sessions` runs a transient agent-side `session/list` query before any WebSocket attach, useful for dashboards that need to browse persisted sessions before choosing one to resume.
- **Live `session/list` decoration.** Returned `sessions[]` entries that match a live muxed upstream session get `sessions[i]._meta.amux` fields (`proxySessionId`, `subscriberCount`, optional `drivingSubscriber`), preserving existing `_meta` keys and leaving non-live entries unchanged.
- **`amux/*` extension namespace.** The mux publishes its own metadata/control plane out-of-band: `amux/session_context`, `amux/peer_joined`, `amux/peer_left`, `amux/turn_started`, `amux/turn_complete`, `amux/turn_cancelled`, `amux/session_busy`, `amux/control_submitted`, `amux/queue_item_added`, `amux/queue_item_submitted`, `amux/queue_item_completed`, `amux/queue_item_removed`, `amux/queue_item_orphaned`, `amux/agent_request_opened`, `amux/agent_request_resolved`, plus subscriber-request controls such as `amux/steer_active_turn`, `amux/queue_prompt`, `amux/unqueue_prompt`, and `amux/cancel_active_turn`. ACP frames stay clean; clients see two distinguishable channels and demultiplex by method prefix.
- **Cancellation.** `$/cancel_request` (request-cancellation RFD / unstable schema, not stable ACP v1) works both directions: subscribers can cancel their own in-flight requests; agents can cancel agent-initiated requests (broadcast to peers + `amux/agent_request_resolved { resolvedBy: "agent:cancelled" }`). The amux extension `amux/cancel_active_turn` lets *any* attached peer cancel the in-flight turn (not just the driver) — internally it sends ACP-native `session/cancel { sessionId }` toward the agent and emits `amux/turn_cancelled` to peers.
- **Replay log.** Every broadcast-tier frame (`amux/*` + agent notifications) is appended; a late joiner receives the full history before any live event. Raw collaborative agent-initiated requests are live-only and are not replayed; late joiners see the inert `amux/agent_request_opened` + `amux/agent_request_resolved` lifecycle pair instead. Blocked client-tool requests never enter this lifecycle.
- **Replay log.** Every broadcast-tier frame (`amux/*` + agent notifications) is appended; a late joiner receives the full history before any live event. Raw collaborative agent-initiated requests are live-only in WebSocket replay, but unresolved `session/request_permission` requests are stored separately and re-issued after `session/attach` so late joiners can answer them. `historyPolicy: "after_message"` is accepted but currently falls back to `"full"` until upstream ACP message IDs are available consistently end-to-end. Blocked client-tool requests never enter this lifecycle.
- **TTL grace.** Last subscriber leaving starts a countdown; a reconnect within `--session-ttl-seconds` reuses the same subprocess with all of its caches intact.

## Client contract
Expand Down Expand Up @@ -95,6 +96,8 @@ This table was audited against the stable ACP v1 schema release [`v0.13.3`](http
| `session/load` | ✅ | Stable v1 | Forwarded to the agent. On success, amux rebinds the room's canonical session id and replay-generation boundary to the loaded session; failed loads leave the cache untouched. |
| `session/resume` | ⚠️ envelope passthrough | Stable v1 | Forwarded, but not yet given `session/load`-style canonical-session rebinding for late joiners. |
| `session/close` | ⚠️ envelope passthrough | Stable v1 | Forwarded, but amux does not yet tear down the mux room or clear local caches after a successful close. |
| `session/attach` | ✅ proxy-local | RFD #533-inspired | Answered by the mux, never forwarded to the agent. Returns `sessionId`, `clientId`, effective `historyPolicy`, optional `history` for `full` / `pending_only`, and amux-specific roster metadata under `result._meta.amux.connectedClients`; `none` omits history; `after_message` falls back to `full` when `afterMessageId` cannot be resolved. |
| `session/detach` | ✅ proxy-local | RFD #533-inspired | Answered by the mux, then the WebSocket is closed normally; remaining peers receive `amux/peer_left`. The mux does not fabricate `session/update` disconnect notifications. |
| `session/list` | ✅ | Stable v1 | Over WS, forwarded with id translation and optional `params._meta.amux` trace fields. Returned `sessions[]` entries matching live mux state are decorated under `sessions[i]._meta.amux`; non-live entries and agent-owned metadata are preserved. `GET /acp/sessions?cwd=...` performs a transient agent-side `session/list` before any WS attach. |
| `session/prompt` | ✅ | Stable v1 | Forwarded with id translation; turn serialization; ordinary concurrent prompts rejected with `-32001`. Plain ACP prompts stay serialized/generic; active-turn steering/queueing uses explicit `amux/*` controls. |
| `session/cancel` | ✅ | Stable v1 | Forwarded unchanged from vanilla clients; also emitted southbound by `amux/cancel_active_turn` for active-turn interruption. |
Expand All @@ -106,8 +109,8 @@ This table was audited against the stable ACP v1 schema release [`v0.13.3`](http

| Method | amux | Spec status | Notes |
|---|---|---|---|
| `session/update` | ✅ | Stable v1 | Broadcast to every attached subscriber; appended to replay log. Includes stable update variants such as `session_info_update`, `available_commands_update`, `current_mode_update`, and `config_option_update` as opaque payloads. |
| `session/request_permission` | ✅ | Stable v1 | Broadcast live with first-writer-wins reply; `amux/agent_request_opened` records inert replay context; `amux/agent_request_resolved` fires when consumed; turn-end sweep cleans up abandoned requests. |
| `session/update` | ✅ | Stable v1 | Agent-emitted updates are broadcast to every attached subscriber and appended to replay log. The mux does not fabricate proxy-owned lifecycle updates; clients use `amux/*` for mux lifecycle. |
| `session/request_permission` | ✅ | Stable v1 | Broadcast live with first-writer-wins reply; `amux/agent_request_opened` records inert replay context; `amux/agent_request_resolved` fires when consumed; unresolved permissions are re-issued after `session/attach`; turn-end sweep cleans up abandoned requests. A re-issued permission reply may be dropped if another peer already answered first. |
| `fs/read_text_file`, `fs/write_text_file` | ✅ safe default / 🚧 not provided | Stable v1 client-tool methods | amux does not advertise filesystem client capabilities by default. If an agent sends `fs/*` anyway, amux returns structured `-32000 { reason: "client_tool_blocked" }` to the agent and does not broadcast/replay. Full delegated-client modes are tracked in [#37](https://github.com/lsaether/acp-mux/issues/37). |
| `terminal/create`, `terminal/output`, `terminal/wait_for_exit`, `terminal/kill`, `terminal/release` | ✅ safe default / 🚧 not provided | Stable v1 client-tool methods | Same policy as `fs/*`: `terminal` is stripped from advertised client capabilities and runtime requests are blocked unless `--unsafe-debug-client-tool-broadcast` is explicitly enabled. |
| `$/cancel_request` | ✅ | Unstable/RFD | Optional request-cancellation RFD; not in stable `schema.json`. Marks `agent_pending` Consumed; broadcasts to all peers; emits `amux/agent_request_resolved { resolvedBy: "agent:cancelled" }`. |
Expand All @@ -118,7 +121,7 @@ This table was audited against the stable ACP v1 schema release [`v0.13.3`](http
|---|---|---|---|
| `params._meta.amux` trace propagation | ✅ opt-in | `_meta` stable, propagation convention from RFD | `--meta-propagate` writes mux-owned metadata under the reserved `_meta` extension field without replacing existing agent/client metadata. |
| `session/delete`, `session/fork`, provider methods, NES methods, MCP-over-ACP, elicitation | ➡️ generic passthrough only | Unstable schema / RFDs | Not intentionally implemented by amux. If experimental peers send them, amux envelope-routes them unless they later need mux-specific state handling. |
| `session/attach`, `session/detach` | ⏳ shelved branch only | Historical open RFD / PR #533 | Implemented on branch [`rfd-533-alignment`](https://github.com/lsaether/acp-mux/pull/3), not shipped on main. |
| `session/attach`, `session/detach` | ✅ | RFD #533-inspired | Implemented as proxy-local methods while preserving `amux/*` as the authoritative mux lifecycle namespace. No proxy-owned `session/update` siblings yet, no upstream capability injection, and `after_message` is provisional/falls back to `full` without stable message-id coverage. |

### amux extensions (not part of ACP)

Expand Down
Loading
Loading