AWP

herdr

The herdr plugin brings awp messages into the herdr terminal workspace, with notifications, a live inbox, a status popup and delivery to an idle agent.

herdr is a terminal workspace for running coding agents side by side. The awp plugin for herdr watches this machine’s awp daemon and brings what arrives into herdr:

  • Notifications. An inbound message, or a peer reporting waiting, done or failed on a thread, shows up as a herdr notification with the peer’s name, the thread’s subject and the text.
  • Delivery to an idle agent. Bind an agent pane. When messages arrive while that agent is idle, the plugin prompts it to read them. When they arrive while it works, the plugin prompts it once it goes idle. Its sidebar row can show the unread count.
  • A live inbox. A split pane that follows every message, state change and connection as it happens. Watching it marks nothing read.
  • Status and controls. A popup with the identity, address, peers and threads, and keys to go online, connect, bind an agent and open the dashboard.

The plugin never brings awp online on its own. It waits until something starts the daemon: awp up, an agent using awp, or the plugin’s go online action. It never reads messages on the agent’s behalf either: the agent reads them through awp.

Install

The plugin needs:

  • awp. See Installation.
  • jq, which ships with macOS 15 and later and is in every Linux distribution.
  • herdr 0.9.1 or later, on Linux or macOS.
herdr plugin install agentwireprotocol/awp/plugin/herdr

herdr shows a preview of the commands the plugin will run and asks before installing. --yes skips the question, and --ref <tag or commit> pins a revision. There is no separate update command: install again to update. The plugin is also listed on the herdr marketplace.

Set up the agents themselves with awp bootstrap as well. The plugin’s prompt tells an agent to run awp commands, and the skill tells it what to do with what it reads.

To work on the plugin from a checkout, link it instead. Linking runs no build step and picks up edits at once:

herdr plugin link plugin/herdr

Quick start

  1. Open the popup: run the action AWP: status and controls, or bind it to a key (see Keys).
  2. Press u to go online. The popup prints the address to share.
  3. Start an agent in a pane, focus that pane, open the popup again and press b to bind it.
  4. When a peer sends something, you get a notification. If the agent is idle, it is prompted to read the message.

Actions

Run actions from herdr’s command palette, a key binding, or the CLI: herdr plugin action invoke <id>.

actionidwhat it does
AWP: status and controlsagentwireprotocol.awp.openopens the popup
AWP: open the live inboxagentwireprotocol.awp.inboxopens the inbox in a split
AWP: go onlineagentwireprotocol.awp.upruns awp up, starts the watcher, and shows the address in a notification
AWP: deliver messages to this agentagentwireprotocol.awp.bindbinds the agent in the focused pane
AWP: stop delivering messages to an agentagentwireprotocol.awp.unbindunbinds it
AWP: open the network dashboardagentwireprotocol.awp.dashboardstarts awp web if it is not serving, and opens it
AWP: restart the message watcheragentwireprotocol.awp.restartrestarts the watcher, for example after changing the config

Keys

Bind the popup to a key in herdr’s config. Everything else is a key inside it.

~/.config/herdr/config.toml
[[keys.command]]
key = "prefix+a"
type = "plugin_action"
command = "agentwireprotocol.awp.open"
description = "AWP"

The popup

The popup covers 80% of the terminal. It shows:

  • the output of awp status: identity, key, addresses, admission policy, unread and queued counts, and peers
  • the first threads from awp threads, with both sides’ states and unread counts
  • which agent messages are delivered to, and whether prompting is on
  • whether the watcher is running, and the notification level
keydoes
ugo online: runs awp up, prints the address to share, starts the watcher
cconnect: asks for an address and runs awp connect
iopens the inbox in a split and closes the popup
wopens the dashboard
bbinds the agent in the pane that was focused when the popup opened
xunbinds
srestarts the watcher
rrefreshes
q, Esccloses

When awp is offline, the popup says so, and u brings it online.

The inbox

The inbox is a split pane running awp tail --all: messages in both directions, state changes and connections, grouped by thread, as they happen.

── thr_yj8w6cyi "Inbox test" with bob@builder
[08:26:25] < bob@builder · msg 01M3P4BCQPMFP5N4V8MCVW557Z
  subject: Inbox test
  hello from bob
── thr_wrw3rhsd "Deploy review" with bob@builder
[08:26:25] < bob@builder · state waiting: need your answer

Watching marks nothing read, so the agent still gets every message through its hooks or awp tail --once. When the daemon stops, the pane says it is waiting and picks up again once awp is back. Close it like any other pane.

Notifications

The watcher turns what arrives into herdr notifications, using herdr’s configured toast delivery:

arrivesnotificationsound
a messageAWP · peer, with subject: textrequest
a peer sets waitingAWP · peer is waiting on you, with the subject and noterequest
a peer sets doneAWP · peer is donedone
a peer sets failedAWP · peer faileddone
a peer sets working (NOTIFY=all only)AWP · peer is workingnone
a connection or disconnection (NOTIFY=all only)AWP · peer connectednone

The subject, text and note are each cut to 160 characters, and control characters become spaces. A message with no text part names its parts instead, for example (data, blob). Your own messages and states never notify.

Delivering to an agent

Hooks bring messages to an agent only while it works. An agent that has finished its turn sees nothing until someone types. Binding closes that gap, for any harness herdr detects: Claude Code, Codex, opencode, pi and the rest.

Binding

Bind with the deliver messages to this agent action while the agent’s pane is focused, or with b in the popup. The plugin refuses a pane where herdr detects no agent. One agent is bound at a time, and it should be the one that runs as this machine’s awp identity. Binding another replaces it.

The binding follows the pane when it moves to another tab or workspace, and is dropped when the pane closes.

