A shipped Rust agent-multiplexer (herdr) makes the same first-party-owns-the-PTYs bet kolu's remote-terminals plan chose — so it's a reference implementation of R2.4, not a competitor. One handoff-discipline borrow, several validations; of the two gaps it surfaced, native resume now ships (juspay/kolu#1495) and resize arbitration remains open. Claims fact-checked against both codebases.
A study of ogulcancelik/herdr (cloned @ HEAD) read through kolu’s remote-terminals plan (#951 and the pty-daemon / kaval-tui / chrome-bar docs). Sibling to theGhostex vs. kolu remote-terminalsanalysis. Every load-bearing claim below was fact-checked against both codebases — herdr citations all verified; three kolu-side claims were corrected (noted inline).13-agent workflow + adversarial critique
Both projects are AGPL-3.0-or-later, so herdr’s open-source code is license-compatible with kolu — porting is permitted under the AGPL’s terms, not blocked. The reason most recommendations are techniques and design rather than verbatim code is the stack gap (herdr is Rust; kolu is TypeScript/SolidJS), not licensing.
herdr is a ~107K-LOC Rust TUI agent multiplexer: one binary, a long-lived background server that owns every PTY (one ghostty VT emulator + one OS-thread PTY actor per pane), thin clients that attach/detach over a unix socket. Workspaces → tabs → panes. An agent-awareness sidebar rolls each workspace up to its most urgent state (blocked / working / done / idle). A second socket exposes a JSON API so agents themselves can create panes, read output, and wait for state. Named sessions, remote-over-SSH, 14+ agent integrations, and an experimental zero-downtime live handoff.
The architectural contrast
Module correspondence. SOLID edges = herdr validates a decision kolu already made, or a direct borrow. DASHED edges = a gap or an explicit non-goal. herdr owns one long-lived server; kolu's R2 inverts the survivor to kaval (the package renamed from @kolu/pty-host in R2.2) while the provider DAG runs fresh in kolu-server.
Concern
herdr
kolu (built + planned)
Who owns PTY lifetime
First-party long-lived server owns every master fd; clients are stateless front-ends.
Same bet. R2 makes kaval the thin survivor; the volatile provider DAG runs fresh in kolu-server.
Survive restart
Server outlives clients; full restart restores from a snapshot; resume_agents_on_restore respawns agents.
kaval-tui = client detach/reattach; R2.4 = daemon survives systemctl restart via cgroup-escape + reattach-by-id. The #1034 hazard lives here.
Recovery on owner restart
Transactional: old owner stays alive and re-binds sockets until the new one acks; one bool gates who may signal children; injected-failure tested.
R2.4’s composed captureSession → drainTerminals → respawn → finalize with waitForPidGone — designed to never repeat the “kill-then-pray” loss.
Late / lazy attach
A live screen snapshot, never a byte replay: reset baseline → re-render the live emulator into one full frame.
Same:ptyHost.ts subscribes then serializes a snapshot | delta union (~4KB).
Renderer
Server diffs a cell-grid → ANSI. No web terminal.
Raw VT → xterm.js in the browser; the headless mirror is for snapshot + taps only.
Architecture — what to adopt
UX — what to adopt
Gaps herdr surfaced
The two highest-leverage herdr ideas the plans never covered. One — native session resume — was clean and real, and kolu has since shipped it (G1, juspay/kolu#1495); the other is a pre-existing condition the plans still don’t arbitrate and remains open (G2).
What to do next
R2.4, now (low risk): adopt the transactional handoff discipline (A1), the single-owner kill invariant (A2), the snapshot invariant test (A3), the two-axis honest-state + inline-recovery-hint (A4). Add the SCM_RIGHTS non-goal note (A7).
Close G2 (multi-client resize arbitration) — Phase 2 shipped with documented last-resize-wins; an arbiter (and the size-change tap attach.ts already names) is still open.
kaval-tui: A5’s socket path and G3’s attach TTY-guard shipped (#1084, #1255); carry the non-tty contract forward to Phase 3’s kill/spawn.
UX: ship the attention rollup with Done = unseen (U1), keeping unread-bytes distinct from turn-finished; fold the navigator into the palette (U3).
G1 (native --resume) shipped (#1495): the live agent.sessionId is persisted as a { kind, id } ref and replayed by id on wake/restore — the clearest adoptable, now done.
U2’s blocked signal:#905 shipped (screen-scrape awaiting_user) — remaining: map awaiting_user into the U1 rollup as Blocked. Later: R9 reattach-hint UX (A6).
Net: herdr is the reference implementation for the survivor kolu already chose to build — most of its architecture validates R2 rather than redirecting it, with one battle-tested checklist to harden R2.4 (A1) and one explicit non-goal to write down (A7). The durable surprises were two gaps it surfaced: native session resume (now shipped by exact id, juspay/kolu#1495) and multi-client resize arbitration (still latent in an already-endorsed feature).