A MIDI controller as a hardware control surface for herdr — the terminal workspace manager for AI coding agents. Pads light up with your agents' states, one tap jumps you to any agent (across Hyprland desktops), the sustain pedal is push-to-talk dictation, and the pitch wheel scrolls whatever you're looking at. The included profile targets an Arturia KeyLab 61 mk3; the device layer is where other controllers plug in.
herdy began as a personal tool, built and tested against one very specific desk.
Important
Treat the included KeyLab profile as a hardware-verified reference, not generic out-of-the-box MIDI-controller support. Broader device profiles and more robust hardware discovery and recovery are later iterations; the generic-controller abstraction and simultaneous multi-device experience still need design work. Device-support requests, test reports, and PRs are welcome—especially from people willing to validate changes on their own hardware.
| Control | Action |
|---|---|
| Pads 1–12 | herdr agents in sidebar order. Tap → focus that agent (jumps workspace + tab + pane, summons the terminal window in Hyprland, warps the cursor into the pane). Hold → peek: a notification with the pane's recent output, without focusing — herdr's unseen/done state is preserved |
| Pad LEDs | Agent status: orange = working, dim blue = idle, blinking yellow = blocked, green = done. Focused agent is brighter |
| Main encoder | Step through panes in the focused workspace (crosses tabs, wraps) |
| Rewind / FastFwd | Workspace up / down |
| Play / Stop | Enter / Esc to the focused pane |
| Loop | Jump to the next blocked agent (LED blinks yellow while any agent waits on you) |
| Record | Toggle dictation (voxtype) |
| Sustain pedal | Push-to-talk dictation — foot down records, foot up transcribes into the focused pane; consecutive dictations are auto-spaced |
| Pitch wheel | Rate scrolling: deflection = speed, spring-release = stop. Scrolls the pane under the cursor (which the surface keeps parked on the focused pane) |
| Low white keys (C2–B2) | Phrases typed into the focused pane without submitting — Play sends. continue, run the tests…, LGTM, … (remap in config.toml) |
| Screen buttons (top) | LCD views — AGENT / WORK / BLOCK — plus BOARD (opens a PR-board herdr plugin) |
| Screen buttons (bottom) | CONT (nudge focused agent) · COPY (pane tail → clipboard) · PASTE · SNIF (agentic PR review of the clipboard URL, via a sniffr herdr plugin) |
| LCD | Focused agent + status + time-in-status; workspace popup on switch |
That's the default mapping — every row of it is a binding in a config file, not code (see Customizing).
Start from the tested profile, then adapt it:
git clone <this repo> && cd herdy
# point your coding agent at AGENTS.md and describe your setup
AGENTS.md carries the architecture, the verified herdr socket
API facts, the reverse-engineered KeyLab SysEx protocol pointers, and the
hard-won hardware gotchas — everything an agent needs to retarget the
surface at your controller, compositor, or agent stack without rediscovering
it all. The experiments/ directory is the full archaeology (protocol RE,
prototypes) if you or your agent want to dig deeper.
- Arturia KeyLab 61 mk3 — the only tested device. The 49/88 mk3 will probably work (port matching is by name substring), the Essential mk3 will not (different protocol). Device must be in the DAWs program (Prog button) with the DAW pad bank selected (Bank button, fifth bank) — the bank cannot be selected reliably via SysEx.
- Arch Linux + Hyprland (omarchy) — window summoning uses omarchy's
Lua dispatch syntax with classic hyprctl as fallback; cursor hiding
relies on
cursor:hide_on_key_press. - herdr ≥ 0.8 (socket API protocol 20) with an agent integration
installed so agent states flow (
herdr integration install claude). - uv for Python;
wl-clipboardfor COPY/PASTE. - voxtype for dictation (Record button / sustain pedal).
- ydotool (+ its user service) for pitch-wheel scrolling and cursor
warping:
sudo pacman -S ydotool && systemctl --user enable --now ydotool. Without it everything else still works. - ghostty with
mouse-scroll-multiplier = 1— each wheel notch is one line, which the scroll feel is tuned around. - Optional: the BOARD/SNIF buttons invoke the
pr-boardandsniffrherdr plugins by id; without them those buttons just log an error.
uv sync
uv run main.py # foreground; the terminal is the debug view
uv run main.py --debug # + raw MIDI / raw event / LED firehose
As a service (runs from the checkout, logs to the journal):
systemctl --user link $(pwd)/systemd/herdy.service # adjust paths inside if needed
systemctl --user enable --now herdy
journalctl --user -u herdy -f # the debug view
Stop the service before running by hand — two instances would both drive the MIDI ports.
omarchy/dghelm.herdy/ is an Omarchy Shell bar widget and configuration
panel for the local daemon. It exposes Feel, Phrases, Bindings, Pad theme,
and Devices; supports MIDI Learn and keyboard phrase picking through an
exclusive capture stream; and shows live pitch-wheel and sustain telemetry.
It talks only to herdy's local Unix control socket.
The installable distribution lives at
dghelm/omarchy-herdy.
| Hub | Feel |
|---|---|
![]() |
![]() |
Install and enable it directly:
omarchy plugin add https://github.com/dghelm/omarchy-herdy.git --enableFor plugin development from this checkout, validate
omarchy/dghelm.herdy/ and copy it into ~/.config/omarchy/plugins/ as a
local source tree.
See docs/omarchy-panel.md for the protocol, capability fallbacks, input-capture lifecycle, recovery behavior, and tests.
Copy config.example.toml to ./config.toml (or
~/.config/herdy/config.toml) and edit — herdy hot-reloads it on save,
so you can retune with the service running. Without a file you get the
stock behavior above. What lives there:
[[bind]]— remap any control to any action (slot.*.tap,soft.5.press,pedal.sustain.press→agent.focus,pane.type,hypr.dispatch, …). Override a default by reusing itsonstring;action = "none"unbinds.[phrases]/[phrase_keys]— the canned-text vocabulary and which keys type it.[theme]— status → LED color/blink, focus boost, LCD text colors. A monochrome vibe is one hue plus blink/dim per status.[scroll]/[input]— pitch-wheel feel (deadzone, rate curve), pad-hold and pedal thresholds. If scrolling moves several lines per notch, that's your terminal's scroll multiplier, not the surface.[devices.*]— device profiles;slotspicks which pads are agent slots (on a big pad grid, dedicate a couple of columns and bind the rest to anything).
docs/architecture.md is the map for going further:
new actions are one registered function in actions.py; new hardware is
one Device profile in devices.py.
- herdy for Omarchy is the installable Omarchy bar widget and configuration panel for this daemon.
- Chordarchy maps chords played on a physical MIDI keyboard to Omarchy shortcuts and commands—a complementary take on using MIDI hardware as a desktop control surface. See its Omarchy Plugins listing.
uv run python test_surface.py
uv run python test_control.py
uv run python test_config_patch.py
uv run python test_config_pass_e.py
uv run python test_input.py
uv run python test_input_helper.py
uv run python test_panel.py
node omarchy/dghelm.herdy/test_model.js
omarchy plugin validate omarchy/dghelm.herdy
The suite is hardware-free and covers the state model, control protocol, atomic configuration writes, input subscriptions, panel state machines, the KeyLab profile's MIDI translation/render caching, and actions. Hardware behavior is still validated the honest way: by running it.