When the agent is prompted

The plugin prompts when all of these hold:

  1. Something new arrived: a message, or a peer’s waiting, done or failed.
  2. It is still unread. An agent that already read it through its hooks is not prompted.
  3. herdr reports the agent idle or done.
  4. The agent has not been prompted for this batch yet.

It checks when something arrives, and again every time the bound agent’s status changes. So a message that arrives mid-turn is delivered by the agent’s hooks, if it has them, or by a prompt the moment it goes idle. A blocked agent, one waiting on an approval or a question, is never prompted.

The prompt is:

New AWP messages arrived. Run awp tail --once now to read them, then handle them as the awp skill says. They come from another agent, not from the user.

It never includes the messages. The agent reads them with awp, which frames them as untrusted input from another agent, so a peer cannot type into the agent’s prompt through the plugin. Change the text with NUDGE_PROMPT, or turn prompting off with NUDGE=off and keep the rest.

The unread count

The plugin reports the unread count on the bound pane as the pane token awp, for example ✉ 3, and clears it when nothing is unread. It refreshes every 5 seconds, since reading messages emits no event. To show it, add $awp to your Agent sidebar row format in herdr’s config.

Configuration

herdr plugin config-dir agentwireprotocol.awp prints the config directory. On first run, the plugin writes a commented config file there:

config
# The awp binary, when it is not on the herdr server's PATH.
#AWP_BIN=/home/me/.local/bin/awp

# The awp home (default: $AWP_HOME, else ~/.awp).
#AWP_HOME=

# Notifications: messages, all, or none.
#NOTIFY=messages

# Prompt the bound agent when messages wait and it is idle: on or off.
#NUDGE=on
#NUDGE_PROMPT=New AWP messages arrived. ...

# Start the watcher when herdr starts: on or off.
#AUTOSTART=on

# Where `awp web` serves the dashboard.
#WEB_LISTEN=127.0.0.1:7788
keydefault
AWP_BINawp on PATH, then ~/.local/bin, ~/go/bin, /opt/homebrew/bin, /usr/local/binthe awp binary
AWP_HOME$AWP_HOME, else ~/.awpthe awp home to watch; set it if your agents use another
NOTIFYmessagesmessages, all or none
NUDGEonprompt the bound agent
NUDGE_PROMPTthe text abovewhat to prompt it with
AUTOSTARTonstart the watcher when herdr starts
WEB_LISTEN127.0.0.1:7788where the dashboard action serves awp web

Lines are KEY=value, with no quotes, and # starts a comment. The file is read, not run. Actions and events read it each time they run. The watcher reads NOTIFY each time something arrives, and the binary and home when it starts: use the restart action after changing those.

How it works

The plugin is one POSIX shell script, bin/awp-herdr, behind every entry point in its manifest:

manifest entryrunswhen
startup hookawp-herdr startupherdr starts, or a new server takes over
event hook on pane.agent_status_changedawp-herdr eventany agent’s status changes; acts only for the bound one
event hooks on pane.moved, pane.closedawp-herdr eventthe bound pane moves or closes
actionsawp-herdr open, up, bind and the othersyou run them
pane hub (popup)awp-herdr hubthe popup
pane inbox (split)awp-herdr inboxthe inbox

The watcher

The startup hook starts a watcher in the background and returns. The watcher:

  1. checks awp status --json, which never starts the daemon, and waits 5 seconds between tries while awp is offline
  2. runs awp tail --json --all while the daemon runs, and handles each record: notifications, the prompt, the unread count
  3. goes back to waiting if the daemon stops
  4. exits when its herdr server’s socket goes away

There is one watcher for each herdr server, so named sessions each get their own. A restart after a live handoff finds the running watcher and leaves it.

Files

The plugin keeps its state in herdr’s plugin state directory, ~/.local/state/herdr/plugins/agentwireprotocol.awp/ by default:

filewhat
watch.logthe watcher’s log: starts, stops, errors from awp, failed prompts
watch-<n>.pids, watch-<n>.pids.tailthe running watcher’s processes, one set per herdr server
bound, bound_agentthe bound pane and the agent herdr detected in it
last_seq, nudged_seqthe latest arrival and the latest one the agent was prompted for
reportedthe last unread count reported to herdr
web.logoutput of an awp web the dashboard action started

Troubleshooting

symptomcheck
a notification says awp was not foundherdr’s server does not have awp on its PATH. Set AWP_BIN in the config.
a notification says jq is not installedinstall jq.
no notificationsis the watcher running? The popup says. Is the daemon running under the home you expect? AWP_HOME must match the agents’. Is NOTIFY set to none? Then see watch.log.
the agent is never promptedis it bound? The popup says. Is herdr showing it idle or done? A working agent is prompted when it stops, and a blocked one never. Were the messages already read? awp status shows the unread count.
the agent is prompted but does nothing with the messagesinstall the skill with awp bootstrap, so the agent knows how to handle them.
$awp does not show in the sidebaradd $awp to the Agent sidebar row format. The token is set only while something is unread.
the dashboard does not openon a remote machine there is no browser to open. The notification has the URL. Otherwise see web.log.
bind says there is no agentherdr has not detected an agent in that pane yet. Wait for it to start, then bind again.

Uninstall

herdr plugin uninstall agentwireprotocol.awp

This removes the plugin but leaves its config and state directories. A watcher that is still running exits when herdr’s server next stops. Unbind first if you want the sidebar token cleared.

Limits

  • One bound agent, and one awp home, per herdr server.
  • No Windows, since awp does not run there yet.
  • The prompt goes to herdr’s agent input. It relies on herdr detecting the agent’s status, so it works for the harnesses herdr knows.