phux
decisions

Decision Records

Index of every decision that has closed off a design space in phux.

stable document
Full source summary

Index of every decision that has closed off a design space in phux. Format and Status: vocabulary defined in ../docs/CONVENTIONS.md. Read these when you need to know why something is the way it is — the architecture docs describe what the code is.

Project record

Read a decision when you need the reason a design space was closed. Use architecture for the current system shape.

Wire modelWhy terminal bytes remain terminal bytes.Peer consumersWhy the TUI has no protocol privilege.Projection modelWhy structured views stay consumer-owned.

We write down decisions so future contributors (including future-us) can understand why the system is the way it is. Format follows Michael Nygard’s template.

Index

#DecisionStatus
0001Use RustAccepted
0002Diff-based wire protocol, not VT byte replaySuperseded by 0013
0003Single server, many sessionsAccepted
0004libghostty-vt is the canonical gridAccepted
0005Relationship to zmx and zmoshAccepted
0006Input event types re-export libghostty-vt’s atomsAccepted (amended by 0024)
0007Mosh-class transport semantics and satellite forward-compatAccepted (forward-compat; SSH-auth-suffices policy superseded by 0098)
0008Use libghostty-vt’s types directly; stop reimplementing themAccepted (amended by 0024)
0009phux vs coder/mux: positioningAccepted
0010phux is TUI-first, non-TUI not precluded; tmux control mode reserved as compat optionAccepted (forward-compat)
0011phux-protocol and phux-core are independent; IdBridge is their only meeting pointAccepted
0012Window layout is a binary split tree, not n-aryAccepted
0013Libghostty bytes on the wire; structured input remainsAccepted (supersedes 0002)
0014Server-side Terminal placement: per-pane PaneActor on a LocalSetAccepted
0015Protocol layering: L1 substrate, L2 collections, L3 metadataAccepted (L2 tier dissolved by 0030)
0016TerminalId as the wire primary; PaneId is a consumer-side aliasAccepted
0017The reference TUI is not protocol-privilegedAccepted (refines 0010)
0018Lazy state synchronization is the wire’s long-arc shapeAccepted (builds on 0013)
0019Multi-pane TUI rendering: layout persistence, wire shape, and chromeAccepted
0020Layered render: ratatui chrome over libghostty pane interiorsAccepted
0021Control-plane commands and client-side selector resolutionAccepted (builds on 0017)
0022phux as a tool for agentsAccepted
0023Config UX: pure-config, defaults as a live base layerAccepted (TUI-local, builds on 0017)
0024The wire protocol owns its input atomsAccepted (amends 0006, 0008)
0025Browser web client over a WebSocket transportAccepted (builds on 0017, 0024)
0026Overlays: one theme, a real stack, and a single dispatch pathAccepted (builds on 0020)
0027Terminals are referenced, not owned: views, links, and L3 tagsAccepted (builds on 0017, 0015)
0028Runtime log controlAccepted (forward-compat, builds on 0024)
0029One cursor authority and a repaint schedulerAccepted (extends 0020; shipped — end_of_frame_cursor and the RepaintLevel accumulator both live, see 0046)
0030Engine-delegated wire and projection consumersAccepted (supersedes the L2 tier of 0015)
0031Remote-consumer authentication and encryption (no SSH tunnel)Proposed
0032Graceful server upgrade (sessions survive a binary update)Accepted
0033Input authority leases and process signals (“take the wheel + kill”)Accepted
0034Kitty graphics / image passthrough through the cell rendererProposed
0035Agent-asked event: a pending human-answerable question on the wireAccepted
0036Agent-asked detection sourcesAccepted
0037Overlay-network reachability for remote self-host consumersAccepted (forward-compat, builds on 0007, 0031)
0038Hub-to-satellite authenticationAccepted (builds on 0031)
0039Layered config: an ordered extends stack with explicit array appendAccepted
0040Agent identity and lifecycle are an L3 metadata recordAccepted
0041Managed plugin installs: snapshot fetches, system tools, one lockfileAccepted
0042Launch executor: a CLI verb that spawns an integration templateAccepted
0043State-diff output mode and loss-tolerant reference advanceAccepted
0044Dedicated input lane: route input off the single runtime threadAccepted
0045Client-side copy-mode over the consumer’s own engineAccepted (builds on 0030, supersedes the abi epic’s server-side selection frames)
0046The server derives agent state; detection is level-triggeredAccepted (extends 0040; implements 0029’s repaint accumulator)
0047CI metrics recorded to an orphan ci-metrics branchSuperseded by 0082
0048Drag-to-resize panes and default outer-terminal mouse captureAccepted
0049Client-local focus and advisory agent attentionAccepted (reaffirms 0019)
0050Explicit spawn ownership, client-owned placementAccepted
0051Outbound dial-out (connector) transport modeAccepted (walks through 0037’s deferred door; builds on 0031, 0038)
0052Connector route identity, registration, and config surfaceAccepted (settles 0051 open questions 1/4/5)
0053Acknowledged idempotent input batchesAccepted (builds on 0021, 0024, and 0044)
0054Worktree-bound sessions by name conventionAccepted (composes existing verbs; adds no server state, consistent with 0009)
0055Always-on server and ssh-bootstrapped enrollmentProposed (makes 0031/0037 usable; mirrors 0038’s pin posture)
0056Cross-session Terminal moveAccepted (opens the door 0050 left shut for existing Terminals; layout stays L3 per 0019)
0057A minimal reference relay in-treeAccepted (implements 0051 and ADR-0052; backs 0051’s trust-honesty claim)
0058Right-click context menus for panes, windows, and sessionsAccepted
0059Sandboxed chunked file uploadAccepted (builds on 0007, 0021, and 0031)
0060Self-contained session recordingAccepted (a consumer-side projection over 0013’s bytes; claims no protocol standing, per 0017)
0061Capabilities add, versions breakAccepted (generalizes the version-gate constraint that shaped 0060; the fleet-wide break it names is what 0032 survives)
0062Headless resize and the window-size policyAccepted (settles the explicit-vs-view precedence 0027 left to a “future resize verb”; takes no wire under 0061)
0063Ephemeral server lifetimeAccepted (an opt-in exit condition alongside the last-pane self-exit of 0003; survives re-exec via 0032)
0064Playback as a paneAccepted (supersedes the “shipping a player” rejection in 0060 for the pane-shaped case only; takes no wire under 0061 and fits the pane with 0062)
0065One CLI grammarAccepted
0066One phux host namespace over the split machine registriesAccepted
0067Cache-preserving agent fleet contextAccepted (projects 0040/0046 into agent-host context without changing the wire)
0068Native agent-session restoreAccepted (bridges 0040, 0042, and workspace archives through bounded L3 provenance; adds no wire under 0061)
0069Generated reference docs from the compiled binaryAccepted
0070Native engine-state bootstrap and client-owned historyAccepted (replaces native clients’ synthesized-VT bootstrap under 0013 with an opaque libghostty READY/history lifecycle; compatibility clients retain synthesized VT, and one PTY retains one authoritative geometry)
0071What phux 1.0 commits toProposed (freezes the consumer surface under semver while the wire keeps its own 0.x line per 0061; point 6 enumerates the agent verbs, JSON documents, event names, and error codes inside that freeze)
0072Prune the policy vocabulary, keep the authorization seamProposed (prunes the unreferenced half of the vocabulary 0031 introduced, and keeps the HELLO seam a post-1.0 paired-workload feature must implement)
0073Login-shell semantics for service-managed pane spawnsAccepted (closes the environment gap 0055’s generated unit opened)
0074The self-update trust boundaryAccepted (checksum-gated, atomic, never mutates an install another tool owns; delivers the one-command update path 0071 puts in 1.0 scope)
0075Agent names are addressable, and a withdrawn name is refusedProposed (adds a %name sigil to the client-side resolution of 0021, over the identity 0040 already carries; no wire change, a grammar addition 0071 has to carve in, and it owns the write-time safety gate every input verb reads)
0076Prompting an agent is acknowledged; waiting on one is event-drivenProposed (spends 0053’s acknowledged batch on the agent surface and subscribes to 0046’s published record rather than adding a sequence field; the wait half shipped, the prompt half has not)
0077The agent read surface: sources, soft wrap, and truncationAccepted (extends 0022’s read surface with additive JSON keys under 0061, all four shipped at SCHEMA_VERSION 3; the alternate-screen harvest it originally carried is split out to 0078)
0078Harvesting alternate-screen historyProposed (split out of 0077 because it is the one read that writes: it narrows the side-effect-free guarantee docs/spec/L1.md §6.1 makes for GET_SCREEN, acquires 0033’s input lease, and needs a capability bit under 0061)
0079Fatal-signal terminal restoreAccepted (covers the teardown path RawModeGuard::drop and the panic hook cannot reach — a SIGSEGV/SIGBUS/SIGABRT out of 0004’s native engine, which does not unwind; vendors phux-crash, the workspace’s only Apache-2.0-ONLY crate, keeping its unsafe behind a crate boundary as 0032 does for portable-pty-adopt)
0080Socket lifecycle and instance isolationAccepted (liveness is a connect probe, not socket existence; every build resolves a profile that scopes socket/runtime/state dirs; supervision corrected under 0003, with the upgrade handoff riding 0032)
0081Overlay auto-listen and one-command pairingAccepted (binds 0037’s overlay address at startup, gated on 0031’s pairing-token store, so phux pair is a pure credential operation with no restart; default profile only, per 0080)
0082Retire the CI metrics store; the run page is the dashboardAccepted (supersedes 0047 — deletes the ci-metrics branch, its collector, and the observatory lane, keeping only the zero-cost step-summary half; a hosted dashboard, if it returns, is the site’s to own)
0083In-place supervisor unit reconcileAccepted (applies 0080’s restart-policy correction to an already-installed unit by patching only those keys — no re-render, no reload, no stopped server; launchd cannot pick it up live and the command says so)
0084Starting an agent in an existing shellAccepted (separates in-place, shell-evaluated startup from 0042’s direct-argv pane creation; positive OSC 133 prompt evidence gates submission, detector publication supplies identity, and possible delivery retains the bound name)
0085Hook-sourced agent state is detector evidenceAccepted (adds capability-gated REPORT_AGENT_STATE: hooks publish immediate working/blocked/done edges through the detector without writing a state declaration that disables self-healing)
0086The pooled libghostty render trio lives in phux-protocolAccepted (one RenderPool owns the RenderState/RowIterator/CellIterator trio and the phux-5pyx rebuild-on-resize, behind the existing server feature; dirty-bit policy stays at the call sites, which deliberately differ)
0087Elastic status-bar space is row-wide slack, not slot layoutProposed (defines the spacer widget frozen by 0071: paid from the row’s leftover width, split evenly, zero on an overflowing row — rather than giving [status] slots a two-pass width budget)
0088Adopting a live server into supervisionAccepted (no supervisor can restart-manage a pid it did not start, so install --adopt transfers the supervision rather than the process: the unit is armed instead of loaded, the incumbent keeps its panes, and the auto-spawn path completes the hand-over)
0089The sidebar is a bounded attention inbox, not a structural listAccepted (three zones ranked by how much each row wants a human: a capped cross-session queue that contributes zero rows when nothing is blocked, the focused session’s windows behind a floor, and one rolled-up line per other session; built from verbs the client already sends, so no wire surface is added)
0090Predictive echo returns to the alt screen via confirmation-gated displayAccepted (predictions queue and reconcile on both screens but display on the alternate screen only after the app confirms a non-blank echo, re-locking on contradiction and hiding on a one-second timeout; the adaptive back-off becomes a display lock so its re-arm path can actually fire; upstreams phux-mobile ADR-0019)
0091The certificate names the advertised address, once, at generationAccepted (SANs cover the listener’s bind address and the overlay address the connect link carries, chosen only when the certificate is minted; an existing certificate is never widened because that rotates the fingerprint every paired device pins, so coverage is reported by phux pair, the listener log, and phux doctor instead)
0092The coordinator owns durable workProposed (would narrowly amend 0009, scope 0030 to terminal synchronization, and reuse 0033/0053 rather than create a second runner)
0093--remote user@host is a resolution ladder, not a new transportAccepted (resolves a target to a [[remote]] entry — registry hit, pasted phux://connect code, or one-time ssh pairing — then reuses the existing dial; adds no transport, no wire change, and no trust model, and user@ is a pairing/lookup label rather than a wire identity)
0094Per-pane scrollback is bounded in bytes, by phux, explicitlyAccepted (defaults.history-limit is only libghostty’s line limit and the engine’s own 10_000-byte constructor default was what actually bound retention, so phux sets the byte bound itself and exposes it as defaults.history-bytes, default 2 MiB, capped at 64 MiB by config check; retention trades directly against attach latency because the native bootstrap materialises every retained page at READY)
0095Blackbird is a peer ledger, not a phux clientAccepted (the two daemons do not connect: the seam is one optional field in the 0040 record written by the 0067 integrations, and the “required by Blackbird ADR-0005” justification is deleted because that document was never written and its architecture is archived)
0096Performance telemetry is always on, in-process, and one command awayAccepted
0098Workload proof and closed-scope authorityAccepted (forward-compat; mutual Ed25519 proof over a persistent authority fingerprint, fresh server incarnation, and channel binding; canonical endpoint-owned scopes are intersected with the live registry and enforced before dispatch; explicitly amends 0031)
0099CI: one aggregate merge gate, immutable action pins, and shared lane setupAccepted (the ci aggregate context is the sole merge contract with an explicit skip policy; pull_request: closed cancels the PR’s runs by head SHA; every action reference is SHA-pinned, checked by a gate, and Dependabot-bumped; Rust-lane setup is one composite action; cockpit and release lanes cache the FFI/Zig layers; toolchain versions derive from rust-toolchain.toml only)

When to write an ADR

  • Picking between viable approaches with long-term consequences.
  • Closing off a design space (deciding against something).
  • Anything you’d want to explain to a new contributor on day one.

When NOT to write an ADR

  • Bug fixes.
  • Refactors that don’t change behavior.
  • Anything purely internal to a single function.

Template

# NNNN — Short title

Status: Proposed | Accepted | Deprecated | Superseded by ADR-NNNN
Date: YYYY-MM-DD

## Context
What is the situation that calls for a decision?

## Decision
What was decided.

## Rationale
Why this and not the alternatives.

## Tradeoffs
What we give up.

## Alternatives considered
Brief sketch of the other candidates and why they lost.
View exact source