Repository navigation
Fleet Studio slice 3 — session multiplexer + SSE relay (interact as if local) #1440
Description
Important
Problem — Even with a fleet-wide list, opening a session on another machine today swaps the whole window (re-attach + full reload, fleet-manager.test.ts:224) and only one machine's traffic is live at a time (all engine//amicode/* traffic routes to one upstream chosen by a single global attachment pointer, fleet-manager-tab.tsx:111 / server.ts:279-408). You cannot stream a solve on machine B while prompting a session on machine A.
Approach — A session-aware multiplexing proxy + SSE relay in the local service routes each session-scoped request — messages, prompt, tool-permission responses, the engine file API, and the SSE subscription — to that session's owner, and merges peers' event streams into the one stream the app reads. This inverts the single-pointer/full-reload model: it creates a per-session owner binding (none exists today) and retires the single attachment pointer. The app keeps a single origin.
Scope — in: the new per-session owner binding + owner-routing key (net-new plumbing — sessions carry no owner today); the multiplexing proxy for session-scoped requests; the SSE merge with new per-event session_id attribution + a per-peer reconnect cursor set; demolition of the single-pointer/full-reload attach path. out: the focused-machine sidebar (slice 4); provenance UI + Preview remote edit (slice 5).
Assumptions — Peer RPC is authorized by the slice-1 credential; the fan-out (slice 2) supplies the session→machine map; the engine file API is the same write plane the agent already uses.
Acceptance Criteria
- Owner-routing key (new plumbing): a per-session owner binding is created and threaded to the request layer (today only a single global attachment pointer exists). A path-less engine
file.write/file.readfor a session carries its ownermachine_id(from the Work Column's bound session); the resolver routes on that key, and a request with no key resolves local (fail-safe), never to an arbitrary attached target. Test: two remote sessions (owners A, B) each write; each write lands on its own owner; a keyless request resolves local. - Per-verb routing, no reload — opening a session owned by a different machine routes each of {stream messages, submit prompt, run tools, answer permission prompts} to the owner, asserted per verb; and no full-window reload / no attach-swap command is dispatched (the current attach path does a full reload — this AC guards its removal).
- Concurrent + un-crossed (deterministic, not latency): hold machine B's SSE stub stream open/blocked; drive a prompt to a session on machine A concurrently; assert A's first event resolves while B is still pending (then release B) — i.e. no head-of-line blocking, asserted by ordering not by a timing threshold. Assert every merged SSE event carries the originating
session_idmatching its source (no A/B cross-attribution). - The app holds a single origin throughout (structural guard: no second base-URL, no origin-switch call).
-
/amicode/fleet/*and posture requests are served locally and never routed to a peer (extend the existing honesty-surface guard,fleet_client_relay.test.ts:291).
Testing Decisions
Genuinely new surface — the session→machine multiplexer routing and the merged SSE relay. The merged/global event stream emits id: undefined and carries no session_id today (out of scope in #1264) — emitting per-event session_id + a per-peer cursor set is new engine surface; build a new merge harness, do NOT lean on the per-session /api/session/{id}/event?after= seam. Concurrency test uses the held-open-stub pattern from fleet_sse_resume.test.ts (block B, drive A, assert A resolves first, release B). The owner-routing test asserts two remote writes land on distinct owners and a keyless request resolves local. Split the compound "opening a remote session…" AC into per-verb routing assertions + a "no reload / no attach-swap dispatched" guard (host: amicode-session-relay.integration.test.ts). Extend fleet_client_relay.test.ts:291 for the honesty-surface-never-proxied half. Reuse the slice-2 machine-keyed map as the resolver's input.
Key Decisions
- Multi-target, both live via a session-aware multiplexer + SSE relay; per-session routing retires the single attachment pointer — this is in-scope demolition, not an increment on the pointer model (ADR 0031 §D1).
- Owner-routing key for path-less engine
filerequests: the ownermachine_idfrom the Work Column's bound session; keyless ⇒ local (ADR 0031 §D1a). - Single app origin is purchased by the multiplexer's per-session routing (ADR 0031 §D1a; ADR 0027 single-origin).
Data Contracts
- Session→machine map + owner-routing key: session-pathed requests resolve the owner from the path id (miss → on-demand fan-out refresh); path-less engine
filerequests carry the ownermachine_idas a mandatory routing header; absent it ⇒ resolve local. The per-session owner binding is created by this slice (noSession.Infoowner field; slice 2's tag is a list overlay, not a request-layer binding). - SSE relay cursor: N merged upstreams need a per-peer cursor set (or a service-side re-sequenced monotonic id space) — a single scalar
lastEventIDcannot address N independent peer positions on reconnect.
Constraints & Invariants
- No silent local fallback: an unreachable owner surfaces the honest degraded outcome, never a fabricated success or a local read.
- The local honesty surface never proxies (ADR 0027 §4).
- Single origin throughout — no native multi-origin switch.
Prior Art
The merged-projection map (slice 2); the single global attachment pointer + full-reload attach path being retired (fleet-manager-tab.tsx:111, server.ts:279-408, fleet-manager.test.ts:224); the fleet transport resolver; the engine file API and the per-session SSE resume path (fleet_sse_resume.test.ts); the merged/global stream that emits no session_id today (#1264); the Work Column bound-session surface (app CONTEXT.md — a named surface, but no session→owner binding in code yet); fleet_client_relay.test.ts:291 (honesty-surface-never-proxied).
Source
Part of #1436. ADR 0031 §D1, §D1a. Blocked by #1439 (needs the fan-out session→machine map). Heaviest UI slice — it inverts the attach model and adds new engine SSE surface.
Activity
feat(fleet): session multiplexer + SSE relay — per-session owner rout…
feat(fleet): honesty surface guard — /amicode/fleet/* never proxied t…
Fleet Studio (#1436): peer-trust credential, fleet-wide sessions, mul…
Fleet Studio (#1436): peer-trust credential, fleet-wide sessions, mul…
jeonghun-jj-lee commentedon Sep 23, 2026
Closed as a partial Fleet Studio foundation: #1455 landed reusable pieces, but the remaining acceptance is superseded by #1482 and #1488 under #1475. This is not a claim that the original end-to-end feature is complete.