A session-based terminal workspace where the frontend is disposable and your shells are not.
Crash the terminal frontend, restart it, reattach, and your terminals keep running exactly where you left them.
Hexa splits into four layers:
hexe terminal— the terminal UI frontend (aliases:hexe mux,hexe multiplexer).- shared frontend runtime — attach lifecycle, transport, and the frontend-side session projection.
hexe session/hexe ses— the session authority that owns canonical session state.hexe pod— one per pane. Owns the PTY, holds the shell, buffers output even while detached.
See architecture for the full picture.
A program can claim its own 256-colour table — one of 32 numbered slots — for the output it writes. Recolour that table and only its cells change — the rest of the pane stays exactly as it was, on screen and in scrollback, with no redraw from the application.
hexe palette set --ns 4 33=#ff00aa bg=#1a1020
hexe palette get # what is actually setAn application drives it directly, with nothing to negotiate first — claim a slot, print, release it:
printf'\033]1330;set;4;33=#ff00aa\033\\'printf'\033]1330;use;4\033\\'printf'this line resolves colour 33 through slot 4\n'printf'\033]1330;end\033\\'hexe holds the colours and resolves the indexes; it never decides which cells belong to which slot. Every cell records the slot that was current when it was written — the number itself, so there is no mapping to lose — and two slots are correct on screen at once, with a repaint reaching scrollback. Slot 0 is what unclaimed output resolves against, so setting it recolours the ordinary palette. Anything hexe does not recognise — an unknown name, a program that claims nothing, another terminal entirely — falls back to your own palette, so a default install looks exactly as it did before.
See the palette protocol for the sequences to emit.
One document per feature in docs/, each opening with a recording of it running:
how it works, how it differs from tmux, what it cannot do, and where it lives in the tree. Every
claim in them was checked against the source or a running build, and the recordings are made by
hexe itself — see recording.
| Four processes | frontend, runtime, SES, pod — and why only one is disposable |
| Sessions | detach, reattach, replay, adoption, and what is written down |
| Pods | one daemon per pane: the PTY, the backlog ring, exactly-once input |
| Instances | two whole stacks on one machine that cannot see each other |
| Panes and tabs | the split tree, geometric focus, zoom, broadcast, select-and-swap |
| Floats | overlay panes with lifetimes: per-directory, sticky, exclusive, sandboxed |
| Keybindings | no prefix: chords, conditions, and what happens to the key afterwards |
| Reading what happened | scrollback search, copy-mode, OSC 133 prompt marks |
| Overlays and popups | notifications, questions, pickers, keycast, pane labels |
| Painting | the bar, titles, sprites and popups are drawn by an external painter |
| Shell integration | what a shell reports to the mux, and how the prompt is drawn |
| Palette protocol | a program claims its own 256-colour table for the output it writes |
| Configuration | one Lua file, a schema that refuses typos, reload without losing panes |
| Project sessions | .hexe.lua, freezing a session, and the trust ledger |
| Isolation | namespaces and cgroups per pane — and what it needs from the kernel |
| The command line | addressing sessions, panes and pods from a script |
| Recording | hexe writes asciicasts of itself; every film in the docs was made that way |
Build (requires Zig). A static musl binary, which is what make build gives you:
make build # zig build -Doptimize=ReleaseFast, static musl, stripped
make install # …and copy it to ~/.local/bin/hexeRun:
hexe terminal new # a new session, named after a pokemon
hexe terminal new --name work # or named by you
hexe # bare: attach to a session rooted here, or load ./.hexe.luaDetach and come back. Detach is a keybinding, so it is whatever your config says — there is no built-in chord:
hexe.key({ hexe.key.ctrl, hexe.key.alt, hexe.key.d }, hexe.action.detach()),hexe session list # what is running, attached or not
hexe terminal attach work # by name, or by uuid prefixConfig lives at ~/.config/hexe/init.lua and is Lua. See configuration, and
keybindings for the binding language.
Started as bash and Python hacks wrapped around tmux. Absolutely cursed code. Shell scripts spawning tmux sessions, Python daemons talking to tmux through send-keys, config files that were basically more shell scripts. It was wild. But it worked, and it was the workflow I wanted.
Rewrote it properly in Rust on top of tmux-rs, got far, learned a lot about terminal internals. But that crate is mostly unsafe and you're still building on top of tmux's architecture rather than escaping it.
Then Ghostty came out. Saw what Mitchell was doing with Zig and decided to start from scratch. Zero regrets. Zig is a joy, Ghostty's VT implementation is solid, and the architecture finally matches what I actually wanted to build.