These are current constraints, not a claim that every existing implementation is correct. A change that conflicts with one needs an explicit decision and updated tests and guidance—not a quiet rewrite that blesses a bug. A lasting trade-off belongs here with its reason and the PR that made it. Older numbered records explain history; look them up deliberately.
Process and runtime
- One server per user per profile; one current-thread runtime; a pane is a
LocalSettask that owns its!Sendlibghostty terminal. - Hot upgrades re-exec and re-adopt live PTYs; cold restarts, crashes and reboots do not preserve PTYs. Records are not process recovery.
- The upgrade blob’s field names are a format. A candidate binary must parse the staged blob and rebuild its records before the running image execs into it, and every shape a release wrote stays frozen in an append-only fixture, because a resume failure kills every pane.
- Exit is a facet, close is a purge. Remove policy removes on exit; Retain keeps an inspectable result until purge/expiry. Organization owns commit and reconciliation rules.
- Events are journaled and resumable; a reconnecting watcher gets what it missed or a gap marker, never silence.
Wire
- Each kind declares its stream contract; the Terminal’s is VT bytes from
server to client and structured input back, with libghostty on both
ends. The Terminal profile is negotiated per connection at
HELLO, but a connection that offers none is admitted: it consumes other kinds, and a Terminal stream it binds is refused withCODEC_UNAVAILABLE. - Stream and input frames (
INPUT_*,RESIZE_TERMINAL,FRAME_ACK,HISTORY_REQUEST) reach the kind that owns their resource through itsResourceApp, as commands do. A kind owns no frame it did not opt into: one it does not serve is refused withWRONG_RESOURCE_KINDand the connection stays. - Any 1.x client works with any 1.x server, older or newer. New behavior
ships as a feature bit or a skippable field or record tail; a server
answers what it does not know with
UNSUPPORTEDand stays connected. A new major is a deliberate, announced break (compatibility). - The kind catalog in
phux-protocol::kindsis the one table for discovery and authorization; anything it does not name is denied. wire-schema.jsonis generated from the codec and test-pinned; the code is the specification.phux-coreandphux-protocolstay independent; the server bridges them.- File transfer rides the command envelope, never PTY input or a second
transport, and addresses a machine by host name, not a pane.
PUT_FILEwrites only into a server-chosen sandbox because a client-named write is an execution surface;GET_FILEreads any file the serving user can read because anINPUTholder on that server can alreadycatit, so the read is classifiedINPUTon the server rather than fenced by a path allow-list (protocol, #1657). - Checkout methods are typed requests keyed by a Terminal, not a generic
exec or a string-named invoke. The server resolves the checkout from the
Terminal’s live working directory and runs the shared
phux-gitmodule, so mobile, remote desktop, TUI and MCP read one contract; a client never names a host path. They areINPUTon that one Terminal, not on the server asGET_FILEis: anINPUTholder on it can already rungitthere. A Terminal whose working directory is inside a large checkout (say, dotfiles tracked at$HOME) therefore exposes every file in that checkout outside.gitto that Terminal’sINPUTholders (protocol). - A host service (OpenCode) is reached through the phux connection, not its
own port, tunnel, or pairing broker: the server names the service, owns
its loopback origin and mints its own credential, and relays one HTTP
exchange at a time that the client pulls in chunks. phux authorization is
the only gate,
INPUTon the server as forGET_FILE, so revoking a device revokes its OpenCode access and no service credential reaches it (host services).
Public operations
- Terminal creation expresses a complete intention: destination, direct argv and placement. Membership, arrangement entry and launch intent commit together; OS execution follows, not a fictitious database/process transaction. New API resources retain failed/completed execution records for reconciliation.
- Keep the binary transport and shared runtime. Public JSON DTOs and nested schemas describe the workflow, not transport tags or metadata carriers. The connected server serves the description; installed-binary discovery remains separate. No generic invocation framework or second listener is introduced.
- A retry identity has a bounded retained receipt, not an exactly-once promise forever. A read-only creation lookup can recover lost replies without effects. Missing/expired receipts leave the original outcome unknown; never infer that it is safe to start again. Dispatch-time authorization remains authoritative.
Clients
phux-client-coreis sans-IO and dependency-light so it compiles to wasm.phux-client-runtimeis the one place client orchestration lives.phux-client-ffiis the one binding crate.- Pane interiors are libghostty’s; chrome is the client’s. The two never paint the same cell.
Agents and authority
- The server derives agent state. A host’s lifecycle stream outranks the detector; an occupant-bounded declaration outranks both.
- The ordinary owner socket has full same-UID authority; delegated sockets only narrow it. Remote consumer ingress requires scoped mTLS plus bearer admission; reverse federation grants the trusted hub satellite-owner authority. Bearer-only and anonymous remote ingress are gone.
- Clients pin the server’s certificate authority at pairing and never learn one from a connection. Devices and satellites enroll with single-use tickets.
- Humans outrank agents on input leases. Scoped grants can be held for approval; the owner never is.
- Plan usage is a plugin publishing a Global L3 key (
phux.usage/v1), not a wire verb or server code: last-writer-wins host facts belong in metadata, and clients watch it with the genericSUBSCRIBE_METADATA. Declared producers and feeds (ADR-0177) remain the upgrade path if a fact outgrows one key (agents).
Substrate
- One keeper writes each work store; shared work has one authoritative home store. Adapters are one-way to avoid competing task authorities. Blackbird is retired from phux coordination. Reverse join grants satellite-owner authority and is only for one person’s own machines; delegated credentials cannot mint, and cold restart requires human reauthorization.
- The accepted future coordinator design uses a separate
phux-coordinator/1endpoint with an external fencing witness, never the terminal stream. That coordinator, durable grant ledger, autonomous Lead, per-agent cross-host grants, and organization-to-organization trust do not ship today (substrate, #1689).
Not doing
- No embedded scripting language: commands are typed messages; logic lives
in scripts that call
phux. - No in-process plugin host: plugins are external processes declared in config.
- No homegrown selection or search engine beyond find-in-scrollback: selection and extraction belong to libghostty.
- No homegrown crypto: TLS, SSH, and Unix permissions.
- No tmux behavior “for compatibility” where phux has a reason to differ.
Release
- Release Please versions from conventional commits. Checksums are minisign signed and the installer verifies them. One manifest per channel.
- Generated reference pages are byte-pinned by a test; hand edits fail CI.