Herding Agents: How I Actually Use Herdr (on a Ridiculously Wide Screen)

One coding agent is a tool. Two is a pair. Six is a zoo, and somebody has to know which animal is chewing on the fence.

For most of this year that somebody was me, armed with a pile of terminal windows, a couple of tmux sessions I half remembered setting up, and the nagging feeling that some agent somewhere had been politely waiting on a yes/no question for twenty minutes. Agents don’t tap you on the shoulder. They sit there, cursor blinking, and burn the most expensive thing in the whole setup, which is wall-clock time.

Herdr fixed most of that for me. This post covers what it is, how it works, and how I lay it out day to day. There’s a companion post (TBD) on why I split things up the way I do and how I prompt inside the panes. This one is the tour.

A coding interface displaying a script on the left side and an output console on the right, with a dark theme and multiple lines of code written in a programming language.

What Herdr is

Herdr bills itself as “the runtime your coding agents live on.” Less marketing, more mechanics: it’s a terminal workspace manager built for AI coding agents. If you’ve used tmux, you already have about 70% of the mental model. It’s:

  • A single Rust binary. No Electron and no browser tab pretending to be a terminal. brew install herdr, or curl -fsSL https://herdr.dev/install.sh | sh, and you’re done.
  • Something that runs inside your terminal. iTerm, Ghostty, WezTerm, kitty, Alacritty, even inside tmux if you enjoy nesting dolls. It doesn’t replace your terminal. It organizes what’s in it.
  • A client/server pair. A background server owns the terminal sessions. The thing you look at is just a client. Close the window, detach, drop SSH, and the agents keep working. Reattach and they’re right where you left them. (I learned this the fun way while building the screenshots for this post: the demo window got closed out from under me mid-run, and every agent was still sitting there when I reattached.)
  • Agent-aware. This is the actual point. Herdr detects Claude Code, Codex, Cursor’s agent, Copilot CLI, OpenCode, Devin and a pile of others, then classifies each one’s state so you don’t have to go look.

The model: session › space › tab › pane

Herdr’s concepts page lays out four nouns. The precision matters, so here they are the way I think about them.

NounWhat Herdr says it isWhat I use it for
SessionA persistent server namespace. herdr attaches to the default one; herdr --session <name> gives you an isolated one.My one long-lived “everything” session, plus throwaway named sessions when I’m experimenting (like the demo session for this post’s screenshots).
Workspace (the sidebar calls them spaces)“The top-level project container. Use one workspace per repo, task, or investigation.”One space per folder I’m working in. Full stop.
Tab“A layout inside a workspace.”One tab per kind of work: pr-reviews, domain-modeling, implementation.
Pane“A real terminal.”One agent, or one shell, or one thing I’m watching. Arranged in vertical or horizontal splits.

Then there are the agent states, which make the whole thing worth it:

  • Blocked: the agent needs input, approval, or a decision. This is the one you care about.
  • Working: it’s chewing.
  • Done: it finished and you haven’t looked yet.
  • Idle: it finished and you have looked.
  • Unknown: Herdr can’t tell, usually because it’s a plain shell.

Those states roll up into the sidebar. Each space gets a dot, and there’s an agents list underneath that shows every agent across every space. One glance and I know who’s blocked, who’s working, and who’s done and quietly waiting for me to read their output.

That’s the shoulder tap agents never gave me.

How it works (enough to be dangerous)

Keyboard

The default prefix is ctrl+b, deliberately tmux-flavored, and the keymap is “prefix-first” so Herdr doesn’t steal keystrokes from your shell, editor, or the agent. The ones I actually use:

KeysDoes
prefix vSplit vertical: new pane to the right, side by side
prefix -Split horizontal: new pane below, stacked
prefix h / j / k / lMove focus left / down / up / right
prefix zZoom the current pane (and again to unzoom)
prefix rResize mode
prefix cNew tab
prefix shift+tRename tab
prefix 1…9Jump to tab N
prefix shift+nNew workspace (space)
prefix wWorkspace picker
prefix bToggle the sidebar
prefix qDetach (everything keeps running)
prefix ?Help, for when you forget all of the above

Run herdr --default-config to see the full annotated config, including how to remap any of it. Mouse works too: click, drag, split. I won’t judge.

The CLI and socket API

Here’s where it stops being “tmux with a status light.” Everything you can do with a keystroke you can also do from the command line over Herdr’s socket API. That includes the agents themselves, since they’re running in panes Herdr owns. Every screenshot in this post came from a layout I scripted like this, in an isolated session so my real one never got touched:

# a space per folder
herdr workspace create --cwd ~/Codez/ledger-service --label ledger-service

# name the first tab for the kind of work happening in it
herdr tab rename w3:t1 domain-modeling

# agent on the left, reference material on the right, split again downward
herdr pane split w3:p1 --direction right --ratio 0.5
herdr pane split w3:p2 --direction down  --ratio 0.5

# start an agent and hand it a scoped prompt
herdr pane run w3:p1 "claude --permission-mode plan"
herdr agent prompt w3:p1 "Read docs/domain.md and internal/ledger/entry.go. Using only the domain terms Account, Entry, Journal, and Posting (never 'transaction'), describe a Posting aggregate..."

# and then, the good part
herdr agent list      # who is working / blocked / done
herdr agent wait w3:p1 --until blocked --until done   # block until it needs me
herdr pane read w3:p1 # read what it said, without switching to it

That’s enough rope for agents to spawn panes, prompt each other, and notice when a sibling is blocked. I’ll admit that’s both very cool and the setup for a future post titled something like “How My Agents Unionized.”

Integrations

herdr integration install claude (or codex, copilot, opencode, and so on) drops a small hook into each agent so it reports its lifecycle to Herdr directly, rather than Herdr inferring state by watching the screen. herdr integration status shows what’s wired up. Do this. The state dots get noticeably more accurate.

The rest of the toolbox

  • Worktrees: herdr worktree create makes a git worktree and opens it as its own space. It fits the one-branch-per-agent pattern I wrote about in Giving Every Agent Its Own Branch: Git Worktrees for Parallel AI Work.
  • Remote machines: herdr --remote <ssh-target> puts panes on other boxes in the same sidebar, and they reconnect independently. See Connecting machines.
  • Session state: layouts persist and restore. Processes don’t survive a server restart (nothing does, that’s physics), but the shape of your workspace does. See Session state.

How I actually use it

That’s the brochure. Here’s what my screen looks like.

One space per folder

Every folder I’m actively working in gets its own space, named after the folder. As I type this, my sidebar has ten of them: interlinedlist, then interlinedlist-ios, interlinedlist-macos-native, interlinedlist-windows-app, interlinedlist-android, interlinedlist-architecture-migration, a couple of client and side projects, and the repo this post lives in.

Is that a lot? Sure. Is it more than what’s actually in my head? No. It’s exactly what’s in my head, and that’s the point. The sidebar is a direct readout of my working memory. If it isn’t a folder I’m working in, it doesn’t get a space. When I stop working in a folder, the space goes away. My config.toml even sorts the agent list by space (agent_panel_sort = "spaces") so the agents group the same way my brain does.

It’s mostly for my own mental organization, and I’m fine with that. The agents don’t care what the sidebar looks like. I do.

One tab per kind of work

Inside a space, tabs aren’t “more room.” Tabs are modes. I name them for the kind of work being done:

  • domain-modeling: one agent, plus the glossary, domain notes, and recent history open next to it. The agent’s job is to think, not type.
  • implementation: one or more agents, each scoped to a slice of the code, with a diffstat or test runner and a plain shell beside them.
  • pr-reviews: a reviewing agent next to the actual diff, so I can check its claims against the code without switching windows.

Most spaces have just one tab, whichever mode that folder is in today. The busy ones have all three. Switching tabs is switching hats, and the tab label tells me which hat is on before I’ve read a single line.

One or more terminals, split vertically or horizontally

Within a tab I keep a simple rule: agents get tall columns, shells get short stacks.

Agents produce long, scrolling output: plans, numbered findings, diffs they want to show me. That wants height. So agents get vertical splits (prefix v), side by side, full height. Shells, test runners, git log, diffstats and log tails are glanceable. They get horizontal splits (prefix -) stacked in the right-hand column.

The ultrawide changes the math

Here’s the cheat. I run all of this on a 5120×1440, 32:9 ultrawide: two 2560×1440 monitors fused into one with no bezel down the middle. At normal terminal font sizes, that’s room for three or four full-height agent columns plus a stacked utility column and the sidebar, all visible at once, nobody squished.

On a regular 16:9 screen, that same implementation tab looks like this:

Workable. Tight. Add a third agent and you’re zooming panes in and out all day. On the ultrawide it’s this:

The difference isn’t really “more stuff.” It’s that nothing is hidden. When an agent goes blocked, I don’t need to find it. It’s already on screen, in my peripheral vision, with a red dot in the sidebar to back it up. Herdr tells me who needs attention. The ultrawide means I can see why without changing anything.

