Project live collaboration from host-authoritative state¶
Status: accepted
Current decision¶
The original Emacs process owns live session state and execution. A browser is an independent presentation adapter over an allowlisted semantic projection of the canonical transcript, agents, tasks, artifacts, and interactions. It receives neither an Emacs server capability nor direct session lease, filesystem, target, or tool-execution authority. Visible content is shared verbatim and can contain secrets; projection is not redaction. Browser scrolling and disclosure state do not affect the host view.
Emacs dials the repository's self-hosted relay for local and remote sharing. The relay serves the viewer and routes AES-256-GCM sealed frames without the room key. Link fragments carry bearer credentials; each shared session has its own room, key, guests, and links. The optional host token restricts relay room creation independently of guest bearer authority. ADR 0114 owns room lifetime, reconnect, and explicit share revocation.
Link tiers grant progressively more typed actions:
- View: inspect projected state and fetch already-published artifacts.
- Full: additionally queue prompts, interrupt the running request, answer eligible interactions, invoke host-admitted commands or skills, and edit shared whiteboards and documents through the host.
- Owner: additionally change permission mode, rename the session through its native naming path, create a same-workspace isolated session directly, or accept an offered Worktree plan with an explicit host-validated branch name. A full-link creation request instead requires approval from Emacs or an owner-link guest. An artifact's own conversation is not such a session: a full link starts it on first use (ADR 0124).
The host validates credentials, action, audience, byte limits, and current admission at receipt and, for queued invocations, delivery. Free text never runs a slash or skill parser. Browsers can combine up to six explicitly selected skills in one prompt; the typed names are checked independently of literal message arguments, and cannot accompany a slash command. Directive-scoped prompts select discussion only. Interaction answers share the per-string input bound used by prompts. An interaction callback's refusal uses the ordinary guest-frame error boundary: the answering owner sees its diagnostic and other writers see a redacted notice. The interaction adapter must not consume that error into host-only logging, and owner lookup must work on first use before any owner settings action has loaded its module. Numeric option IDs are nonnegative indices into the offered list. Attachments have allowlisted types and host-generated filenames. Browser input uses the ordinary pending-input path; guest attribution is visible to users but excluded from model context. Browser permissions are one-shot, not a way to install reusable rules. Their cards preserve native warnings, including the concurrency risk of a side-conversation mutation while its parent is active. Patch approvals project the current selected and host-edited diff, not the original authored patch. A bounded plain-text notice carries staged comments, conflicts, rollback warnings and parent activity before the colored diff. Semantic review changes update the same request identity; unchanged native redraws do not republish it. The browser retains an in-progress feedback draft and focus across those updates, while editing and selection remain in Emacs. Plan cards disclose their full implementation instructions and settings as Markdown without inactive native key hints. The host enforces owner-only Worktree acceptance; full links retain Here acceptance and plan feedback. Branch input is typed data checked by the native validator, not a remote minibuffer. If full plan disclosure exceeds the card's wire budget, acceptance remains in Emacs and the browser offers feedback only with an explicit full-review notice. Truncation cannot become approval of instructions the browser did not disclose. Save, Rewind, arbitrary Fork, lease transfer, publication, and execution-target changes remain host operations. Accepting an offered Worktree plan is the narrow owner-authorized exception.
The workspace lobby lets writable guests open listed sessions through the host's ordinary restore path. It may reclaim a provably stale same-host PID lock without confirmation, but only while the observed holder remains unchanged. Live, foreign or uncertain holders and portable lease takeovers retain their existing decision requirements; guest frames never wait for an Emacs prompt. Owners can rename listed sessions through that same restore and naming path. A rename preserves the session identity and bearer room, and refreshes all connected lobby listings as well as the room's heading.
Tool display comes from the host's bounded semantic presentation tree and selected direct-tool identity. Display metadata is allowlisted rather than exposing raw internal render data. Delivered Skill dependencies are historical captured bodies, not current file reads. Each browser owns nested disclosures. Agent messages and final reports are projected from structural mailbox envelopes as inspectable notices in the receiving transcript. Outcome labels come from the saved envelope, not optimistic body prose or mutable roster state; folded final answers do not require opening the child's transcript. Activity grouping metadata reuses native eligibility, semantic summaries and failure names. Original records and IDs remain independently addressable; the browser moves their existing elements into local disclosures instead of creating a second transcript or replacing records with group envelopes. Nested invocation events update host facts immediately but coalesce tree construction and publication through the room's existing stream timer. Every projection read refreshes dirty pending trees; top-level events publish immediately, including settlement. Canonical transcript records replace these transient trees.
The browser's unified background-work panel projects the native agent/execution
collector and stable ordering instead of independently interpreting transcript
notices or sorting mutable statuses. Bounded passive terminal snapshots preserve
completion facts when executions leave the live registry; each browser owns its
selection, focus and finished-row grace. The host sends relative terminal age
from the original cache timestamp, so reconnects do not revive expired visible
grace or require synchronized clocks. Duplicate evidence does not restart that
age, and elapsed age alone does not trigger publication. Preview availability
and truncation are explicit, with exactly three bounded output lines and a 64 KiB encoded-frame
budget. Full and owner links may stop a live scoped execution or interrupt a
known retained child through typed identity-bearing actions. The host rechecks
the current guest tier and registry identity before the native user-authority
operation; view links remain inspection-only. No browser-supplied host path or
row key grants execution authority. Read-only source navigation uses the same
native audit lookup as the Emacs action: only a click scans source-backed
history, and the host returns the receiving transcript's owner and exact segment
instead of asking the browser to guess or sequentially load archives. Child
archive fetches are confined to that retained child's canonical history.
Read-only full-output requests also admit running work through the room's live
execution registry, exact owner/ID, known receiving transcript and native source
lookup. Completion breadcrumbs exist only after settlement, so requiring them
would make the running panel's Full output action unavailable. A source:live
response is a bounded artifact snapshot at request time, not a second output
consumer or subscription. Terminal and forwarded evidence keep their existing
source-backed authorization and fallback; browser paths never authorize reads.
The browser replaces separate agent chips and finished-agent rows with this single panel. A bounded scroll area and three fixed preview lines keep status growth away from the composer. Expansion retains completed rows until collapse; collapse starts a fresh ten-second grace. Mouse hover and keyboard focus hold the displayed row set, but touch does not acquire a synthetic hover hold. In compact reading mode, work focus preserves dock geometry: expanding the composer on work focus moved a row between pointer-up and click on phones. Other dock controls retain their own focus behavior. Disclosure and hold state belong to the inspecting browser, not shared native view state. Original tool IDs also reveal the source inside folded activity groups without replacing records or disturbing a draft. Reveal aligns within the actual readable viewport: centering in the entire window hid root and archived sources behind a large writable dock. Child-sheet reveal scrolls only the sheet, not its parent's conversation.
Artifact cards are settled live or archived transcript records; bytes are fetched on demand by record ID, resolved and bounded by the host. HTML runs in a sandboxed iframe with scripts permitted but no same-origin authority and a restrictive CSP. Artifacts live in the workspace artifact store, not in relay file storage. See Artifact store.
Compaction reduces model context without withdrawing browser history. Earlier
segments load as read-only disclosures through the existing session reader.
Canonical chat descriptors group those segments across /clear boundaries;
compaction is continuation within a chat, not a new chat. The host publishes
chat numbers, segment ranges and titles in bounded history-index chunks, without
reading transcript files just to recover chat identity.
The room caches only their published artifact metadata, keeping sidebar access
independent of the current context segment; transcript bodies load on demand.
Shared whiteboards and documents allow direct browser editing through typed, host-committed operations. ADR 0120 owns this extension beyond model-authored artifacts.
Opt-in notifications use browser-native Push with room-scoped service workers. The host forwards endpoint routing metadata to the relay, which sends an empty VAPID-authenticated push. The worker shows generic text; opening the viewer retrieves sealed detail. Focused viewers suppress push, and browsers without an active subscription use live-page notifications. Bearers can persist in browser storage for tab reload, room navigation, and notification-enabled PWA recovery; that persistence is disclosed and does not extend a stopped share's authority.
Browser collaboration owns commands, configuration, protocol surfaces, browser storage, and recovery behavior.
Rationale and consequences¶
A structured projection grants only the actions it describes. Terminal or frame mirroring would expose Emacs input; durable publications would make live sharing depend on cold-resume storage. The host therefore projects live authoritative state, while a content-blind transport supplies reachability.
Dialing out removes Emacs' public-listener responsibilities and its HTTP-server dependency. Typed input makes phone control useful without handing out arbitrary Emacs execution. Per-session credentials keep one shared room from granting all other sessions; switching rooms is browser navigation rather than changing the session behind a bearer.
Content-blind relay transport still exposes connection/routing metadata, and browser push routing exposes endpoints. Bearer links authorize their holders, not named people; projected secrets and locally retained credentials remain within that deliberate sharing boundary. Untrusted artifact scripts must not inherit the viewer origin that holds decrypted content and credentials.
Decision history¶
- Unified work inspection, 2026-10-19: the browser's independent agent chips and finished-agent list omitted running Bash and Monitor work, their previews and stop controls even after native execution changes. A shared native collector now supplies all work and ordering; one browser-local inspector preserves expanded completions and grants only explicitly tier-checked stop actions. This replaces the old agent-only presentation rather than keeping competing status surfaces.
- Lobby crash recovery, 2026-10-14: lobby opens previously refused even a provably stale same-host PID lock because lock acquisition asked Emacs to confirm. A crashed host therefore stranded browser users until someone returned to the keyboard. A lobby-scoped opt-in now skips only stale-lock confirmation, retaining checked replacement and all other prompt refusals. PID inspection also keeps invalid or unobservable holders active rather than mistaking failed inspection for proof of death. Verification reproduced a live UTC holder being reclaimed by a Europe/Zurich checker: the previous reuse heuristic compared a zone-less invocation timestamp with a wall-clock process start. Reuse now requires a different clock-independent process identity (Linux boot UUID plus start ticks); missing evidence keeps live locks active.
- Nested event bursts, 2026-10-13: a 200-child start/approval/settlement burst rebuilt 602 trees and visited 100,902 tree nodes. Reusing the existing publication timer avoids repeating whole-tree projection per child event, without a second cache index or timer. Invocation facts remain synchronous, direct projection reads are fresh, and root settlement publishes final facts immediately.
- Browser continuity, 2026-09-24: compaction removed two still-existing HTML artifacts from a live room because the sidebar used only current-segment records. Archived publication records now contribute to its artifact catalog, and expandable archived segments preserve access to earlier conversations. This reuses canonical session storage and projection, without publishing arbitrary files or adding a second durable transcript store.
The following changes belong to ADR 0099 unless another ID is named.
- Initial loopback projection, August 2026: the host listened on localhost
through
web-server, with a read-only viewer, one guest, Origin checks, acknowledgment windows, and bounded output. Transport/product spikes exercised authentication, wrong Origins, ping/pong, stale guests, forged acknowledgments, non-reading guests, slowloris frames, and teardown. They did not verify public reachability; external tunnel/browser checks lacked a configured endpoint. Those old test-run reports are working evidence, not the current relay contract. - Relay and typed input, 2026-08-17: multi-device use required an external
tunnel for the loopback design. Listener defenses and a
web-serverrecipe collision withsimple-httpdexisted only because Emacs was serving. An outbound content-blind relay removed that burden. Structured input also showed that writable transport need not imply Emacs-level control, so write-token bearers gained prompt and interrupt actions. Tracing interaction answers found a second model-input channel without the prompt byte bound; the same per-string bound now covers all guest text, enforced by the host. - Host authentication, 2026-08-29: an open endpoint protected ciphertext confidentiality but let strangers consume rooms, connections, and outbound Push resources. Host-token authentication addressed that operator cost. Making it mandatory briefly imposed credential distribution even for localhost-only relays, so tokenless local/test use remains an explicit option.
- Scoped input and attachments, 2026-08-29: replying while viewing a directive previously sent ordinary chat elsewhere. The filter became an explicit discuss scope. The photo-only attachment limit was unnecessary because Read already interprets typed files; PDF and text attachments reuse it. The decoded limit fell from 1.5 MiB to 1.25 MiB because base64 plus prompt text could exceed the relay's 2 MiB frame limit. Images can be downscaled; other oversized files are refused rather than truncated.
- Explicit invocation and per-session rooms, 2026-08-29: phone use needed planning, review, and compaction beyond free text. Typed frames and host-curated admission made those invocations explicit while keeping free text inert. Replacing a process singleton with per-session rooms removed its scaling limit; a proposed in-room session switcher was rejected because it broadened bearer authority unnecessarily.
- PWA persistence and Push, 2026-08-30/31: a suspended Home Screen viewer lost its in-memory key. Persisting an opted-in bearer enabled reconnect, but did not wake the app. Empty Web Push added wake-up without exposing session content to push services. Persistent VAPID identity preserves subscriptions across relay restarts; per-room service-worker scopes prevent one room's opt-out or key replacement from affecting another. The host replays routing metadata after transport reconnect so a network blip does not strand subscriptions.
- Artifacts, 2026-09-01: guests needed to inspect model-created HTML and Markdown. Relay-side file hosting was rejected because it added content trust, storage, quotas, and independently leaking URLs. Selected-only ApplyPatch records establish what actually applied, while the host serves bounded bytes on demand. Artifacts then lived in the session; ADR 0124 moved them into a workspace store that sessions attach to.
- Owner authority, 2026-09-02: daily phone control still required returning to Emacs to change mode or create separate work. A third bearer tier grants those two typed authorities, without broadening browser access to durable session operations. New rooms receive fresh credentials; requester replies preserve its tier. Other owner guests may approve requests and receive owner-room offers.
- Tool presentation, 2026-09-08: a Skill call displayed properly in Emacs but appeared as raw ToolCall/dependency reminders in the browser because projection dropped renderer metadata. Bounded semantic presentation corrected this, with host/viewer protocol 3 deployed together and the relay rebuilt to embed the corresponding viewer. It added no compatibility adapter.
- Nonblocking connection, 2026-09-08: room recreation spent 135.29 of 135.35
seconds in
websocket-openwhile IPv6 stalled before IPv4 succeeded; projection took 1 ms and QR encoding 4 ms. Asynchronous dialing plus a collaboration-only adapter postponing websocket's handshake until connection reduced the same unreachable-address probe to about 1 ms. A TLS fixture delaying negotiation by half a second returned from dialing in under 1 ms, accepted a trusted certificate, and rejected an untrusted issuer. This bounds UI blocking, not DNS reachability or IPv4 fallback time. - Room lifetime, ADR 0114: two active sessions expired under the old one-hour host timer. An idle timeout could not distinguish passive readers from idle tabs, and the relay's maximum-age sweep only forced automatic recreation. Explicit host-share lifetime replaced both absolute-age policies; ADR 0114 retains the reconnect, keepalive, and revocation rationale.
- Room storage boundaries: Dismiss originally removed a saved room link, and reload replayed old approvals. Notices and remembered rooms now have separate lifetimes. Whole-link storage also duplicated rooms and let tab saves overwrite each other; merging by room ID preserves the strongest received bearer, while each tab caps display to its own tier. Without that cap a full tab could use an owner tab's stored offer to open as owner. Per-tab current-share storage prevents reload from choosing an older notification-enabled room. The original browser manual supplied no separate dates for these corrections.
-
Room handoff, 2026-10-19: accepting a plan with Worktree forks the session and kills the planning buffers, so its guests saw only "Session ended" and lost the work they were following. The host now shares the successor without repeating
/collab's consent question, because those guests were already admitted to this work, and ends the old room with a per-guest goodbye linking the successor at the tier that guest holds. A bearer link still never retargets: the old room ends and the successor has fresh credentials. Reaching the goodbye exposed a relay race that dropped a host's last queued frames on close; the relay now flushes them before room-closed. -
Retained projection, 2026-10-10: every publish reprojected the whole transcript. At 1,001 records that took 62 ms on a desktop and 360-500 ms on the deployed host, so a streaming session kept Emacs about 80% busy: in the same daemon whiteboard edits rose from 221 to 373 ms p50, cursor presence reached 414 ms p95, and a join's snapshot took 3.1 s. Rooms now retain per-segment records, reused only while their source text, properties, context and recorded live lookups are unchanged; a streamed update projects 1,001 records in 12 ms instead of 63 ms on the desktop, with the same result. The whole-transcript segment scan remains because a later closing control can reclassify earlier text, so coalesced publishes are also paced by their measured cost, capped at one second so a single garbage-collection pause does not hold the stream for several seconds.
- Combined skill selection, 2026-09-20: the single armed chip and automatic menu dismissal prevented composing several skills on one message. The menu now stays open, selected skills travel as an explicit name array, and the existing planner applies them together. Admission is checked for every name on receipt and delivery; pasted message tokens remain inert.