72 seconds, with music and no speech. One workspace in every client: Suplex for Linux, the sidebar in a terminal, the Mac, and Android on a Harbour home screen and in a Flotilla float. Terminals and agents of three nodes side by side, and the same workspace on a desk and a phone at once.
Filmed by script on 14 Nov 2026, on a fleet of three made-up nodes on one machine: Suplex 0.1.0, Suplex for Android 0.6.14, Harbour 0.12.102, Flotilla 0.1.53, Rex 0.0.1793741575, pi-daemon 1.14.0. The agents are real Pi sessions on real daemons, and what they say is scripted so that every take is the same. The phone is an emulator; what Nix-on-Droid runs on a real one (its own Rex, pi-daemon, pi-client and Suplex daemon) runs beside it as the node phone. The Mac's window is the real one, recorded through the Mac's own screen sharing at about nine pictures a second. Some shots run faster than they were filmed, the fastest 4.6 times.
Suplex is a multiplexer of multiplexers. A terminal multiplexer such as Rex keeps terminals in sessions, tabs and splits; a browser keeps pages; an agent host keeps conversations. Suplex lists what each of them holds, on every machine, and lets one workspace of tabs and splits point at any of it, side by side.
It stores the layout and nothing else. Every pane still belongs to the backend that made it, and its stream comes straight from there.
todayRex's terminals in every client, and pages on the Mac, on Linux and on Android. More backends are in progress.
What already runs
- worksession
- editorwindow
- vimblock
- testswindow
- cargo testblock
- Workwindow
- Grafanapage
- agentssession
- wrenwindow
- logswindow
What Suplex stores
- tabs
- splitbuildrow
- sloteditorrex@desk#block:01a1…
- splitcolumn
- slottestsrex@desk#block:01a4…
- slotgrafanatyler@desk#p_7f3a
- slotagentsrex@atlas#session:01b2…
A tree, and four addresses. No terminal's bytes and no page are in it.
What a client draws
1 ## Remaining work 2 3 | ID | Work | Needs | 4 | --- | --- | --- | 5 | V2 | frames views | V1 | 6 | X6 | resume a slot | | ~ ~ PLAN.md 3,1
running 16 tests ................ ok. 16 passed
wren reading tests/merge.rs wren 3 of 16 cases need the new op wren writing the fourth ▍
$ git status --short M crates/core/src/op.rs M tests/merge.rs $
The tab on show is this device's own. Each pane's stream comes straight from its backend: a terminal's bytes, a page's pictures or its address.
Workspaces, frames and slots
A workspace is a named tree. Its nodes are frames, of three kinds, and its leaves point at things that backends own.
| tabs | shows one child at a time |
| split | lays its children out in a row or a column; each child has a weight |
| slot | a leaf: empty, or one reference to an entity of a backend |
Suplex makes nothing of its own. Filling a slot asks a backend to make something new, or pins something it holds already; the slot keeps the address, and the backend's recipe for bringing the thing back should it go. An empty slot offers both.
{
"name": "work", "root": "f_…ARSZ",
"frames": {
"f_…ARSZ": { "kind": "tabs" },
"f_…00YP": { "kind": "split", "parent": "f_…ARSZ", "pos": "V", "dir": "row", "title": "build" },
"f_…0E1D": { "kind": "slot", "parent": "f_…00YP", "pos": "V", "title": "editor", "weight": 3,
"ref": { "at": "rex@atlas#block:…510b", "kind": "terminal", "resume": { the backend's recipe } } },
"f_…RWB3": { "kind": "split", "parent": "f_…00YP", "pos": "W", "dir": "column", "weight": 2 },
"f_…3BJJ": { "kind": "slot", "parent": "f_…RWB3", "pos": "V", "title": "tests",
"ref": { "at": "rex@atlas#block:…67cc", "kind": "terminal", "resume": { the backend's recipe } } },
"f_…8RE0": { "kind": "slot", "parent": "f_…RWB3", "pos": "W", "title": "docs",
"ref": { "at": "fake@atlas#p_7", "kind": "page", "resume": { the backend's recipe } } },
"f_…XNF4": { "kind": "slot", "parent": "f_…ARSZ", "pos": "W", "title": "agents",
"ref": { "at": "rex@atlas#session:…e8c9", "kind": "session", "resume": { the backend's recipe } } }
}
}
An ID is the backend's own and says nothing of where the thing sits, so an address survives a move. It may name a leaf or a container.
rex:work@deska second Rex server on one machinerex@desk#*everything that backend holds
A slot that points at a container is a pin
A whole Rex session, a window, a whole backend. The client draws whatever the backend has there now, from the backend's live tree, as the backend arranges it. Nothing under a pin is stored in Suplex, so what other programs change there never touches the master.
What is done inside a pin is the backend's own operation. Step the keys one layer in, and a split made there is Rex's split and closing a pane closes it in Rex. One layer out, the same keys act on the slot as a whole.
Backends
A backend is a multiplexer that speaks the contract to its machine's daemon over a local socket: JSON-RPC, one object a line. The contract is additive. A backend keeps working with no Suplex anywhere.
Rex needed no change: suplex backend rex runs beside a Rex server as one more client of Rex's own control API.
| rex | terminals | works |
| reference | terminals, pages as addresses, a screen as pictures; every method of the contract | works |
| tyler | a browser's pages; its own daemon speaks the contract. Linux draws them | in part |
| desktops | a machine's screens and windows, as pictures: an X server, a Mac, an Android device, programs kept in cages. Built; not yet in daily use | in progress |
| pi | a machine's agent sessions, listed and made new. Built; not yet turned on | in progress |
- sessiontabs
- windowfloat
- layersingle
- splitsplit
- blockleaf
- windowtabs
- tabsingle
- grouptabs
- splitsplit
- pageleaf
backend fake- windowtabs
- tabsingle
- splitsplit
- paneleaf
- tabstabs
- splitsplit
- slotleaf
- tabs ordered, one on show
- split a row or a column, by weight
- single one child, which fills it
- float rectangles, front to back
- list an inventory
- leaf a terminal, a page
No backend is pressed into session, tab, block. Each says which levels it has and how a level arranges its children. Suplex's own tree is the one that can hold any of them.
| hello | Its schema: the levels, how each arranges its children, the operations each level takes, the kinds of leaf and how each can be shown. |
| tree | tree.get and tree.watch: entities with IDs that are the backend's own and stay for the entity's life; changes told as they happen. |
| operations | entity.create, close, move, rename, select, focus, resize, zoom. Each carries a request key: asked twice, it is done once. |
| views | view.open answers offers, best first: vt terminal bytes, frames pictures, surface a shared native surface, url an address the client loads, external the backend's own app. |
| resume | A recipe for bringing an entity back. The backend writes it; Suplex keeps it unread and hands it back when what a slot pointed at has gone. And what letting go of an entity does, keep or close. |
# the first line a backend sends, and the daemon's answer {"jsonrpc": "2.0", "id": 1, "method": "backend.hello", "params": { "protocol": "suplex.backend.v0", "backend": "yours", "version": "1.0", "schema": SCHEMA, "capabilities": ["watch", "resume", "views", "state"]}} {"jsonrpc": "2.0", "id": 1, "result": {"node": "atlas", "suplexd": "0.1.0", "capabilities": [those both have]}}
suplex node listatlas (here) master 0.1.0 55be3ba2 - fake, rexsuplex backend listfake@atlas 0.1.0 [watch resume views state unasked shown place] window (tabs) > tab (single) > split (split) > pane (leaf)rex@atlas 0.1.0 [watch state resume views unasked] session (tabs) > window (float) > layer (single) > split (split) > block (leaf)suplex tree rex@atlas --depth 5rex@atlas├─ work session [session:…0d7a]│ └─ editor window [window:…9476]│ └─ layer [layer:…0834]│ └─ split [split:…8009]│ ├─ shell block terminal [block:…510b]│ └─ shell block terminal [block:…67cc]└─ agents session [session:…e8c9] └─ wren window [window:…f810] └─ layer [layer:…4047] └─ shell block terminal [block:…ef39]
SCHEMA = {
"levels": [
{"name": "window", "arrangement": "tabs", "children": ["page"], "ops": ["rename"]},
{"name": "page", "arrangement": "leaf", "kinds": ["page"], "ops": ["rename"]},
],
"root": ["window"],
"kinds": {"page": {"presentations": ["frames"], "size": "shared"}},
}
suplex tree demo@atlasdemo@atlas└─ Work window [w1] ├─ Grafana page [p1] └─ Docs page [p2]
The example backend is 152 lines of Python with no packages. It says hello with this schema, two levels, and answers tree.get and entity.rename from a small tree in memory. That is enough to be listed in the fleet, as above, and pinned into a slot.
suplex backend check fake@atlas --mutatefake@atlas 0.1.0 [watch resume views state unasked shown place]pass H0 the backend is registered with the daemon, which took its hello and schemapass T1 tree.get without an ID gives the top-level entitiespass T2 the whole tree (7 entities) follows the schema and hangs togetherpass T3 an answer is one message: at most 5,000 entities and under a megabytepass T4 tree.get with depth 0 gives the entity alonepass T5 tree.get with depth 1 gives the entity and its childrenpass T6 an unknown ID is refused as not_foundpass T7 an unknown method is refused as unsupportedpass T8 an unknown field is ignoredpass T9 what stands at the top is given in parts: after one of it, what follows and none of it againpass W1 tree.watch gives a watchpass O1 entity.create makes an entity, in the tree when it answers… 48 more lines: 48 pass60 passed, 0 failed, 0 skipped
One command holds a backend to the contract and to its own schema, rule by rule. The reference backend passes all 60 rules, asked through a daemon, and the check is itself tested by breaking that backend one rule at a time. For a backend in Rust, the crate suplex-backend has the protocol, request keys, watches, views and the stream of pictures written once.
- the layout: edits, small JSON
- control, on a local socket
- a pane's stream, straight from its backend
Across machines
Every machine runs the same daemon. One is the master: it keeps the workspaces, orders edits and relays them. It never carries a pane's data.
A client talks only to the daemon on its own machine, which holds a full copy, opens from it at once and keeps working when the master cannot be reached.
- < 5 msan edit on one node reaches a client of another, at the 99th percentile
- < 14 MiBthe master's own memory, holding 100 workspaces of 20 frames
- < 20 ms
suplex workspace list, at the median
Budgets a test holds, with three daemons on one machine.
| Two edits to one property | The later one wins, by a clock that travels with the edit. A stale offline edit does not beat a newer one that got there first. |
| Edits to different properties or frames | Both apply. |
| A delete whose author had not seen the newest edit under that frame | Refused: the edit wins. |
| An edit under a frame deleted meanwhile | Applies, and brings the frame back. |
| A move that would put a frame inside itself | Refused. |
The master tells every node of an edit at once and answers its author only when the edit is on disk. Until that answer the author keeps the edit, in memory and in a journal.
So an edit whose author was answered is on the master's disk, and one that was only told around is still held where it was made. Killing a daemon loses neither.
Access is the tailnet's: like Rex's own servers, a daemon admits callers on the tailnet without a login, and refuses what only a browser sends.
Clients
A client draws a workspace. The connection, the edits, how a split shares its room, the keymap and following a backend's stream are one library under all of them.
| suplex tuiworks | the sidebar: a tree, and a workspace laid out with a live terminal in every pane |
| Macbeta | native; Ghostty's terminal, its own web view for a page, and pictures of what a backend draws |
| Linuxworks | native and drawn by itself; Ghostty's terminal core, and a page from its backend's pictures |
| Androidinternal testing | terminals, its own web view for a page, and pictures of what a backend draws |
| CLI, MCPworks | the same operations, for scripts and for agents |
work ────────────────── tabs│ 1 build │ 2 agents build │ editor rex@atlas#block:01a1dc92-4703-78…│ tests rex@atlas#block:01a… editor rex@atlas#block…│~/lighthouse main $ ls │running 16 tests tests rex@atlas#block:…│Cargo.toml PLAN.md crates docs site │................ docs fake@atlas#p_7│~/lighthouse main $ │test result: ok. 16 passed; agents rex@atlas#session…│ │ 0 failed │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fleet ──────────────────── 1│ │ atlas master · here│ │ docs fake@atlas#p_7 fake 1 window│ │ rex 2 sessions│ │ │ │ │ │ │ │ │ │ │ │ page fake@atlas#p_7 │ │ │ │ │ │ │ │ │ │ │ │ │ │ atlas master Ctrl-] back to the tree Ctrl-A ? keys
What is the same in every client is written once, here. Each client brings its own terminal emulator (Ghostty's, on the Mac and on Linux), and the library hands it the backend's bytes as they arrive.
CtrlA or CtrlB, then
- hjklthe pane that way
- |Ssplit right, split down
- cnpnew tab, next, previous
- 1–9a tab by its number
- x&close the pane, the tab
- z=zoom, even out
- ,$rename a tab, the workspace
- g:switch workspace, every action
One keymap for every client, read from one file, keys.yaml. The sidebar, the Mac app and the Linux app have every chord here. One layer in, a chord is the backend's own: in a pinned Rex session, | makes Rex's split.
A run, as it happened
A throwaway daemon called atlas, a private Rex server with two sessions, and the reference backend. Every line below is what the program printed; long IDs are cut to their last four.
suplex workspace create workcreated work w_…51W1suplex frame add work --kind split --dir row --title buildadded f_…00YPsuplex frame add work --kind slot --parent f_…00YP --title editor --weight 3added f_…0E1Dsuplex frame fill work f_…0E1D --pin rex@atlas#block:…510bf_…0E1D points at rex@atlas#block:…510bsuplex frame fill work f_…8RE0 --pin fake@atlas#p_7f_…8RE0 points at fake@atlas#p_7suplex frame add work --kind slot --title agentsadded f_…XNF4suplex frame fill work f_…XNF4 --pin rex@atlas#session:…e8c9f_…XNF4 points at rex@atlas#session:…e8c9suplex workspace show workwork w_…51W1 [tabs [f_…ARSZ]]├─ build split row [f_…00YP]│ ├─ editor rex@atlas#block:…510b ×3 [f_…0E1D]│ └─ split column ×2 [f_…RWB3]│ ├─ tests rex@atlas#block:…67cc [f_…3BJJ]│ └─ docs fake@atlas#p_7 [f_…8RE0]└─ agents rex@atlas#session:…e8c9 [f_…XNF4]
Works today
- The daemon, the master and its followers, merging, the CLI and the MCP server.
- Rex's terminals in all four clients, and a whole Rex session in one slot, drawn live.
- Pages: in a web view of the app's own on the Mac and on Android, and from the backend's pictures on Linux.
- A slot whose terminal has gone is brought back from its recipe when a person asks, and by itself only where its backend allows.
- The reference backend and the conformance check, for anyone writing a backend.
Not yet
- Pages in the sidebar, which shows a card with the address.
- Desktops and agent sessions as backends in daily use. They are built.
- iOS. And the repository is private, so there is nothing here to install.