That screenshot is an agent I’d told to touch cmd/main.go only. It found the repo had no go.mod, which meant staying in scope required a decision. So it stopped, went blocked, and asked. Herdr flagged it, and I could see the question without switching anything. Total cost: one keystroke. The alternative, an agent “helpfully” adding a module file and rewiring imports across a package I’d explicitly fenced off, costs a lot more than one keystroke. Why that worked, and how I write prompts so it keeps working, is the next post (TBD).

The short version

  • Herdr is a terminal workspace manager for coding agents: one Rust binary, runs in your terminal, background server, agent-state detection.
  • Spaces are folders. One per folder you’re working in. It’s a mirror of your head, not a filing cabinet.
  • Tabs are modes of work. pr-reviews, domain-modeling, implementation.
  • Panes: agents tall, shells short.
  • The sidebar tells you who’s blocked. The ultrawide lets you see why without moving.

Install it, run herdr, hit ctrl+b ?, and give it a day. Worst case, you’ve got a nice tmux. Best case, you stop finding agents that have been patiently blocked since lunch.

References

Twenty-ish Terminal Commands for Getting Around the File System

Recently I was digging around in the terminal and wanted an ls command that behaved like a fully opened tree view. That led me back through a bunch of the file-system commands I use all the time, along with a few switches that make them far more useful.

This is the resulting quick reference. I’m writing this on macOS with zsh, but most of it applies directly to Linux too. A few tools—particularly tree and fd—may need to be installed first.

First, the Many Faces of ls

The plain command lists the current directory.

ls

Add -l for the vertical, long-listing format. This shows permissions, link count, owner, group, size, modified date, and name.

ls -l

Add -a to include hidden entries—names beginning with a dot—and -h to make sizes human-readable. The switches can be combined.

ls -lah

To descend through every directory and list its contents, add -R for recursive.

ls -laRh

That gets us the fully opened contents, although the output is grouped by directory rather than drawn as a visual tree.

Navigation

1. pwd — Where Am I?

When I’ve wandered six directories deep and forgotten where I am, pwd prints the full path to the working directory.

pwd

2. cd — Change Directory

Move into a directory, up one level, back to the previous directory, or straight home.

cd projects
cd ..
cd -
cd ~

Paths containing spaces need quotes, as in cd "My Projects".

3. pushd and popd — Directory Bookmarks, Sort Of

pushd changes directories while saving the current location on a stack. popd takes me back. This is excellent when bouncing between two distant parts of a repository.

pushd ~/Code/my-project/docs
popd

4. tree — The Actual Tree View

Unlike recursive ls, tree draws the hierarchy. -a includes hidden entries, -L 2 limits output to two levels, and -h prints readable sizes.

tree -a -h -L 2

On macOS, install it with brew install tree if it is not already available.

Finding and Inspecting Things

5. find — Search the Directory Tree

Find every Markdown file below the current directory. Here . means “start here,” -type f limits results to files, and -name supplies the filename pattern.

find . -type f -name "*.md"

Use -type d to find directories instead.

6. fd — A Friendlier Find

fd provides a concise, fast alternative when installed. This finds Markdown files while including hidden paths but excluding .git.

fd --hidden --exclude .git '\.md$'

Install it on macOS with brew install fd.

7. stat — All the File Details

stat reports metadata such as size, permissions, timestamps, and inode information.

stat quick-article.md

The exact output differs between macOS and Linux, but the intent is the same.

8. file — What Is This Thing?

Extensions can lie. file inspects the contents and reports what it believes the file actually is.

file mysterious-download

9. du — What Is Using the Space?

du measures disk usage. -s summarizes instead of listing everything below the target, and -h makes the result readable.

