Repository navigation
Releases: DnzzL/herdr-docket
Release list
v0.8.0
-
The prompt names a CLI the agent can actually run. The plugin is not on
a pane's PATH, and every run was told to close itself with
herdr-docket task done. The work got done; the closing command did not
exist; the fleet recorded the silence that followed as the agent's failure.
Seventeen runs in a fortnight, including one that had already pushed a green
pull request. The binary writing the prompt knows where it lives, so the
commands it prints are absolute when the bare name does not resolve — and
the prose still readsherdr-docket, because an agent copies the indented
lines and a sentence full of path is a sentence nobody gains from. -
A task with a run in flight cannot be started twice. The run lock was
keyed per agent (or per shared checkout), so it kept two runs off one
workspace — but afleet.yamledit re-routes the next pick from under a
live run, and two different agents hold two different keys: an unassigned
task mid-run under one default agent was picked up by another within one
tick of the default changing. Every run now also holds a flock keyed by the
task id (run-taskid-<id>.lockin the state dir), across processes — the
daemon and a manualherdr-docket run_TASK-xcannot double a task while
either one is live. A run whose process died releases the task as before:
flock dies with the process, so the In-Progress self-heal still picks work
up the next tick. -
New work a task create files lands where the daemon can claim it.
herdr-docket task createnever named a status, so the queue filed new
work into whatever the project considered its default — and where that
word was not the word this fleet maps toTo Do, every task the fleet
created (the intake meta-task, a reviewer's follow-up, a lookback fix)
sat open in a column no run would ever read. A Backlog.md queue now files
them into its own pickup status directly at creation, in the project's own
vocabulary; a queue with no column to start a task in (Basecamp) is
untouched and the CLI says where the task landed rather than implying the
fleet chose. No new flag: the fleet knows which word it wants, and a human
picking a specific column still does it where the queue lives. -
A run branches from the queue's default branch, not your working copy. A
worktree run used to fork whatever the project's main checkout had checked
out at the moment it started, so a human mid-branch silently became the
base an agent built on — observed as a pull request carrying two commits by
two authors. A run now cuts from the queue repo's own default branch
(origin/HEAD); a source block can pin another ref withworktree_base:.
The run record names the exact commit the branch is cut from (cut <sha>
inherdr-docket history), so a PR carrying anything past that is visible
from the history alone. Root-mode runs are untouched — borrowing the
checkout they stand in is their design. -
A task lands in the queue of the project you are standing in. A fleet
that grows a second queue used to break every prompt and persona written
before it: they all calltask createwithout naming one, and the CLI
refused rather than guess. It no longer has to — an agent runs inside the
checkout of the project it was given, or a worktree of it, so the working
directory already names the queue.-sstill wins, anddefault_sourcein
fleet.yamlcovers the one caller standing nowhere: a scheduled automation
filing work for somebody else. Adefault_sourcenaming no declared queue
is refused when the config loads, not at 3am. -
Work is never routed to an agent of another project. An agent works one
checkout; a task belongs to the queue it came from. When those disagreed the
run went ahead and did the work in the wrong repository — succeeding, so
nothing downstream could see it. The daemon now leaves that task alone and
says once what disagrees with what, the way it already does for an assignee
nobody answers to. The binding is derived from the agent's workdir, so it
cannot drift from the truth the run would use. -
A root-mode run opens a tab in the project's workspace instead of a
workspace beside it. A fleet of three projects was putting one sidebar
entry per run in front of you — 29 of them, of which three meant anything.
Worktree runs were already grouped: herdr derives that from the repository,
not from anything the fleet says. Root runs had no such tie and could not
get one, so they borrow the workspace the project is already open in. The
parent is found, never configured — a workspace id written into a config
file is a reference that rots the first time somebody closes it. A project
nobody has open still gets a workspace of its own: the fleet never creates
the primary it would then be borrowing. Stopping or cleaning up a borrowed
run closes its tab and leaves the workspace, the runs beside it, and your
own work in it alone. -
The review sweep stops re-reviewing a PR it has already judged. Coverage
was "no open task mentions this PR", but a review task always closes, and a
PR the reviewer refuses to merge stays open by design — so every morning the
same PR qualified again. Coverage is now a fact about the PR: a verdict at
its current head commit, which a new commit invalidates and nothing else. -
A task routed to an agent nobody answers to is told who does answer. The
daemon's refusal used to quote the ghost and offeradd agents/<name>/AGENT.md
— an instruction-shaped option one model took during the 2026-09-25 review
leg, writing a retired name back onto a task mid-run. The comment now lists
the fleet's agents and says reassign the task to one of them; creating an
agent is a human's decision and stays out of the note. Refusal still written
once per task and unknown name, and only for names no agent answers to. -
An agent is not resumed onto a checkout that is not there.
init --factoryships three agents on a placeholder workdir and prints "point its
workdir at your repo, then resume"; nothing enforced the then, so the
agent went active, the daemon routed work to it, and the run died at
provision with the board saying only that it failed.agent resumenow
refuses and names the path to edit. Pausing is untouched: stopping an agent
is always safe. -
A failed run shows a desktop notification. Runs the daemon starts while
you are elsewhere — the morning sweep working its way down the queue — used
to fail silently: the task read failed on the board, and the record in
herdr-docket historysaid so, but only if you came back and looked. The
daemon now raises Herdr's own notification (herdr notification show) once
per failed run, naming the task and the reason. Done and cancelled runs stay
quiet — they are the fleet working and a human deciding respectively. The
notification is asked for once, best-effort: a channel that cannot fire
never changes a verdict's record. -
herdr-docket logsreads the daemon log. The daemon's stderr has always
been redirected into the plugin's state dir, but the path was remembered by
whoever set it up. The tail (-n LINES, 100 by default) is now one command
away — a report of what the daemon has said so far, not a second log.
v0.7.0
-
The factory loop, installed in one command.
herdr-docket init --factory
writes all six personas into an existing fleet — never overwriting one you
already have — and appends the four schedules that drive the loop to the
automations plugin's config when it is present, pointingrepo:at the
fleet it was installed into. The three that read the queue (intake,
stall,lookback) are pointed at the fleet and run; the three that work a
repo (pm,dev,reviewer) land paused on a placeholder workdir, because
every stage the loop names has to exist — intake and lookback file topm,
the reviewer takes the dev's handoff — and none of them may run against a
repo you did not choose. Edit the workdir,herdr-docket agent resume, and
drop thedisabled: truethereview-sweepentry ships with. Without the
automations plugin the personas still land and the output says they await
schedules. The fleet's ownfleet.yamlis not touched. -
The loop is written down:
docs/factory.mdis the wiring — the cron
entries, the dials it leans on, and the refusals (the fleet never merges;
agents do, under a policy paragraph in the reviewer's persona). The loop's
three new personas join the worked examples, and the dev and reviewer
personas gain what makes their stage real: the dev babysits its PR to
green, the reviewer's merge policy decides what ships unattended. -
A run's delivery is read, not believed. A worktree-mode run's history
records the branch it was provisioned on, how many commits that branch
gained, and the pull request it shipped as — reported through the new
herdr-docket task done|fail|block --pr <url>— soherdr-docket history
answers what did this run produce without reading prose on a task. A
root-mode run claims no branch, because it has none to vouch for. -
A worktree that still holds uncommitted changes is not torn down. When a
disposable worktree endsdonewith changes nobody committed, the fleet
keeps the workspace, says so on the task, records the fact on the run, and
moves the task to the human-decides column — the agent's report and the
worktree's state disagree, and the board now says which it found. A delivery
that cannot be read is kept and called unverified; blindness is not evidence. -
A reported verdict is no longer recorded as silence. A run whose agent
judged the ticket and closed it — a reviewer refusing to merge a migration,
say — was filed with the same sentence as an agent that closed nothing:
the agent settled without reporting a verdict. On a fleet whose config
pointsfailedandblockedat one column, that was every correct refusal.
The run still ends failed, because the task did; the record now says whether
anybody needs to go and look at the agent. -
A budget counts runs, not log lines. Attaching a pull request to a run
that had already closed appended a second closing record for it, and the
budget charged both — an agent that had run four times read as5/4and was
parked with spend left. The window now collapses per run, the way the run
list always has. The line also says the window it measures:4/6 runs in 24h,
because the window is a rolling day and never reset at midnight. -
A persona that does not load is named on the board.
agent listprinted
it and the daemon logged it; the pane dropped it, so the agent vanished from
the roster and its work drew asghost?— telling you to fix an assignee
when the fault was a file you had just edited. The roster now names the file
and the reason, and the agent column saysbrokenrather thanghost?. -
The board refreshes in one pass over the history. It read a record per
row, so a refresh walked the whole log once per task — 1.4s every five
seconds on a fleet of 80 tasks, on a file the plugin never truncates. Now
30ms, and it grows with the log rather than with the log times the board. -
greaches the roster from the task detail, which is what the line under
it always said; it went to the board instead. -
An agent in root mode is told the checkout is not its workspace. The
reviewerandpmpersonas run in the project checkout because the tasks
they file are files in it; nothing said the rest of that checkout belongs to
a human. Found on a live fleet as a four-day-oldreviewer-stashholding
thirty task files — an agent had stashed a working tree to get a clean one
for its own testing and never popped it. Both personas now refuse git in the
root checkout and build their own throwaway worktree instead.
v0.6.0
- The board pane is the fleet's triage surface: what runs next, what is
running now, and the keys to act on both. A task with a run in flight is in
aRunninggroup above the phases — shown once, not twice — with the time it
has been going against the timeout it was started with, markedstalepast
it. Everything below is in the order the daemon works it, so the top of
To Dois the task the next tick picks up, which it was not before. - A row names the queue a task came from and the id that queue's own board
uses —TASK-12besidemyapp, not another truncation ofmyapp/TASK-12—
in a colour that stays with the queue. A fleet with a single queue shows no
such column. The agent column says the two ways routing can be wrong: yellow
pausedfor an agent the scheduler is skipping, redghost?for a name
nobody answers to. - Three new keys and one new view.
vreads the selected task — body,
criteria and notes, the same renderer asherdr-docket task view— ands
re-routes it to another agent.xstops the run on the selected row, which
closes its workspace and ends the taskBlockedthrough the path a cancelled
run already took — and then says which of the two it found:closed TASK-12's workspace, orits workspace is already gonewhen the record
outlived the run.gnow shows the roster with each agent busy (and on what)
or idle, andppauses or resumes the agent under the cursor: pausing writes
disabled:to itsAGENT.mdand never touches a run already in flight —x
is the key that stops one, andrstill runs a paused agent's task by hand. - An assignee written
@nameroutes to the agentname. That is how
Backlog.md prints an assignee and how its CLI accepts one, so the sigil
reaches the task file; it used to carry on into the router, where the task
went to nobody while its row looked routed. A name no agent answers to is
still shown as unknown — the sigil comes off, the name does not. aasks which queue a task belongs in when the fleet has more than one, so
creating work from the pane no longer fails on a baretask create. Nothing
in the pane closes a task with a verdict: see
ADR 0008 for what it shows
and what it deliberately refuses.
v0.5.0
- A GitHub Projects board can be the queue.
kind: githubwith the board's
owner, itsprojectnumber and therepoits issues live in: a task is a
real issue that has been put on the board,assignwrites the board's
Agentfield, andnote,done,failandblockland on the issue's
thread as they do anywhere else. The token wants theprojectscope;
herdr-docket auth githubchecks it, stores it (--token ghp_…stores a PAT
instead) and creates theAgentfield with one option per agent. It is looked
for inHERDR_DOCKET_GITHUB_TOKEN, thengh auth token, then the stored copy. - A GitHub board is read one page of 100 at a time, in the board's own
order: past that the tail waits for the next poll, and a pull request or a
draft card on the board is not the fleet's work. The poll asks for titles and
the two columns only — bodies, checklists and comments are read by
task view— so a tick costs about one of GitHub's 5,000 hourly points
rather than a hundred. - The README named
HERDR_FLEET_BASECAMP_CLIENT_IDand_SECRETfor the
Launchpad app. The names the code reads areHERDR_DOCKET_BASECAMP_CLIENT_ID
andHERDR_DOCKET_BASECAMP_CLIENT_SECRET.
v0.4.0
herdr-fleet is now herdr-docket
A bigger name than the queue deserved, a clearer one than the plugin index could hold — and herdr-fleet was already taken, so the fleet went to the wall.
This is a rename release. The binary, the repo and every command are now herdr-docket (plugin id stays dnzzl.docket). If you installed v0.3.0 or earlier, reinstall:
herdr plugin install DnzzL/herdr-docketEnv vars renamed with it: HERDR_FLEET_BASECAMP_* → HERDR_DOCKET_BASECAMP_*.
Everything else is v0.3.0's behavior, rebuilt for the new name — same queue, same agents, same board.
v0.3.0 — roles, and the queue's own words in every prompt
Two things an AGENT.md should never have had to carry.
A role, shared by every post that names it
An agent is one post — a role in one repo — so two devs on two projects meant
two copies of one method, drifting apart. role: names a file in the fleet's
roles/ dir, and a run is now assembled in three layers, widest first:
FLEET.md the whole fleet
roles/dev.md this role
agents/dev/AGENT.md this post
---
role: dev
model: opus
workdir: ~/Projects/myapp
---Any layer may be absent, a role file is plain markdown, and there is no
inheritance between roles — write roles/marketer.md and it works. A role:
naming a file that does not exist grounds that agent on purpose:
FLEET.md is optional because nobody named it, while a run assembled without
a third of its instructions looks exactly like one that had them.
herdr-fleet agent list names what failed and the rest of the fleet keeps
working.
The queue's own status words, in the prompt
Every persona retyped its project's vocabulary by hand, so renaming a column
left agents writing statuses their own CLI refuses. The fleet already owns
those words — the prompt now states them, for the queue the task came from:
work this fleet may pick up: ready-for-agent
a task that ended done: done
a run that failed: ready-for-human
Only the words the fleet writes itself. A board's triage, wontfix and
waiting-on-a-human columns are not named, for the same reason they are not in
the statuses: whitelist: the fleet never puts a task in them. A backend with
no status words at all is told so rather than handed Backlog.md's.
Two prompt lines were also false once a source could work a project's own
backlog — the task no longer lives "not in this repo", and a run may now end
without a verdict when it hands the task on.
Also
- The README documents running a post on another agent runtime (
pi,
opencode, and the twenty-odd kinds Herdr speaks), including the two traps:
model:is written through as--modelin that runtime's spelling, and
mcp_config:emits Claude Code's flag, which no other runtime has. herdr-fleet initscaffoldsroles/with a commented example.
v0.2.0 — a fleet works several projects, in their own words
A fleet could name several queues but every local one resolved to the same
directory, and the Backlog.md adapter wrote the fleet's five status words at
whatever project it was pointed at. This release makes a multi-project fleet
real.
A source works its own project
dir: names the repo — absolute, ~, or relative to the fleet dir.
statuses: names that project's own words, and is a whitelist: a status
the fleet was not given is not its business. Pointed at a real board, the old
adapter read every unrecognised status as open work — triage, wontfix,
waiting-on-a-human included — and would have put an agent on all of them.
todo, done, failed and blocked are required; in_progress is
optional (display only). No statuses: block means the fleet's own five,
exactly as before. A source with a dir: of its own is never scaffolded or
patched by init.
Each project has its own intake
default_agent is now a per-source field. An agent carries its own
workdir, so one global default sends one project's tasks into another
project's checkout. The fleet-wide default_agent remains as the fallback,
and no default anywhere still means unassigned work is left alone.
herdr-fleet list shows the routed agent rather than the written one.
herdr-fleet task assign <id> <agent>
One agent hands work to another without closing it: same task, same id, whole
history in one place. A task reassigned during a clean run is read as handed
on rather than abandoned — previously the runner closed any task left open as
Failed, which closed a handoff under the PM's feet. A run that crashed after
reassigning is still an unreported run.
work.Assigner is an optional capability: a backend that routes work another
way refuses out loud rather than accepting the call and doing nothing.
Also
- The README is organised around how much autonomy you hand over, lever by
lever, and documents writing an adapter — five methods and a conformance
suite. Contributions welcome.
v0.1.0
First release. A shared task queue worked by your coding agents: a
Backlog.md project — or Basecamp — as the queue,
AGENT.md personas as the workers, and a daemon that routes every open task
to the agent it names, one run per agent, in Herdr workspaces you can watch,
join, or close.
herdr plugin install DnzzL/herdr-fleetPrebuilt binaries for macOS (arm64, amd64) and Linux (arm64, amd64), with
checksums.txt verified during install.