Skip to main content
Glama
aquaherd
by aquaherd
README.md
# goose-herdr

A **goose** integration for **[Herdr](https://herdr.dev)** — the terminal
workspace manager for AI coding agents.

The repo ships two complementary pieces:

| Piece | Direction | What it does |
|---|---|---|
| `goose_herdr` MCP extension | goose → Herdr | Exposes the `herdr` CLI as first-class goose tools: inspect workspaces/tabs/panes/agents, create layout, run commands, read output, wait for state. |
| `agent-state/` hook plugin | goose → Herdr | Reports goose's lifecycle state (`working` / `idle`) to the Herdr pane hosting the goose session, so Herdr's sidebar shows goose correctly. |

> Both directions are "goose → Herdr". Herdr itself needs no code change: it
> detects agents by foreground process and exposes a local socket + CLI.

---

## Prerequisites

- **[goose](https://goose-docs.ai/)** installed.
- **[Herdr](https://herdr.dev/)** installed and on your `PATH` (or
  `HERDR_BIN_PATH` set), with a Herdr server running.

The MCP extension works from any shell that can reach the Herdr server. The
state-reporting hook only activates when goose itself is running inside a
Herdr-managed pane (`HERDR_ENV=1`).

---

## Install the MCP extension

goose extensions are MCP servers. The package exposes a `herdr-mcp` console
script.

### Option A — config file (persistent)

Add an entry under `extensions` in `~/.config/goose/config.yaml`:

```yaml
extensions:
  herdr:
    name: Herdr
    cmd: uvx
    args: ["--from", "git+https://github.com/aquaherd/goose-herdr", "herdr-mcp"]
    enabled: true
    envs: {}
    type: stdio
    timeout: 300
```

Once the package is published to PyPI, you can use the shorter form:

```yaml
    args: ["--from", "goose-herdr", "herdr-mcp"]
```

### Option B — interactive CLI

```sh
goose configure
```

Choose **Add Extension → Command-line Extension**, then enter:

```text
uvx --from git+https://github.com/aquaherd/goose-herdr herdr-mcp
```

### Option C — one-off session (not installed)

```sh
goose session --with-extension "uvx --from git+https://github.com/aquaherd/goose-herdr herdr-mcp"
```

### Option D — local development install

```sh
git clone https://github.com/aquaherd/goose-herdr
cd goose-herdr
uv pip install -e .          # or: pip install -e .
```

Then register the extension with `cmd: herdr-mcp` (and empty `args`):

```yaml
extensions:
  herdr:
    name: Herdr
    cmd: herdr-mcp
    args: []
    enabled: true
    envs: {}
    type: stdio
    timeout: 300
```

---

## Install the state-reporting hook

Copy the `agent-state/` directory into a goose plugin location, for example:

```sh
mkdir -p ~/.agents/plugins/goose-herdr-agent-state
cp -r agent-state/plugin.json agent-state/hooks agent-state/scripts \
  ~/.agents/plugins/goose-herdr-agent-state/
chmod +x ~/.agents/plugins/goose-herdr-agent-state/scripts/report-state.sh
```

This reports `idle` on `SessionStart`/`Stop`, `working` on
`UserPromptSubmit`/`PreToolUse`, and releases the agent on `SessionEnd`. It is
a no-op unless `HERDR_ENV=1` and `HERDR_PANE_ID` are set (i.e. goose is running
in a Herdr pane).

On `SessionStart` it also forwards the session identity (`session_id` from the
hook payload) via `herdr pane report-agent-session`, so Herdr persists an
`agent_session` reference for the pane.

> `SessionStart` reports `idle` (not `working`) so a freshly started or
> restarted goose session is not shown as active in Herdr's sidebar until it
> actually begins working.
>
> **Resume caveat:** this hook reports the session *id* only (goose stores
> sessions in `sessions.db` and resumes by id via
> `goose session --resume --session-id <id>`). Herdr persists the id but will
> not relaunch the pane on a Herdr restore by itself — that would require a
> Herdr-native `goose` integration. See
> [Restore goose after a Herdr restart](#restore-goose-after-a-herdr-restart)
> for the `goose-herdr-restore` command that works around this.

---

## Restore goose after a Herdr restart

Herdr's `resume_agents_on_restore` only relaunches agents it has an official
integration for, and goose is not one of them. The hook records each goose
session's identity (pane id + session id) on `SessionStart`, and the
`goose-herdr-restore` command relaunches goose in those panes after a Herdr
restart.

```sh
goose-herdr-restore            # relaunch any goose sessions that are not running
goose-herdr-restore --dry-run  # preview without sending commands
```

Records live under `${XDG_STATE_HOME:-~/.local/state}/goose-herdr/restore/`.
The command skips panes where goose is already running, so it is safe to run
repeatedly. Relaunch uses `goose session --resume --session-id <id>`, which
restores the conversation; extensions come from your normal goose config.

For fully automatic restore, run it from a user timer (the `herdr` and `goose`
binaries must be on `PATH`):

```ini
# ~/.config/systemd/user/goose-herdr-restore.service
[Unit]
Description=Restore goose sessions into Herdr panes

[Service]
Type=oneshot
ExecStart=%h/.local/bin/goose-herdr-restore
```

```ini
# ~/.config/systemd/user/goose-herdr-restore.timer
[Unit]
Description=Restore goose sessions into Herdr panes periodically

[Timer]
OnBootSec=10s
OnUnitActiveSec=30s

[Install]
WantedBy=timers.target
```

```sh
systemctl --user daemon-reload
systemctl --user enable --now goose-herdr-restore.timer
```

---

## Tools

The extension registers `herdr_*` tools. The read-only ones are safe to call
anytime; the rest perform one Herdr action each.

| Group | Tools |
|---|---|
| Status / sessions | `herdr_status`, `herdr_session_list`, `herdr_help` |
| Workspaces | `herdr_workspace_list`, `_get`, `_create`, `_focus`, `_rename`, `_close` |
| Tabs | `herdr_tab_list`, `_get`, `_create`, `_focus`, `_rename`, `_close` |
| Panes | `herdr_pane_list`, `_current`, `_get`, `_split`, `_read`, `_run`, `_send_text`, `_send_keys`, `_wait_output`, `_rename`, `_focus`, `_close` |
| Agents | `herdr_agent_list`, `_get`, `_read`, `_prompt`, `_send_keys`, `_rename`, `_focus`, `_wait`, `_start`, `_goose_restore` |

Agent states used by Herdr: `idle`, `working`, `blocked`, `done`, `unknown`.

---

## Development

```sh
uv venv .venv
uv pip install --python .venv/bin/python -e .
.venv/bin/herdr-mcp            # starts the MCP server over stdio
.venv/bin/goose-herdr-restore  # restores goose sessions after a Herdr restart
```

The package uses the official `mcp` Python SDK (>= 2.0) and wraps the `herdr`
CLI rather than speaking the raw socket protocol.