du -sh .
du -sh ./*

10. df — How Much Disk Is Left?

Where du examines files and directories, df reports free and used space on mounted file systems. Again, -h keeps the numbers readable.

df -h

11. less — Read Without Flooding the Terminal

For a file longer than one screen, use less. Search with /text, advance with the space bar, move backward with b, and quit with q.

less application.log

12. head and tail — Inspect the Edges

These show the beginning or end of a file. -n 20 asks for twenty lines. tail -f keeps watching as new lines arrive, which is particularly handy for logs.

head -n 20 application.log
tail -n 20 application.log
tail -f application.log

Creating and Managing Files

13. mkdir — Create Directories

The useful switch here is -p: it creates missing parent directories and does not complain if the path already exists.

mkdir -p notes/terminal/examples

14. touch — Create an Empty File

If the file does not exist, touch creates it. If it does exist, touch updates its timestamps without changing the contents.

touch notes.md

15. cp — Copy

Copy a file with plain cp. Use -R to copy a directory recursively and -i to ask before overwriting something.

cp -i notes.md notes-backup.md
cp -Ri source-folder destination-folder

16. mv — Move or Rename

mv handles both jobs. I often include -i for an overwrite prompt.

mv -i draft.md published.md
mv -i published.md archive/

17. ln — Create a Link

ln -s creates a symbolic link. The first path is the existing target; the second is the new link.

ln -s ~/Code/my-project/current-config.json config.json

18. rmdir — Remove an Empty Directory

rmdir only removes empty directories. That limitation makes it useful when I want the command to refuse anything containing files.

rmdir old-empty-folder

19. rm — Remove Files Carefully

Plain rm removes files. -i asks before each removal. Recursive -R removes directories and their contents, so double-check the path before pressing Return.

rm -i unwanted.txt
rm -Ri unwanted-folder

There generally is no built-in undo. On macOS, moving something to the Trash in Finder is often a better choice when I’m not absolutely sure.

20. open — Hand It to macOS

open opens a file in its default application. Give it a directory and Finder opens there; use -a to choose an application.

open README.md
open .
open -a "Visual Studio Code" .

On Linux, the closest general equivalent is usually xdg-open.

A Few Combinations I Actually Use

Here are the quick combinations I tend to reach for most often.

# Everything here, including hidden entries, with readable details.
ls -lah

# Everything below here, recursively, in long format.
ls -laRh

# A manageable visual overview, two levels deep.
tree -a -L 2

# Find the biggest immediate entries in the current directory.
du -sh ./* | sort -h

# Jump somewhere temporarily, inspect it, and jump back.
pushd ~/Code/some-project
ls -lah
popd

That’s the lot: twenty-ish commands and a pile of switches that turn the terminal into a quick file-system navigator. The commands themselves are only half of the story. Run man ls, man find, or man followed by any of the built-in commands above to spelunk through everything else they can do.

Documentation First w/ README.md && Project Tree Build

I’m sitting here trying to get the folder structure for my project into a kind of ASCII Tree or something. I wasn’t going to manually do this, it would be insane. Especially on any decent size Enterprise Project with an endless supply of folders and nested content. I went digging to come up with a better solution. On Linux I immediately found the Tree Utility which was perfect.

Except I was on OS-X.

First option I gave a go to was to build the thing. Because I like to do things the hard way sometimes. First I needed to get the source, which is available here.

[sourcecode language=”bash”]curl -O ftp://mama.indstate.edu/linux/tree/tree-1.7.0.tgz[/sourcecode]

Once downloaded, unzip the source into a directory and find the following section for the particular operating system you want to use the utility on. The section for OS settings looked like this when I finished editing it.

[sourcecode language=”bash”]
# Uncomment options below for your particular OS:

# Uncomment for OS X:
CC=cc
CFLAGS=-O2 -Wall -fomit-frame-pointer -no-cpp-precomp
LDFLAGS=
MANDIR=/usr/share/man/man1
OBJS+=strverscmp.o
[/sourcecode]

Now get a good build of the command file.

[sourcecode language=”bash”]
./configure
make
[/sourcecode]

Now let’s get tree into the executable path.

[sourcecode language=”bash”]
sudo mkdir -p /usr/local/bin
sudo cp tree /usr/local/bin/tree
[/sourcecode]

Make sure your ~/.bash_profile is setup right, include this.

[sourcecode language=”bash”]
export PATH="/usr/local/bin:$PATH"
[/sourcecode]

Reload the shell and tree should be available as a command.

The other option which is really simple, if you don’t want to compile to code, is to just use brew to install it.

[sourcecode language=”bash”]
brew install tree
[/sourcecode]

So now you can use tree, and do cool stuff like pipe it out to a file. If you’re running this against a Node.js Project you may want to delete the node_modules directory and then just reinstall it after running the tree command.

[sourcecode language=”bash”]
tree > prof-tree.md
[/sourcecode]

Then in your README.md file you can include the folder structure in the description of the project. Here’s a sample output!

[sourcecode language=”bash”]
.
├── README.md
├── client
│   └── README.md
├── package.json
├── proj-tree.md
├── server
│   ├── boot
│   │   ├── authentication.js
│   │   ├── explorer.js
│   │   ├── rest-api.js
│   │   └── root.js
│   ├── config.json
│   ├── datasources.json
│   ├── middleware.json
│   ├── model-config.json
│   └── server.js
└── test
└── test_exists.js

4 directories, 14 files
[/sourcecode]

That’s a super easy way to offer better documentation that provides some real insight into what various parts of the project structure are actually for.