Using nexus from herdr
herdr is the terminal workspace manager nexus integrates with. The plugin turns every sandbox into something you can see and act on without leaving herdr: a listing overlay, a guest shell in a pane, and lifecycle actions bound to the focused workspace.
Install the plugin built
One-command install (Linux x86-64)
The plugin is self-bootstrapping. One command downloads, verifies, and installs everything:
herdr plugin install IniZio/nexus/plugins/herdrbuild.sh (the plugin's build hook) reads plugins/herdr/nexus-version for a pinned release tag, downloads nexus-linux-amd64 and SHA256SUMS from GitHub Releases, verifies the checksum, installs the binary to ~/.local/bin/nexus, ABI-probes it, then runs nexus herdr install-default-shell --write-config to hard-link the guest shell and wire config.toml automatically.
Manual fallback. If install-default-shell --write-config is skipped or fails the herdr config check step, it prints the line to paste:
[terminal]
default_shell = ~/.local/bin/nexus-guest-shellPaste it into ~/.config/herdr/config.toml and run herdr server reload-config.
Confirm the plugin loaded:
herdr plugin listPlatform matrix
| Platform | Status |
|---|---|
| Linux x86-64 | supported — nexus binary downloaded and installed by build.sh |
| macOS arm64 | supported — nexus-client downloaded from the pinned release by build.sh |
| macOS amd64 | supported — nexus-client downloaded from the pinned release by build.sh |
| Linux arm64 | no released binary — build from source (see below) |
macOS users run the same install command as Linux:
herdr plugin install IniZio/nexus/plugins/herdrbuild.sh detects the platform, downloads the matching nexus-client binary from the pinned release, verifies its checksum, and installs it to ~/.local/bin/nexus-client. No Go toolchain required.
For Linux arm64, build.sh exits with a clear message. Build and wire manually:
git clone https://github.com/IniZio/nexus
cd nexus && go build -o ~/.local/bin/nexus ./cmd/nexus
nexus herdr install-default-shell --write-configThen wire the plugin from a local clone (two steps — herdr plugin link does not run the build hook, so run build.sh first):
cd /path/to/nexus/plugins/herdr
NEXUS_LOCAL=1 sh build.sh
herdr plugin link /path/to/nexus/plugins/herdrLocal-dev path
If nexus is already on PATH and you want to skip the download entirely (e.g. while iterating on the binary itself):
cd /path/to/nexus/plugins/herdr
NEXUS_LOCAL=1 sh build.sh
herdr plugin link /path/to/nexus/plugins/herdrherdr plugin install only accepts OWNER/REPO (GitHub) source; it cannot install from a local path. herdr plugin link registers the manifest but does not run the [[build]] hook. Run build.sh manually before or after linking; NEXUS_LOCAL=1 skips the download and uses the binary already on PATH.
build.sh writes the shim next to the plugin files (plugins/herdr/nexus-shim.sh in a checkout install), and that shim is what every live hook execs. Override INSTALL_DIR for a throwaway run only together with NEXUS_SHIM_DIR=<dir>; without it build.sh refuses rather than repoint the live shim at a temporary binary.
Rebuild the binary, not just the plugin
The shim records the absolute path of the installed binary. If you install a new binary to a different location, re-run the install so the shim is rewritten. A stale shim is the most common cause of "herdr shows old behaviour".
Sandbox lifecycle in herdr built
Create: transactional open
When you run nexus: create a sandbox (or open a worktree pane), nexus creates three things in sequence: the sandbox, the herdr workspace, and the binding that links them. If any step fails before the binding is written, the incomplete pieces are rolled back — you do not end up with orphaned workspaces. A sandbox that already exists and is running is never destroyed by a failed pane-open.
Teardown: one idempotent path
Whether teardown is triggered by the nexus: remove this sandbox and close the space action, nexus herdr remove, a nexus rm cascade, or the real-time pane-close reap described below, it follows the same path: remove the sandbox, close the herdr workspace, delete the binding. If the workspace close does not succeed, the binding is retained rather than deleted, so nexus herdr prune --apply can retry it on the next pass — the only copy of the record is never lost.
Real-time reap on pane close
Closing the last pane of a worktree sandbox tears it down automatically. herdr sends a trappable SIGHUP to the process group; nexus catches it and runs the teardown path above. This is best-effort. If the signal arrives while nexus is in a non-interruptible state, or if herdr is not available, the teardown is skipped and the binding remains. nexus herdr prune --apply is the reliable backstop — run it after a session to confirm nothing leaked.
prune --apply: 4-case reconciler
nexus herdr prune (dry-run by default) inspects every binding and classifies it:
| Binding state | Action |
|---|---|
| sandbox gone + workspace gone | delete binding |
| sandbox gone + workspace live | close workspace, delete binding |
| sandbox live + workspace gone | keep binding, clear stale workspace ID |
| both live | noop |
It also sweeps for nexus:-labelled orphan workspaces that have no binding at all, and closes them.
nexus herdr prune # dry-run: report what would change
nexus herdr prune --apply # apply: close workspaces and delete stale bindingsGlobal prune --apply is a manual verb. A workspace whose tab you merely closed is absent from herdr workspace list, so a global apply reaps its sandbox too. The worktree.removed hook therefore never runs it; it runs nexus herdr prune --apply --workspace <id>, which takes the workspace id from the event payload and reconciles only that one binding (the named workspace is taken as gone; no orphan or bindingless sweep runs). Run the dry form first to see what a global apply would take.
Known residue (D-SHL-27)
The first worktree you open for a given repo opens a plain host shell — not a supervised guest pane. Every subsequent worktree for that repo auto-creates a supervised sandbox as expected. This is a known limitation left in place by decision D-SHL-27. The first worktree's sandbox, if one is left orphaned, is reclaimed by:
nexus herdr prune --applyWhat the overlay shows built
Open it with the nexus: list sandboxes action. herdr has no built-in action menu key, so actions are invoked through whatever you have bound to a palette — with the jt.command-palette plugin that is prefix+p — or from a terminal:
herdr plugin action invoke workspaces --plugin nexusThe overlay lists every sandbox, however it was created:
WORKSPACE STATE AGENT MOUNTS SPACE ID
demo/api running - /work bound sb-06G1…
demo/agent-1 running claude-code /work,nm→/work/nm - sb-06G1…| Column | What it answers |
|---|---|
WORKSPACE | the project/name handle you pass to every other command |
STATE | running, paused, stopped, created, error |
AGENT | which agent profile it was created for, or - for a plain sandbox |
MOUNTS | guest paths of live host mounts, then volume→path for named volumes |
SPACE | bound if a herdr workspace is attached, - if not |
ID | the sandbox ULID, for commands that want an unambiguous ref |
MOUNTS is the column that usually decides what to open: a sandbox with no mounts gives you a guest shell in /root, while one with a live mount drops you straight into the mounted directory.
The same information is available in a terminal with nexus ps.
Actions built
These appear in herdr's action list for the focused workspace:
| Action | Effect |
|---|---|
nexus: list sandboxes | open the listing overlay described above |
nexus: create a sandbox | prompt for image, project and name, then create, boot and open a space |
nexus: attach to a workspace | reattach to an existing guest session |
nexus: create sandbox space (from local Containerfile) | build, boot, and open a space in one step |
nexus: open guest pane | another guest shell in the current space |
nexus: pause this sandbox | pause the bound sandbox — frees CPU, keeps memory state |
nexus: resume this sandbox | resume it |
nexus: remove this sandbox and close the space | remove the sandbox, close the workspace, drop the binding |
nexus: workspace logs | not built prints a not-implemented notice |
nexus: doctor | substrate and plugin diagnostics |
Every action resolves the sandbox from the focused herdr workspace, so none of them asks you to type a handle.
Sandboxes created outside herdr built
A sandbox made in a terminal is a first-class herdr citizen:
nexus create demo/api --image ghcr.io/inizio/nexus-base:latestIt appears in the overlay immediately, because the listing is unfiltered. The first time you run a herdr action against it, nexus adopts it — creating the binding that the action needs and telling you so:
nexus: adopted sandbox demo/api into herdr as nexus:demo/apiAdoption is deliberately lazy. Binding at creation time would mint a herdr workspace for every throwaway sandbox — including the ones nexus run creates and deletes seconds later — and would make sandbox creation depend on herdr being installed. Doing it on first use costs nothing until you actually ask herdr to act.
Adoption does not create a herdr workspace. Pause, resume and remove need only the sandbox handle; only opening a pane needs a workspace, and that one is created at the moment it is needed. Otherwise pausing a sandbox would leave an empty workspace behind every time.
Run nexus herdr list to see the current bindings.
A working session
# create a sandbox with your repo mounted live
nexus create demo/api --image ghcr.io/inizio/nexus-base:latest --mount "$PWD:/work"
# see it
nexus psThen, in herdr: run the nexus: list sandboxes action, find demo/api, and use nexus: open guest pane. The pane opens a login shell already in /work, because the shell's working directory is derived from the sandbox's first live mount.
When you are done, nexus: remove this sandbox and close the space tears down the sandbox, the workspace, and the binding together. Verify nothing leaked:
nexus reapUpdate the plugin built
To upgrade nexus to the version pinned in the plugin's nexus-version file, re-run the same install command:
herdr plugin install IniZio/nexus/plugins/herdrThe build hook (build.sh) compares the installed binary against the pin. It upgrades an older release build, and leaves a -dev build or a binary newer than the pin in place. To check whether your installed version matches the pin, run:
nexus herdr version-checkThis prints one of: version ok: <ver> (matches pin), a kept-newer or dev message, or version skew: installed <old> < pinned <new> with the exact update command on stderr. The same check runs at herdr startup and in nexus herdr doctor.
Restart herdr after a plugin upgrade
After running herdr plugin install …, the new binary is installed but the running client daemon (nexus-client herdr local-agent-startup) continues using the old code. To activate the new version, restart herdr:
herdr server stop
# then relaunch herdr normallyUntil herdr is restarted, port forwarding continues with the old daemon behaviour.
Troubleshooting
An action says a sandbox does not exist. The binding outlived the sandbox — something removed it outside herdr. nexus herdr list shows the stale entry; removing through herdr again clears it.
The overlay is empty but you know a sandbox exists. The overlay reads the same store as nexus ps. If ps also shows nothing, check that both are running as the same user: state lives under the user's own state directory.
herdr shows behaviour you already fixed. Rebuild nexus and re-run the plugin install, per the warning above.
stop says the sandbox is still running. That is the honest answer, not a bug in the report: the detached supervisor did not finish within its timeout, so the record still reads running. Wait a moment and check nexus ps; if the state does not settle, nexus reap will show whether anything leaked.
Ports are not forwarded even though sandboxes are running. The client daemon forwards ports for the sandbox bound to the currently focused herdr workspace on the host session. When no workspace is focused, or the focused workspace has no bound sandbox, no forwards are active — the daemon logs portfwd focus: focused workspace has no sandbox; forwarding nothing and cancels any previously applied forwards within one reconcile tick. Look for that line in ~/.local/state/nexus/portfwd-client/agent.log. Switch focus to the workspace that owns the sandbox, or run nexus herdr list on the host to confirm a binding exists.
Sole-interactive-client rule
herdr 0.9.0 maintains one session-wide focus state shared across all connected clients. The nexus-client daemon reads the focus from the host session. Focus-scoped forwarding is accurate only when nexus-client is the sole interactive client on that herdr session. When a host TUI and a remote client are both attached, the host session's most recent workspace focus is the one the daemon reads; the remote client has no independent focus channel.
Mac remote-client focus on herdr 0.9.0
herdr 0.9.0 does not reflect Mac remote-client focus changes in session.snapshot, workspace.list, events.subscribe, or any plugin hook. The host-side focus watcher works around this by tailing the session server log (~/.config/herdr/sessions/<session>/herdr-server.log, or ~/.config/herdr/herdr-server.log for the default session) and parsing lines with event="workspace.focus" and outcome="ok". This log-tail source is polled every 250ms; it is the authoritative source for remote-client focus until the upstream protocol exposes a per-client focus surface.