decisions
Decision Records
Index of every decision that has closed off a design space in phux.
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.
Read a decision when you need the reason a design space was closed. Use architecture for the current system shape.
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
| # | Decision | Status |
|---|---|---|
| 0001 | Use Rust | Accepted |
| 0002 | Diff-based wire protocol, not VT byte replay | Superseded by 0013 |
| 0003 | Single server, many sessions | Accepted |
| 0004 | libghostty-vt is the canonical grid | Accepted |
| 0005 | Relationship to zmx and zmosh | Accepted |
| 0006 | Input event types re-export libghostty-vt’s atoms | Accepted (amended by 0024) |
| 0007 | Mosh-class transport semantics and satellite forward-compat | Accepted (forward-compat; SSH-auth-suffices policy superseded by 0098) |
| 0008 | Use libghostty-vt’s types directly; stop reimplementing them | Accepted (amended by 0024) |
| 0009 | phux vs coder/mux: positioning | Accepted |
| 0010 | phux is TUI-first, non-TUI not precluded; tmux control mode reserved as compat option | Accepted (forward-compat) |
| 0011 | phux-protocol and phux-core are independent; IdBridge is their only meeting point | Accepted |
| 0012 | Window layout is a binary split tree, not n-ary | Accepted |
| 0013 | Libghostty bytes on the wire; structured input remains | Accepted (supersedes 0002) |
| 0014 | Server-side Terminal placement: per-pane PaneActor on a LocalSet | Accepted |
| 0015 | Protocol layering: L1 substrate, L2 collections, L3 metadata | Accepted (L2 tier dissolved by 0030) |
| 0016 | TerminalId as the wire primary; PaneId is a consumer-side alias | Accepted |
| 0017 | The reference TUI is not protocol-privileged | Accepted (refines 0010) |
| 0018 | Lazy state synchronization is the wire’s long-arc shape | Accepted (builds on 0013) |
| 0019 | Multi-pane TUI rendering: layout persistence, wire shape, and chrome | Accepted |
| 0020 | Layered render: ratatui chrome over libghostty pane interiors | Accepted |
| 0021 | Control-plane commands and client-side selector resolution | Accepted (builds on 0017) |
| 0022 | phux as a tool for agents | Accepted |
| 0023 | Config UX: pure-config, defaults as a live base layer | Accepted (TUI-local, builds on 0017) |
| 0024 | The wire protocol owns its input atoms | Accepted (amends 0006, 0008) |
| 0025 | Browser web client over a WebSocket transport | Accepted (builds on 0017, 0024) |
| 0026 | Overlays: one theme, a real stack, and a single dispatch path | Accepted (builds on 0020) |
| 0027 | Terminals are referenced, not owned: views, links, and L3 tags | Accepted (builds on 0017, 0015) |
| 0028 | Runtime log control | Accepted (forward-compat, builds on 0024) |
| 0029 | One cursor authority and a repaint scheduler | Accepted (extends 0020; shipped — end_of_frame_cursor and the RepaintLevel accumulator both live, see 0046) |
| 0030 | Engine-delegated wire and projection consumers | Accepted (supersedes the L2 tier of 0015) |
| 0031 | Remote-consumer authentication and encryption (no SSH tunnel) | Proposed |
| 0032 | Graceful server upgrade (sessions survive a binary update) | Accepted |
| 0033 | Input authority leases and process signals (“take the wheel + kill”) | Accepted |
| 0034 | Kitty graphics / image passthrough through the cell renderer | Proposed |
| 0035 | Agent-asked event: a pending human-answerable question on the wire | Accepted |
| 0036 | Agent-asked detection sources | Accepted |
| 0037 | Overlay-network reachability for remote self-host consumers | Accepted (forward-compat, builds on 0007, 0031) |
| 0038 | Hub-to-satellite authentication | Accepted (builds on 0031) |
| 0039 | Layered config: an ordered extends stack with explicit array append | Accepted |
| 0040 | Agent identity and lifecycle are an L3 metadata record | Accepted |
| 0041 | Managed plugin installs: snapshot fetches, system tools, one lockfile | Accepted |
| 0042 | Launch executor: a CLI verb that spawns an integration template | Accepted |
| 0043 | State-diff output mode and loss-tolerant reference advance | Accepted |
| 0044 | Dedicated input lane: route input off the single runtime thread | Accepted |
| 0045 | Client-side copy-mode over the consumer’s own engine | Accepted (builds on 0030, supersedes the abi epic’s server-side selection frames) |
| 0046 | The server derives agent state; detection is level-triggered | Accepted (extends 0040; implements 0029’s repaint accumulator) |
| 0047 | CI metrics recorded to an orphan ci-metrics branch | Superseded by 0082 |
| 0048 | Drag-to-resize panes and default outer-terminal mouse capture | Accepted |
| 0049 | Client-local focus and advisory agent attention | Accepted (reaffirms 0019) |
| 0050 | Explicit spawn ownership, client-owned placement | Accepted |
| 0051 | Outbound dial-out (connector) transport mode | Accepted (walks through 0037’s deferred door; builds on 0031, 0038) |
| 0052 | Connector route identity, registration, and config surface | Accepted (settles 0051 open questions 1/4/5) |
| 0053 | Acknowledged idempotent input batches | Accepted (builds on 0021, 0024, and 0044) |
| 0054 | Worktree-bound sessions by name convention | Accepted (composes existing verbs; adds no server state, consistent with 0009) |
| 0055 | Always-on server and ssh-bootstrapped enrollment | Proposed (makes 0031/0037 usable; mirrors 0038’s pin posture) |
| 0056 | Cross-session Terminal move | Accepted (opens the door 0050 left shut for existing Terminals; layout stays L3 per 0019) |
| 0057 | A minimal reference relay in-tree | Accepted (implements 0051 and ADR-0052; backs 0051’s trust-honesty claim) |
| 0058 | Right-click context menus for panes, windows, and sessions | Accepted |
| 0059 | Sandboxed chunked file upload | Accepted (builds on 0007, 0021, and 0031) |
| 0060 | Self-contained session recording | Accepted (a consumer-side projection over 0013’s bytes; claims no protocol standing, per 0017) |
| 0061 | Capabilities add, versions break | Accepted (generalizes the version-gate constraint that shaped 0060; the fleet-wide break it names is what 0032 survives) |
| 0062 | Headless resize and the window-size policy | Accepted (settles the explicit-vs-view precedence 0027 left to a “future resize verb”; takes no wire under 0061) |
| 0063 | Ephemeral server lifetime | Accepted (an opt-in exit condition alongside the last-pane self-exit of 0003; survives re-exec via 0032) |
| 0064 | Playback as a pane | Accepted (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) |
| 0065 | One CLI grammar | Accepted |
| 0066 | One phux host namespace over the split machine registries | Accepted |
| 0067 | Cache-preserving agent fleet context | Accepted (projects 0040/0046 into agent-host context without changing the wire) |
| 0068 | Native agent-session restore | Accepted (bridges 0040, 0042, and workspace archives through bounded L3 provenance; adds no wire under 0061) |
| 0069 | Generated reference docs from the compiled binary | Accepted |
| 0070 | Native engine-state bootstrap and client-owned history | Accepted (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) |
| 0071 | What phux 1.0 commits to | Proposed (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) |
| 0072 | Prune the policy vocabulary, keep the authorization seam | Proposed (prunes the unreferenced half of the vocabulary 0031 introduced, and keeps the HELLO seam a post-1.0 paired-workload feature must implement) |
| 0073 | Login-shell semantics for service-managed pane spawns | Accepted (closes the environment gap 0055’s generated unit opened) |
| 0074 | The self-update trust boundary | Accepted (checksum-gated, atomic, never mutates an install another tool owns; delivers the one-command update path 0071 puts in 1.0 scope) |
| 0075 | Agent names are addressable, and a withdrawn name is refused | Proposed (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) |
| 0076 | Prompting an agent is acknowledged; waiting on one is event-driven | Proposed (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) |
| 0077 | The agent read surface: sources, soft wrap, and truncation | Accepted (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) |
| 0078 | Harvesting alternate-screen history | Proposed (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) |
| 0079 | Fatal-signal terminal restore | Accepted (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) |
| 0080 | Socket lifecycle and instance isolation | Accepted (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) |
| 0081 | Overlay auto-listen and one-command pairing | Accepted (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) |
| 0082 | Retire the CI metrics store; the run page is the dashboard | Accepted (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) |
| 0083 | In-place supervisor unit reconcile | Accepted (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) |
| 0084 | Starting an agent in an existing shell | Accepted (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) |
| 0085 | Hook-sourced agent state is detector evidence | Accepted (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) |
| 0086 | The pooled libghostty render trio lives in phux-protocol | Accepted (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) |
| 0087 | Elastic status-bar space is row-wide slack, not slot layout | Proposed (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) |
| 0088 | Adopting a live server into supervision | Accepted (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) |
| 0089 | The sidebar is a bounded attention inbox, not a structural list | Accepted (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) |
| 0090 | Predictive echo returns to the alt screen via confirmation-gated display | Accepted (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) |
| 0091 | The certificate names the advertised address, once, at generation | Accepted (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) |
| 0092 | The coordinator owns durable work | Proposed (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 transport | Accepted (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) |
| 0094 | Per-pane scrollback is bounded in bytes, by phux, explicitly | Accepted (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) |
| 0095 | Blackbird is a peer ledger, not a phux client | Accepted (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) |
| 0096 | Performance telemetry is always on, in-process, and one command away | Accepted |
| 0098 | Workload proof and closed-scope authority | Accepted (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) |
| 0099 | CI: one aggregate merge gate, immutable action pins, and shared lane setup | Accepted (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.