Skip to content

feat(subagents): add herdr terminal multiplexer support - #57

Open
0xRichardH wants to merge 1 commit into
HazAT:mainfrom
0xRichardH:feat/herdr-mux-backend
Open

0xRichardH wants to merge 1 commit into
HazAT:mainfrom
0xRichardH:feat/herdr-mux-backend

Conversation

@0xRichardH

Copy link
Copy Markdown

What

Adds herdr as a first-class mux backend alongside cmux/tmux/zellij/wezterm.

How

  • New file: pi-extension/subagents/herdr.ts — all herdr-specific CLI integration, extracted into its own module per SRP.

    • Runtime detection (HERDR_ENV=1 + herdr in PATH)
    • Surface creation: new tab per subagent (herdr tab create --no-focus) so parallel spawns get full tabs instead of ever-narrower splits
    • Command delivery: herdr pane run (atomic text + Enter in one socket request)
    • Screen reading: herdr pane read --source visible (reliable for freshly-created panes)
    • Escape, close, tab/workspace rename
    • Env-var-first current pane info (HERDR_PANE_ID/HERDR_TAB_ID/HERDR_WORKSPACE_ID) with subprocess fallback
  • pi-extension/subagents/cmux.ts — adds "herdr" to MuxBackend, getMuxBackend(), muxSetupHint(), and dispatches to herdr.ts in all mux operations. No herdr logic leaks into the dispatcher.

  • test/integration/harness.ts — probes herdr in getAvailableBackends(), implements focusSurface/getFocusedSurface/getSurfacePane for herdr.

  • test/integration/mux-surface.test.ts — skips the focus-preservation test for herdr/wezterm (neither exposes absolute pane focusing via CLI).

  • test/test.ts — herdr detection and JSON response parsing unit tests in a dedicated describe("herdr.ts") block.

  • README.md — documents herdr as a supported multiplexer.

Bug found and fixed during development

hasCommand in herdr.ts initially used execFileSync("command", ["-v", ...]) which fails because command is a shell builtin, not a binary — Node's execFileSync doesn't invoke a shell. This silently broke herdr detection (returned false even inside a live herdr session). Fixed to use execSync(\command -v ...`)(shell form) matchingcmux.ts`.

Test results

npm test:                        133 pass / 0 fail
npm run test:integration (herdr):  8/8 mux-surface pass

Verified end-to-end in a live herdr 0.7.1 session: create → send → read → close, rename tab/workspace, escape delivery.

Extract herdr into its own backend module (herdr.ts) and wire it into
the mux dispatcher (cmux.ts). Herdr surfaces are created as new tabs
with --no-focus so parallel subagent spawns get full tabs instead of
ever-narrower splits, and focus stays on the parent pane.

herdr.ts owns all herdr-specific CLI integration:
- Runtime detection (HERDR_ENV + herdr in PATH)
- Surface creation (tab create / pane split)
- Command delivery (pane run — atomic text + Enter)
- Screen reading (pane read --source visible)
- Escape delivery, pane close, tab/workspace rename
- Env-var-first current pane info with subprocess fallback

cmux.ts adds herdr to MuxBackend, getMuxBackend(), muxSetupHint(),
and dispatches to herdr.ts in createSurface, createSurfaceSplit,
sendCommand, sendEscape, readScreen, readScreenAsync, closeSurface,
renameCurrentTab, and renameWorkspace.

Integration harness probes herdr and implements focus/focused-surface
helpers. The focus-preservation test is skipped for herdr/wezterm
because neither exposes absolute pane focusing via CLI.

README documents herdr as a supported multiplexer.

Tested: 133 unit tests pass, 8/8 herdr mux-surface integration tests
pass in a live herdr session.
@abix5

abix5 commented Jul 22, 2026

Copy link
Copy Markdown

@HazAT Let's proceed, as everything is working.

@mfractal

Copy link
Copy Markdown

I tested this on Herdr 0.8.0 and opened a follow-up PR against this PR's head branch: 0xRichardH#29. It fixes Herdr-vs-inherited-cmux precedence, explicit source-pane handling, left/up placement and cleanup, hidden-tab reads, and adds live focus/layout plus failure-path coverage (135 unit tests and 10 Herdr mux integration tests passing).

@BigKelLearns

BigKelLearns commented Sep 2, 2026

Copy link
Copy Markdown

Hey @0xRichardH — clean implementation, thanks for putting this together. One thing worth flagging before this can land against current herdr:

This targets herdr 0.7.1, but herdr's CLI changed shape in 0.8.x. Specifically:

  • pane IDs are now w:p (e.g. w1:p3), not the 1-2 form
  • the layout probe returns .result.layout.panes[].rect (keyed by pane_id), not .result.root_pane.pane_id

I've been running a parallel implementation targeting 0.8.x (verified live against herdr 0.8.2) here:

The two take different approaches (you use a dedicated herdr.ts module; I branched inline in cmux.ts), but the functional deltas worth your attention:

  1. 0.8.x id/layout shapes — my branch parses w:p ids and the .result.layout.panes[].rect probe. Worth folding in if you rebase onto current herdr.
  2. Missing-branch hazard — when I first added herdr inline, I missed readScreen / readScreenAsync / closeSurface and they fell through to the zellij path (spawnSync zellij ENOENT, leaked panes on
    teardown). Your separate-module dispatch sidesteps that, but all nine backend-dispatch sites need a herdr branch regardless of approach — easy thing to lose in a refactor.
  3. Out-of-pane targeting — mine adds PI_SUBAGENT_HERDR_SESSION so a named session can be targeted from outside a live pane; your detection (HERDR_ENV=1 && hasCommand) only works inside a herdr pane.
    Might be worth adopting.

Small nod: --source visible on pane read is a nice touch — I'd happily borrow it.

Happy to sync up. If it helps, I can open a PR against your branch with just the 0.8.x id/layout changes, or share the diff directly. Which direction would you prefer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants