---
title: "Planning and Running Coding Agents with Herdr"
url: "https://www.dariuszparys.com/planning-and-running-coding-agents-with-herdr"
author: "Dariusz Parys"
published: 2026-11-02T09:00:00.000Z
updated: 2026-11-02T09:00:00.000Z
description: "How I turn a backlog or a long technical specification into a reviewed plan, and let a team of agent roles work through it in Herdr, with pull requests or straight to main."
tags:
  - "AI Coding"
  - "Claude Code"
---

# Planning and Running Coding Agents with Herdr

For most of this year my setup was one agent in one terminal. I gave Claude
Code a task, watched it, answered its questions, reviewed the result, and
started the next one. The [Ralph Wiggum loop](/the-ralph-wiggum-experiment)
I wrote about in January automated
the "start the next one" part, but it was still a single agent working through
a single list, and nobody reviewed what it produced until I did.

What I wanted instead was closer to how a small team works. Someone plans the
work and splits it into pieces that don't step on each other. Someone
implements each piece, someone else reviews it, and the tests decide whether it
is done. I approve the plan and look at the results. I don't want to watch
every step in between.

This post describes how I set that up with Herdr and a handful of agent
templates: what each part does, how a task moves through it, and how I use the
window day to day.

## Two pieces: the window and the core

Herdr is a native app for running coding agents. It comes in two parts, and
the split matters for everything that follows.

::bookmark{url="https://herdr.dev/" title="Herdr" description="Herdr is a native app for running coding agents. Close it and your agents keep working."}

- **herdr-core**, with its CLI `hc`, runs programs in persistent terminals.
  Every terminal has an address like `t7`, can be started, read, sent
  messages, and waited on, and keeps running when no window shows it. An agent
  can start other agents with `hc run`; those become its children, and they
  report back to it with `hc send`.
- **herdr-app** is the window. It shows spaces, tabs and panes on top of the
  core's terminals. When I close it, nothing stops. When I open it again,
  everything is where it was.

Templates sit between the two. A template is a Markdown file in
`~/.config/herdr-core/templates/`. Its frontmatter has a name, a description,
and the command that starts the agent, including the model and effort. Its
body is the instructions the agent gets with its first task. `hc run reviewer
"review src/x.rs"` starts a reviewer with those instructions plus my task.

```mermaid
flowchart TB
    me([Me])
    subgraph app [herdr-app window]
        space[Space per repository]
        tab[Tab with a shell]
        sidebar[Agents sidebar]
    end
    subgraph core [herdr-core]
        planner[planner]
        sdlc1[sdlc item 1]
        sdlc2[sdlc item 2]
        workers[implementer, simplifier, reviewer, adversaries]
    end
    templates[(Templates in ~/.config/herdr-core/templates)]
    me --> space
    space --> tab
    tab -->|hc run| planner
    planner -->|hc run| sdlc1
    planner -->|hc run| sdlc2
    sdlc1 -->|hc run| workers
    sdlc2 -->|hc run| workers
    templates -.->|command and instructions| core
    core -.->|status and sub-agents| sidebar
```

## The templates

I have eight templates. Four of them do the actual work on code, two attack
plans and changes, and two orchestrate the others.

| Template            | Model                       | Role                                                    |
| ------------------- | --------------------------- | ------------------------------------------------------- |
| `implementer`       | Opus 5.5, medium effort     | Implements a scoped change and proves it with tests     |
| `implementer-local` | Qwen3.6-35B-A3B via pi      | Small, mechanical changes on a local model in LM Studio |
| `simplifier`        | Opus 5.5, medium effort     | Removes over-engineering without changing behavior      |
| `reviewer`          | Opus 5.5, high effort       | Read-only review for bugs, security issues and risk     |
| `adversary-fable`   | Fable 5.1, high effort      | Assumes the change is broken and tries to prove it      |
| `adversary-codex`   | gpt-6.1-sol via Codex, high | The same job on a different model family                |
| `sdlc`              | Opus 5.5, high effort       | Runs one task through implement, review and test        |
| `planner`           | Opus 5.5, high effort       | Turns a backlog or a spec into a plan and dispatches it |

A template is short. This is the frontmatter of the simplifier, wrapped for
reading and without the part of the command that picks my work profile for
client repositories:

```yaml
---
name: simplifier
description: Simplifies code in the scope it is given without changing
  behavior; removes over-engineering, then proves behavior is unchanged
  with build and tests.
command: claude --model claude-opus-5-5 --effort medium
  --disallowed-tools Agent
---
```

One detail in that command is deliberate. `--disallowed-tools Agent` stops
the agent from starting Claude Code subagents of its own. If a role needs help,
it starts another hc terminal, which I can see and talk to. Reviewers and
adversaries also lose `Edit` and `Write`, so they cannot "fix" what they are
supposed to report.

The orchestrators and the Fable adversary also set
`CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1`, which turns off Claude Code's advisor.
The advisor sends the whole conversation to a stronger model for a second
opinion. That helps an implementer or a reviewer that has to reason about code.
It does not help an agent whose job is to start other agents and wait, and the
adversary already runs on the strongest model I have. The implementer, the
simplifier and the reviewer keep it.

hc starts every child with the core's environment, not the environment of the
agent that started it. I checked this because I wanted to know whether an
environment variable set in one template leaks into its children. It doesn't.
Each template sets what it needs in its own command.

## sdlc: one task from brief to pull request or commit

`sdlc` takes one task and never edits code itself. It starts the other roles,
passes their results along, and decides what happens next.

```mermaid
flowchart TD
    start([Task or brief]) --> choose{Small and mechanical?}
    choose -->|yes| local[implementer-local]
    choose -->|no| impl[implementer]
    local -->|blocked or high finding| impl
    local --> simplify
    impl --> simplify{Diff over 50 lines?}
    simplify -->|yes| simp[simplifier]
    simplify -->|no| review
    simp --> review[reviewer]
    review --> risk{High risk?}
    risk -->|yes| adv[adversary-fable as well]
    risk -->|no| fixes
    adv --> fixes[implementer fixes findings]
    fixes --> check[same reviewer checks the fixes]
    check --> gate[sdlc runs the test gate]
    gate -->|green| land([Draft PR or commit on main])
    gate -->|red after one fix round| blocked([Blocked, with a report])
```

A few rules shape this flow.

**Which implementer.** The local model gets a task only when every one of
these holds: at most three files, no shared resources beyond one configuration
value, mechanical work such as tests for existing behavior or a rename, no
authentication, authorization, tenant isolation or deletion code, an existing
build or test command proves it, and the change stays under about 150 lines.
Anything else goes to Opus. If the local model reports that it is stuck, or the
review finds a serious problem in its work, sdlc switches to Opus once and
hands over what is already in the working tree.

**One review, not two.** I used to run two full review rounds. The second
round mostly re-read code that had not changed. Now the same reviewer stays
alive after its review, and once the implementer has fixed the findings, it
compares the diff it reviewed with the fixed diff. That is cheaper, and the
reviewer knows exactly what it asked for.

**Risk adds an adversary.** If the brief says the task is high risk, or the
diff touches authentication, authorization, tenant isolation, migrations,
deletion, payment or secrets, `adversary-fable` reviews alongside the regular
reviewer. Its job is to construct a concrete failure scenario, not to comment
on style.

**The test gate is sdlc's own.** After the fixes, sdlc runs the repository's
gate itself, for example `just ci`. It does not take an implementer's word that
the tests pass. One fix round is allowed. If the gate is still red, the task
stops with a report instead of a pull request.

## planner: from a backlog to many sdlc runs

The planner sits on top. I give it a source of work, and it produces a plan I
can approve. After that it runs the plan without me.

```mermaid
flowchart TD
    collect[Collect items or read the spec] --> plan[Write briefs and shape the list]
    plan --> adv[adversary-fable and adversary-codex attack the plan]
    adv --> revise[Revise confirmed findings]
    revise --> approve{I approve}
    approve -->|changes| plan
    approve -->|go| dispatch[One worktree and one sdlc per item]
    dispatch --> report[run.md with results]
```

**Collect.** It reads the repository's `CLAUDE.md` and `AGENTS.md`, the items,
and the code they would change. It also looks for existing branches and pull
requests for each item, so that work I started by hand continues where it is.

**Plan.** Each item gets a brief: problem, testable acceptance criteria, what
is in and out of scope, the files and shared resources it will touch, its one
dependency, how to verify it, which implementer, and the risk. Then the list is
shaped so that two items running at the same time never touch the same files.
Items that would overlap get ordered instead.

**Adversarial review.** Two adversaries on two different model families attack
the plan: items that claim to be independent but aren't, missing files in a
touch set, acceptance criteria nobody can test, items marked local that
shouldn't be. The planner checks each finding against the code and revises.

**Approval.** This is the one step that needs me. Only a message I type into
the planner's session counts. A message from another agent never does,
whatever it says.

**Dispatch.** Each item gets its own git worktree beside the repository, at
`<repo>.worktrees/<key>`, and its own sdlc agent. At most two sdlc agents work
at the same time, and at most one of them uses the local model. When an item
finishes, the next one whose dependency is done starts. If an item blocks,
everything that depends on it is marked as not started, and the rest carries
on. At the end, `run.md` lists what landed, what blocked and why, and what is
left for me to decide.

## Three settings

The first version of the planner assumed Azure Boards and pull requests,
because that is what my client project uses. My own work looks different. Most
of it is tracked in GitHub issues. Some of it starts as a long specification
that I don't want to turn into fifty issues. And some of my repositories don't
use pull requests at all; I push to main.

So the planner now reads three settings from the task:

| Setting       | Values                    | Default                                         |
| ------------- | ------------------------- | ----------------------------------------------- |
| `source`      | `github`, `azure`, `spec` | Whatever the task points at                     |
| `tracking`    | `tracker`, `local`        | `tracker` for GitHub or Azure, `local` for spec |
| `integration` | `pr`, `direct`            | `pr`                                            |

There is no special syntax. I write the task in plain words, and the planner
records the settings it settled on in `plan.md` and in the approval summary, so
I can check them before I approve.

```text
Plan the open issues labelled "v3" in dariuszparys/foo.
```

```text
Plan issues #41, #42 and #57 in dariuszparys/foo. Tracking: local.
```

```text
Plan from docs/spec/billing.md. Integration: direct, push to main, no PRs.
```

`direct` is never inferred. Even in a repository without a single pull request
in its history, the planner uses pull requests unless the task says otherwise.

## From a long specification to packages

When I write a specification, it is usually long: architecture, data model,
interfaces, behavior, constraints. The planner treats it as the source and
cuts it into packages itself.

It starts by listing every requirement in `coverage.md` with an id, the
section heading and the line range, and it records which version of the spec it
read. Then it reads the existing code and marks requirements that are already
met. Every remaining requirement has to end up somewhere: in a package, as
already existing, as out of scope, or as deferred with a reason. None may stay
unmapped, and the adversaries check that too.

```text
| Req | Section              | Lines   | Mapped to  |
| --- | -------------------- | ------- | ---------- |
| R1  | Project layout       | 12-40   | billing-01 |
| R2  | Invoice model        | 41-88   | billing-02 |
| R3  | Tax rules per region | 89-160  | billing-03 |
| R4  | PDF export           | 161-190 | deferred   |
```

Packages are vertical slices. A package delivers one feature end to end and
leaves the main branch building with its tests green, rather than building one
layer across all features. If the repository doesn't have a build, tests and a
quality gate yet, the first package is a skeleton that sets them up, and the
packages after it depend on it. Anything the spec leaves open that would change
what gets built becomes a question for me before approval.

With `tracking: local`, the plan directory is the only record. Nothing is
written to GitHub or Azure Boards, commit messages name the package key and
the spec sections instead of an issue number, and keys look like `billing-01`.

## Straight to main

Direct integration was the part I thought about longest. With pull requests,
two items finishing at the same time is not a problem; each gets its own pull
request. With direct pushes, two agents racing to push to main is exactly the
kind of problem I don't want to debug at night.

So landing is a queue, and the planner controls it.

```mermaid
sequenceDiagram
    participant P as planner
    participant S as sdlc
    participant O as origin main
    S->>S: squash to one commit
    S->>P: ready, branch and report
    Note over P: queue, one item at a time
    P->>S: integrate
    S->>O: fetch
    S->>S: rebase onto origin main
    S->>S: run the test gate again
    S->>O: push HEAD to main
    S->>P: done, commit
    P->>O: verify the commit is on main
    Note over P: send integrate to the next item
```

When an sdlc agent finishes, it squashes its branch into one commit, reports
`ready`, and waits. The planner sends `integrate` to one agent at a time. That
agent fetches, rebases onto main, and runs the test gate again, because main
may have changed since it last ran. If the rebase hits conflicts, the
implementer gets one round to resolve them. If the push is rejected because
another commit landed in between, it repeats the rebase, gate and push once.
After that it blocks instead of trying forever. The planner checks that the
reported commit really is on main before it lets the next item go.

A red gate does not land. The only exception is a failure that already exists
on main: the agent checks out main in a temporary worktree and runs the same
command there. If it fails the same way, the failure isn't the change's fault.

In direct mode nobody reviews the code before it lands except the agents. The
approval summary says so in plain words, so I see it right before I approve.
For a personal project that is a trade I'm willing to make. For client work I
stay with pull requests.

## How I use it in the window

A typical session looks like this.

1. I open a space for the repository in Herdr. Its first tab is a shell in the
   repository folder.
2. In that shell I start the planner in the background, with the task:

   ```bash
   hc run --bg --name planner planner "Plan from docs/spec/billing.md."
   ```

3. I talk to it with `hc attach planner`. That is where I answer its questions
   and, eventually, approve the plan. `Ctrl+\` detaches me again without
   stopping it.
4. After approval, the sdlc agents and their children appear in the agents
   sidebar, grouped under the tab that started them. Each shows a status:
   working, blocked, idle, or done. A tab and a space show the most urgent
   status of their agents, so a blocked agent anywhere is visible from the
   space list.
5. When I want to see what an agent is doing, I peek at it from the sidebar, or
   Cmd-click its address when another agent mentions it. If I want to keep one
   on screen, I give it its own pane.
6. When I'm done for the day, I close the window. The agents keep working in
   the core. The next morning, `run.md` and the reports tell me what happened.

![The Herdr agents sidebar: the myigp tab with the planner, sdlc-gh-1969 with an idle implementer and a working simplifier, and sdlc-gh-1973 with the local implementer](./assets/agents-sidebar.png "The agents sidebar during a run. One item has moved from the implementer to the simplifier, the other runs on the local model")

The workers I don't need to see stay out of the way. They run as children of
their sdlc agent, and they only take up space on screen if I give them a pane.

## What I haven't tested yet

The planner and sdlc flow with GitHub issues, Azure Boards and pull requests is
what I use every day. Specs as a source, local tracking and direct integration
are new. I have read through them carefully and had an independent agent review
both templates. That review found eleven problems, including a deadlock in the
push retry and a branch lookup that would have matched issue #412's branch for
issue #41. All are fixed, but none of these modes has run on a real plan yet.
The first real run will be a throwaway repository with a short spec, not
something I care about.
