@devwithdavid/ledger 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LEDGER.md +459 -0
- package/README.md +140 -0
- package/dist/cli/commands/agents.js +457 -0
- package/dist/cli/commands/catchup.js +174 -0
- package/dist/cli/commands/clerk.js +90 -0
- package/dist/cli/commands/docs.js +29 -0
- package/dist/cli/commands/events.js +59 -0
- package/dist/cli/commands/projects.js +134 -0
- package/dist/cli/commands/roadmap.js +120 -0
- package/dist/cli/format.js +19 -0
- package/dist/cli/index.js +33 -0
- package/dist/db/client.js +48 -0
- package/dist/db/migrations/0001_init.js +72 -0
- package/dist/db/migrations/0002_project_herdr_workspace.js +13 -0
- package/dist/db/migrations/0003_agent_authorization_basis.js +18 -0
- package/dist/db/migrations/0004_roadmap_priority.js +19 -0
- package/dist/db/migrations/index.js +10 -0
- package/dist/db/migrations/types.js +1 -0
- package/dist/db/types.js +33 -0
- package/dist/lib/git.js +61 -0
- package/dist/lib/herdr.js +321 -0
- package/dist/lib/treehouse.js +23 -0
- package/dist/plugin/watcher.js +63 -0
- package/herdr-plugin.toml +16 -0
- package/package.json +33 -0
package/LEDGER.md
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# `ledger` — reference
|
|
2
|
+
|
|
3
|
+
This is the CLI surface and the safe ways to extend it — not the philosophy
|
|
4
|
+
behind the project (see `DESIGN.md` for that, and `DECISIONS.md` for why
|
|
5
|
+
specific implementation choices were made).
|
|
6
|
+
|
|
7
|
+
Everything below shells out to the `ledger` CLI (built to `dist/cli/index.js`,
|
|
8
|
+
installed as `ledger` on PATH once linked/packaged). State lives in one
|
|
9
|
+
SQLite file at `$LEDGER_HOME/ledger.db` (default `~/.ledger`, override with
|
|
10
|
+
the `LEDGER_HOME` env var). There is no daemon — every command opens the
|
|
11
|
+
DB, does its thing, and exits.
|
|
12
|
+
|
|
13
|
+
Two very different audiences touch this file. Read the section for yours.
|
|
14
|
+
|
|
15
|
+
- **The first clerk** — the orchestrator. Turns what the human asks for into
|
|
16
|
+
scoped briefs, sets up tracking for them, dispatches agents, keeps things
|
|
17
|
+
moving, and escalates to the human when something needs their judgment.
|
|
18
|
+
Should always have this whole doc loaded.
|
|
19
|
+
- **Dispatched agents** — the workers. Execute one scoped task in an
|
|
20
|
+
isolated worktree, then report back. Should need almost none of this —
|
|
21
|
+
see below for why.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## For dispatched agents
|
|
26
|
+
|
|
27
|
+
You don't need to read this section, or load this file at all — it exists
|
|
28
|
+
so a human or clerk can see the contract you're actually held to. Every
|
|
29
|
+
`ledger agent dispatch` builds your real first prompt as your task text
|
|
30
|
+
plus this contract appended automatically (`buildTaskPrompt` in
|
|
31
|
+
`src/cli/commands/agents.ts` — that function is the single source of truth;
|
|
32
|
+
what's below is a description of it, not a separate copy to keep in sync):
|
|
33
|
+
|
|
34
|
+
- **Always work on a new branch** — never commit directly to the project's
|
|
35
|
+
default branch, regardless of delivery mode.
|
|
36
|
+
- **When you're done**, what your last action looks like depends on the
|
|
37
|
+
project's `delivery_mode`:
|
|
38
|
+
- `direct-pr`: open a pull request against the default branch — using
|
|
39
|
+
whatever tooling is available for that project's remote (e.g. `tea`
|
|
40
|
+
for a Forgejo remote; ledger doesn't care which, that's your call) —
|
|
41
|
+
**do not merge it yourself, regardless of anything else you're told.**
|
|
42
|
+
PR review is a real checkpoint, not a formality to clear on your own
|
|
43
|
+
(see `DECISIONS.md` for the incident that made this explicit). Then
|
|
44
|
+
`ledger agent update <your-agent-id> --status done --outcome
|
|
45
|
+
'<pr-url>'`.
|
|
46
|
+
- `local-only`: just `ledger agent update <your-agent-id> --status done
|
|
47
|
+
--outcome '<branch-name-or-report-path>'`.
|
|
48
|
+
Your agent id was given to you in your initial prompt.
|
|
49
|
+
- **If you get stuck and need a human/clerk decision before you can
|
|
50
|
+
continue**: just state the question and stop where you are (don't spin,
|
|
51
|
+
don't guess and proceed). herdr detects an agent sitting idle mid-question
|
|
52
|
+
as `blocked` automatically — the watcher plugin picks this up with zero
|
|
53
|
+
action from you, and it surfaces to the clerk via `ledger catchup`.
|
|
54
|
+
- **Everything else is the clerk's job, not yours** — roadmap, project
|
|
55
|
+
registration, dispatching other agents. Per `DESIGN.md`'s v1 default, you
|
|
56
|
+
don't spawn peers or coordinate with other agents directly even though
|
|
57
|
+
herdr's socket API would technically let you; work your own task and
|
|
58
|
+
report through the two channels above. (See "Agent-to-agent coordination"
|
|
59
|
+
below if this default has been changed for your project.)
|
|
60
|
+
|
|
61
|
+
## For the first clerk
|
|
62
|
+
|
|
63
|
+
### Governance gates (binding on you — the clerk)
|
|
64
|
+
|
|
65
|
+
Full text and provenance for each gate: the "Governance decisions"
|
|
66
|
+
section of `DECISIONS.md` (2026-08-23, user-directed). It is
|
|
67
|
+
authoritative; this is a working summary.
|
|
68
|
+
|
|
69
|
+
**Hard gates** — the CLI enforces them mechanically. Do not work around
|
|
70
|
+
them; if a gate blocks something you want to do, that is the gate
|
|
71
|
+
working — escalate to the user.
|
|
72
|
+
|
|
73
|
+
- **C3 — survival proof before releasing.** `agent release` first proves
|
|
74
|
+
where the work in that worktree lives: `durable` (clean, and every
|
|
75
|
+
commit on the checked-out branch is on a remote) auto-returns the
|
|
76
|
+
worktree; `at-risk` (uncommitted changes and/or unpushed commits) and
|
|
77
|
+
`unprovable` (git could not verify) are *refused* and recorded as a
|
|
78
|
+
`release_refused` event carrying the proof. `--force` skips the proof —
|
|
79
|
+
it is the user's explicit authorization to discard work, never a repair
|
|
80
|
+
path. Use it only when the user has actually said so.
|
|
81
|
+
- **C6 — recorded authorization; no silent duplicates.** `agent dispatch`
|
|
82
|
+
requires `--authorization <basis>` and records it on the agent row and
|
|
83
|
+
the `dispatched` event: `user-explicit` = an in-the-moment green light;
|
|
84
|
+
`pre-authorized` = a previously granted, per-item, revocable standing
|
|
85
|
+
latitude. The value is *your* attestation — the CLI cannot know whether
|
|
86
|
+
the user really said yes, so never dispatch with a basis you would not
|
|
87
|
+
stand behind (the soft half of this gate). It also refuses to start a
|
|
88
|
+
second live (`working`/`blocked`) agent on a roadmap item that already
|
|
89
|
+
has one, unless you pass `--confirm-duplicate` — pass it only when the
|
|
90
|
+
user explicitly approved a second live agent on that item. Older
|
|
91
|
+
`idle`/`done` agents on the same item are *not* a refusal.
|
|
92
|
+
|
|
93
|
+
**Soft gates** — nothing in the CLI can check these; they bind you, not
|
|
94
|
+
the code.
|
|
95
|
+
|
|
96
|
+
- **C1 — you never act on project code directly.** Read-only over project
|
|
97
|
+
code; all code change goes through dispatched agents. Sole exception: a
|
|
98
|
+
concrete, in-the-moment, user-approved operation — executed exactly as
|
|
99
|
+
approved, never inferred or generalized, conferring no standing
|
|
100
|
+
authority.
|
|
101
|
+
- **C2 — you never merge, force-push, or close a PR without an explicit
|
|
102
|
+
user word.** One explicit word at a time, in the moment; there is no
|
|
103
|
+
standing relaxation. (Worker-side mirror: A2.)
|
|
104
|
+
- **C4 — agents never address the user directly; you are the single
|
|
105
|
+
channel.** If the user intervenes directly in a worker pane, that
|
|
106
|
+
instruction is authoritative: reconcile at the next catch-up, never
|
|
107
|
+
override or re-dispatch against it.
|
|
108
|
+
- **C5 — report outcomes faithfully.** What was observed, not intended;
|
|
109
|
+
failures stated plainly with evidence; uncertainty labeled.
|
|
110
|
+
- **C7 — observe before mutating; observation failure is not evidence.**
|
|
111
|
+
Board/agent status changes only from fresh observation (pane state,
|
|
112
|
+
process, git) — never cosmetics. When observation fails, report the
|
|
113
|
+
unknown rather than patching the record to look consistent.
|
|
114
|
+
- **C8 — orient at session start; no blind turn-end.** First act:
|
|
115
|
+
`ledger catchup` and verify your own claim (a foreign live claim is
|
|
116
|
+
reported as a conflict, never force-taken; `--force` is the
|
|
117
|
+
user-authorized path). After a dispatch, re-observe that the agent
|
|
118
|
+
actually spawned and engaged before reporting success.
|
|
119
|
+
- **C9 — you do not self-modify.** Never edit your own contract or skills
|
|
120
|
+
(this file, `DECISIONS.md`, the skill pointer) without explicit user
|
|
121
|
+
approval — a gate must not be editable by the party it binds.
|
|
122
|
+
|
|
123
|
+
**Liveness (A8, clerk side).** At catch-up, an `idle` agent with
|
|
124
|
+
unfinished work is suspect — it may have died on a usage limit. `catchup`
|
|
125
|
+
now lists every `idle` agent together with the tail of its pane's recent
|
|
126
|
+
output (see below) — that's the mechanical part: it puts what the agent
|
|
127
|
+
last showed in front of you without a separate pane read. The judgment
|
|
128
|
+
(is that tail a real question, a usage error, or nothing wrong at all —
|
|
129
|
+
and whether to answer, re-dispatch, or escalate) is still yours.
|
|
130
|
+
|
|
131
|
+
### Session start: catch up in one command
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
ledger catchup [--project <name>] [--idle-pane-lines <n>] [--json]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Returns, in one call: agents currently `blocked` (need a decision now),
|
|
138
|
+
every agent currently `idle` together with the tail of its pane's recent
|
|
139
|
+
output (A8 liveness triage — did it ask a question, hit a usage error, or
|
|
140
|
+
go quiet mid-task?), every `events` row since the last time any clerk ran
|
|
141
|
+
`catchup` (tracked in `first_clerk.last_seen`), and every roadmap item not
|
|
142
|
+
yet `done`/`dropped`, ordered by priority `high → normal → low` (ties by
|
|
143
|
+
id).
|
|
144
|
+
This is the entire "what's going on" operation from `DESIGN.md` — read this
|
|
145
|
+
instead of any prose file, every session.
|
|
146
|
+
|
|
147
|
+
`--idle-pane-lines <n>` controls how much of each idle agent's pane tail is
|
|
148
|
+
shown (default 25; `0` skips pane reads entirely — just the idle-agent list).
|
|
149
|
+
A pane read failing (pane/tab closed, herdr socket down) never fails
|
|
150
|
+
`catchup` itself: a single dead pane prints `pane unreadable: <reason>`
|
|
151
|
+
under that agent and the rest of catch-up proceeds; a herdr socket that's
|
|
152
|
+
unreachable entirely prints one note for the whole section instead of
|
|
153
|
+
repeating the same failure under every idle agent. `--json` includes the
|
|
154
|
+
same data as an `idle` array (`agent`, `pane_tail`, `pane_read_error`) plus
|
|
155
|
+
top-level `idle_pane_read_note` for the whole-socket case.
|
|
156
|
+
|
|
157
|
+
### First-clerk claiming
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
ledger clerk claim --session-id <id> --herdr-pane <id> [--force]
|
|
161
|
+
ledger clerk status
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`claim` fails if another claim exists and is under 12 hours old, unless
|
|
165
|
+
`--force` is passed. Do this once per new/resumed clerk session before
|
|
166
|
+
dispatching anything.
|
|
167
|
+
|
|
168
|
+
### Registering a project
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
ledger project add <url-or-local-path> --name <name> [--delivery-mode direct-pr|local-only]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Always clones into ledger's own `$LEDGER_HOME/projects/<name>` — never the
|
|
175
|
+
user's working checkout (see `DESIGN.md` for why: worktree metadata
|
|
176
|
+
pollution, and predictable starting state). A local path clones fast
|
|
177
|
+
(hardlinks) and picks up unpushed branches; it can never see uncommitted
|
|
178
|
+
changes — tell the user to commit first (a throwaway branch is fine) if
|
|
179
|
+
they want an agent working from something not yet committed.
|
|
180
|
+
|
|
181
|
+
**For work that doesn't exist anywhere yet** — no repo to clone, local or
|
|
182
|
+
remote (e.g. the very first agent dispatched for a brand-new idea) — use
|
|
183
|
+
`init` instead of `add`:
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
ledger project init --name <name> [--delivery-mode direct-pr|local-only]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Creates an empty git repo (one empty initial commit — just enough for
|
|
190
|
+
treehouse to have a ref to lease worktrees from) directly in
|
|
191
|
+
`$LEDGER_HOME/projects/<name>`. `repo_url` is `NULL` for these — there's no
|
|
192
|
+
origin yet. Defaults to `--delivery-mode local-only` rather than
|
|
193
|
+
`direct-pr`, since there's nowhere to open a PR against until the project
|
|
194
|
+
actually gets a remote. Don't scaffold anything beyond the empty commit
|
|
195
|
+
yourself — what the project becomes is the dispatched agent's job.
|
|
196
|
+
|
|
197
|
+
**Once a from-scratch (`init`'d) project gets a real remote** — code
|
|
198
|
+
pushed somewhere for the first time — record it:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
ledger project update <name> [--repo-url <url>] [--delivery-mode <mode>]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Adds a git `origin` remote to the local clone if it doesn't already have
|
|
205
|
+
one; never overwrites an existing remote. `repo_url` in the returned row
|
|
206
|
+
always reflects the actual git remote, not just what was requested — if
|
|
207
|
+
`origin` already existed with a different URL, the response includes a
|
|
208
|
+
`warning` rather than silently recording something git doesn't agree with.
|
|
209
|
+
|
|
210
|
+
Other project commands: `ledger project list [--json]`, `ledger project get <name>`.
|
|
211
|
+
|
|
212
|
+
### Roadmap: turning asks into briefs
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
ledger roadmap add --project <name> --title <title> [--parent <id>] [--description <text>] [--priority <high|normal|low>]
|
|
216
|
+
ledger roadmap list --project <name> [--status <status>] [--all] [--json]
|
|
217
|
+
ledger roadmap update <id> [--status <status>] [--title <title>] [--description <text>] [--priority <high|normal|low>]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Statuses: `planned | in_progress | blocked | done | dropped`. `--parent`
|
|
221
|
+
nests a sub-item under an existing roadmap item (arbitrary depth).
|
|
222
|
+
|
|
223
|
+
Priority: `high | normal | low`, default `normal` (also a column on every
|
|
224
|
+
row: JSON output and `list`'s table both show it). It is a coarse triage
|
|
225
|
+
rank for ordering the not-done queue — *order*, not readiness: readiness
|
|
226
|
+
stays with `status = blocked` + `description` (auto-unblock is a separate
|
|
227
|
+
future feature, not implied by priority). A stale priority degrades
|
|
228
|
+
gracefully: it mis-sorts the catch-up list, which is visible there; it
|
|
229
|
+
cannot silently block ready work the way a stale dependency edge could
|
|
230
|
+
(DECISIONS.md, 2026-08-23).
|
|
231
|
+
|
|
232
|
+
This is where "what the human asked for" becomes "briefs an agent can
|
|
233
|
+
actually execute without holding the whole feature in context." Default
|
|
234
|
+
behavior per `DESIGN.md`: propose a breakdown and let the human approve it,
|
|
235
|
+
rather than deciding unilaterally or always making them write it themselves
|
|
236
|
+
— unless they've told you otherwise for this project. A `roadmap` row's
|
|
237
|
+
`description` is a good place for the brief itself (acceptance criteria,
|
|
238
|
+
constraints, what's explicitly out of scope) — that's what you'll turn into
|
|
239
|
+
`--task` text at dispatch time via `--roadmap-item <id>`.
|
|
240
|
+
|
|
241
|
+
`roadmap list` excludes `done`/`dropped` by default; pass `--all` to see
|
|
242
|
+
everything.
|
|
243
|
+
|
|
244
|
+
### Dispatching an agent
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
ledger agent dispatch --project <name> --task "<description>" \
|
|
248
|
+
--authorization <user-explicit|pre-authorized> \
|
|
249
|
+
[--roadmap-item <id>] [--kind claude|pi|codex|...] \
|
|
250
|
+
[--label <short-label>] [--spawned-by <agentId>] [--wait] \
|
|
251
|
+
[--confirm-duplicate]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`--authorization` is required (gate C6): `user-explicit` = an in-the-moment
|
|
255
|
+
green light from the user; `pre-authorized` = a previously granted,
|
|
256
|
+
per-item, revocable standing latitude the clerk requested. It is recorded
|
|
257
|
+
on the agent row (`agents.authorization_basis`) and the `dispatched`
|
|
258
|
+
event, so every dispatch is auditable — the default is no longer
|
|
259
|
+
"allowed". The value is your attestation: the CLI cannot verify the
|
|
260
|
+
conversation, so pick the value that is true.
|
|
261
|
+
|
|
262
|
+
`--confirm-duplicate`: if `--roadmap-item` names an item that already has
|
|
263
|
+
a live (`working`/`blocked`) agent, the dispatch is refused without this
|
|
264
|
+
flag; pass it only when the user explicitly approved a second live agent
|
|
265
|
+
on that item (C6).
|
|
266
|
+
|
|
267
|
+
For `--kind claude`, dispatch always runs it with `--permission-mode
|
|
268
|
+
bypassPermissions` and auto-dismisses Claude Code's one-time "do you trust
|
|
269
|
+
this folder?" dialog (every treehouse worktree is, from its point of view,
|
|
270
|
+
a folder it's never seen — nobody's present in that pane to answer it, so
|
|
271
|
+
without this it would just hang forever). Both confirmed live — see
|
|
272
|
+
`DECISIONS.md`. Scoped to `claude` specifically per the user; other kinds
|
|
273
|
+
run with whatever their own default permission behavior is.
|
|
274
|
+
|
|
275
|
+
What this does, in order (see `DECISIONS.md` for why it's shaped this way):
|
|
276
|
+
|
|
277
|
+
1. `treehouse get --lease` against the project's clone → a durably-leased,
|
|
278
|
+
isolated worktree path.
|
|
279
|
+
2. **One herdr workspace per project, not per dispatch.** If the project
|
|
280
|
+
already has a live workspace (`projects.herdr_workspace`, checked with
|
|
281
|
+
`herdr workspace get` in case the user closed it since), this dispatch
|
|
282
|
+
adds a new **tab** to it (`herdr tab create --workspace <id> --cwd <that
|
|
283
|
+
path>`) — so multiple agents working the same project show up as tabs
|
|
284
|
+
in one workspace, not scattered across separate workspaces. Otherwise
|
|
285
|
+
this is the project's first dispatch (or its old workspace is gone): a
|
|
286
|
+
new workspace is created (`herdr workspace create --cwd <that path>`,
|
|
287
|
+
labeled with the *project* name) and recorded onto the project row for
|
|
288
|
+
every later dispatch to reuse.
|
|
289
|
+
3. `herdr agent start --kind <kind> --pane <pane>` → starts the coding
|
|
290
|
+
agent in that pane.
|
|
291
|
+
4. Inserts the `agents` row (worktree path + herdr workspace/tab/pane ids)
|
|
292
|
+
and a `dispatched` event — *before* sending the task, so the task prompt
|
|
293
|
+
can reference the agent's own row id.
|
|
294
|
+
5. `herdr agent prompt <pane> "<task + reporting contract>"` → delivers the
|
|
295
|
+
task (see "For dispatched agents" above for exactly what gets appended).
|
|
296
|
+
|
|
297
|
+
If workspace/tab-creation or agent-start (steps 2-3) fail, that tab (or the
|
|
298
|
+
whole workspace, only if this dispatch just created it — never the shared
|
|
299
|
+
workspace if it was reused, since other agents may be live in it) is closed
|
|
300
|
+
and the worktree lease is returned before the error surfaces — no orphaned
|
|
301
|
+
pane, no phantom `agents` row for a dispatch that never actually started.
|
|
302
|
+
If only the final prompt delivery (step 5) fails, the row is *kept* (the
|
|
303
|
+
agent process is real and running by then) with a `dispatch_prompt_failed`
|
|
304
|
+
event — investigate with `agent get`, retry the prompt by hand via
|
|
305
|
+
`herdr agent prompt`, or `agent release` to abandon it.
|
|
306
|
+
|
|
307
|
+
Scope each dispatch to a single roadmap sub-item where one exists, rather
|
|
308
|
+
than handing an agent a whole feature — that's the actual point of having
|
|
309
|
+
a roadmap (small, well-scoped context per agent).
|
|
310
|
+
|
|
311
|
+
From here, **you do nothing further** to track state — the herdr watcher
|
|
312
|
+
plugin keeps `agents.status` and `events` current automatically as that
|
|
313
|
+
pane's agent state changes.
|
|
314
|
+
|
|
315
|
+
Other agent commands:
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
ledger agent list [--status <status>] [--project <name>] [--json]
|
|
319
|
+
ledger agent get <id>
|
|
320
|
+
ledger agent update <id> [--status <status>] [--outcome <text>]
|
|
321
|
+
ledger agent release <id> [--force]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`agent release` first proves the work's survival (gate C3): `durable`
|
|
325
|
+
(worktree clean, and every commit on its checked-out branch is on a
|
|
326
|
+
remote) auto-returns the worktree to the treehouse pool and closes the
|
|
327
|
+
agent's herdr tab; `at-risk` (uncommitted changes and/or unpushed
|
|
328
|
+
commits) or `unprovable` (git could not verify) is *refused* and recorded
|
|
329
|
+
as a `release_refused` event carrying the proof — escalate to the user
|
|
330
|
+
instead. `--force` skips the proof: it is the user's explicit
|
|
331
|
+
authorization to discard work, never a repair path. It does **not** touch
|
|
332
|
+
`agents.status` — release is a worktree-lifecycle action, not a judgment
|
|
333
|
+
that the work is finished. Run it once you're done inspecting a
|
|
334
|
+
completed/abandoned agent's worktree.
|
|
335
|
+
|
|
336
|
+
### Monitoring and escalating
|
|
337
|
+
|
|
338
|
+
`ledger catchup` surfaces every `blocked` agent. For each one:
|
|
339
|
+
|
|
340
|
+
1. Read its `task_description` and recent `ledger event list --agent <id>`
|
|
341
|
+
to understand what it's blocked on.
|
|
342
|
+
2. If it's something you can resolve yourself with information you already
|
|
343
|
+
have (a clarification, a decision within scope the human already gave
|
|
344
|
+
you) — answer it directly with `herdr agent prompt <pane> "<answer>"`,
|
|
345
|
+
using the agent's `herdr_pane` from `agent get <id>`. This unblocks it;
|
|
346
|
+
the watcher will pick up the resulting state change on its own.
|
|
347
|
+
3. If it genuinely needs the human's judgment — surface it to them. Don't
|
|
348
|
+
sit on a blocked agent hoping it resolves itself; that's exactly the
|
|
349
|
+
state `ledger catchup` exists to make visible immediately instead of
|
|
350
|
+
burying it in a pane you'd otherwise have to remember to check.
|
|
351
|
+
|
|
352
|
+
`ledger catchup` also surfaces every `idle` agent, each with a tail of its
|
|
353
|
+
pane's recent output (A8). An idle agent isn't blocked — herdr didn't detect
|
|
354
|
+
it waiting on a question — but with unfinished work it's still suspect: read
|
|
355
|
+
the tail before assuming it's fine. A real usage-limit error or a stalled
|
|
356
|
+
run looks different from ordinary quiet-between-turns output; judge which
|
|
357
|
+
one you're looking at, same as you would from reading the pane directly,
|
|
358
|
+
just without the extra step of going to find the pane first.
|
|
359
|
+
|
|
360
|
+
## Safe ways to extend this
|
|
361
|
+
|
|
362
|
+
Per `DESIGN.md`'s core philosophy: before adding anything, ask *"could this
|
|
363
|
+
instead be a skill I load, or a plugin herdr invokes?"* Concretely, for
|
|
364
|
+
this project:
|
|
365
|
+
|
|
366
|
+
**Core** (belongs in this repo, under `src/`): the schema/migrations, the
|
|
367
|
+
`ledger` CLI verbs every clerk needs regardless of what project or workflow
|
|
368
|
+
it's running (`project`/`roadmap`/`agent`/`event`/`clerk`/`catchup`), and
|
|
369
|
+
the one watcher plugin that keeps `agents.status`/`events` in sync with
|
|
370
|
+
herdr. The test: is this genuinely load-bearing for *every* clerk session
|
|
371
|
+
on *every* project, not just a preference for how one workflow should work?
|
|
372
|
+
|
|
373
|
+
**Pure extension** (does not belong in this repo): anything that's really
|
|
374
|
+
"how I personally want this workflow to behave" rather than a shared
|
|
375
|
+
primitive. It reads/writes the same SQLite file or shells out to the same
|
|
376
|
+
`ledger` CLI, but lives entirely outside `src/`:
|
|
377
|
+
|
|
378
|
+
- A notification integration (Slack/X/Discord on `blocked`/`done`) → its
|
|
379
|
+
own small script polling `ledger event list --json` or `ledger agent list
|
|
380
|
+
--status blocked --json`, run however you like (cron, a herdr plugin of
|
|
381
|
+
its own). Not a new `ledger` subcommand.
|
|
382
|
+
- A different/additional herdr event hook (e.g. reacting to
|
|
383
|
+
`pane.agent_detected`) → its own separate `herdr-plugin.toml` +
|
|
384
|
+
entrypoint, `herdr plugin link`'d independently. Doesn't have to live in
|
|
385
|
+
this repo, and shouldn't be folded into `herdr-plugin.toml` here unless
|
|
386
|
+
it's something the watcher itself needs to keep `agents`/`events`
|
|
387
|
+
correct.
|
|
388
|
+
- A roadmap-breakdown heuristic, an escalation policy, "how to write a
|
|
389
|
+
good brief for this specific project" → a Claude Code skill the clerk
|
|
390
|
+
loads, or just accumulated judgment in this doc's "For the first clerk"
|
|
391
|
+
section. Not schema, not CLI.
|
|
392
|
+
- A dashboard/reporting view → a standalone script reading
|
|
393
|
+
`$LEDGER_HOME/ledger.db` directly (it's "fully inspectable with any
|
|
394
|
+
SQLite client," by design — see `DESIGN.md`). Not a `ledger` command.
|
|
395
|
+
|
|
396
|
+
If you're genuinely unsure which side something falls on: does it need to
|
|
397
|
+
exist for *any* dispatch to work correctly, or is it optional polish one
|
|
398
|
+
workflow wants? The former is core; the latter is an extension, even if
|
|
399
|
+
it's small.
|
|
400
|
+
|
|
401
|
+
**Adding a column or table** (core, when it's actually needed). Add a new
|
|
402
|
+
file under `src/db/migrations/000N_<name>.ts` exporting a `Migration` (see
|
|
403
|
+
`src/db/migrations/0001_init.ts` for the shape), and register it in
|
|
404
|
+
`src/db/migrations/index.ts`. Migrations run automatically, in order,
|
|
405
|
+
inside a transaction, tracked in `schema_migrations` — never hand-edit
|
|
406
|
+
`0001_init.ts` after it's shipped. Every new column needs a concrete reason
|
|
407
|
+
tied to something a clerk or the watcher actually does (see `DECISIONS.md`
|
|
408
|
+
for the precedent: `first_clerk.last_seen` was added exactly this way).
|
|
409
|
+
|
|
410
|
+
**Adding a CLI command** (core). Add a `register*Commands(program)`
|
|
411
|
+
function in `src/cli/commands/`, call it from `src/cli/index.ts`. Reuse
|
|
412
|
+
`getDb()` from `src/db/client.ts` and the `printJson`/`printTable` helpers
|
|
413
|
+
in `src/cli/format.ts` — don't hand-roll output formatting per command.
|
|
414
|
+
|
|
415
|
+
**Adding a new herdr event hook to the watcher** (core, only if the hook
|
|
416
|
+
is about keeping `agents`/`events` correct — otherwise it's the "separate
|
|
417
|
+
plugin" case above). Add an `[[events]]` block to `herdr-plugin.toml`
|
|
418
|
+
(`on = "<hook.name>"` — dotted, confirmed live: `workspace.created`,
|
|
419
|
+
`tab.created`, `pane.created`, `pane.agent_status_changed`,
|
|
420
|
+
`pane.agent_detected`, etc.; this is a curated subset of herdr's full
|
|
421
|
+
internal event catalog, not identical to it — the full catalog is
|
|
422
|
+
`schemas.event.$defs.EventData` from `herdr api schema --json`, but not
|
|
423
|
+
every one of those has a corresponding plugin hook name). `command = [...]`
|
|
424
|
+
and a new entrypoint under `src/plugin/`. Keep each hook a single
|
|
425
|
+
short-lived process that reads `HERDR_PLUGIN_EVENT_JSON` (delivered as
|
|
426
|
+
`{ event: "<underscored_name>", data: {...} }` — confirmed live, see
|
|
427
|
+
`src/plugin/watcher.ts` and `DECISIONS.md`), does one write, and exits —
|
|
428
|
+
never a loop, never a standing process.
|
|
429
|
+
|
|
430
|
+
**Agent-to-agent coordination.** Explicitly deferred in `DESIGN.md`. If
|
|
431
|
+
ever wanted, it's a skill/instruction set given to dispatched agents (they
|
|
432
|
+
already have herdr's own socket API available to them), recorded via
|
|
433
|
+
`agents.spawned_by` — not a schema, CLI, or watcher change.
|
|
434
|
+
|
|
435
|
+
**Notifications, dashboards, delivery gates beyond the `direct-pr` /
|
|
436
|
+
`local-only` field.** All explicit non-goals for v1 — pure extensions per
|
|
437
|
+
above if genuinely needed, don't fold them into this repo.
|
|
438
|
+
|
|
439
|
+
## Reference: schema at a glance
|
|
440
|
+
|
|
441
|
+
Full DDL lives in `src/db/migrations/0001_init.ts` (source of truth — this
|
|
442
|
+
is a summary, not a copy to keep in sync by hand):
|
|
443
|
+
|
|
444
|
+
- `projects` — one row per registered project (`local_clone_path`,
|
|
445
|
+
`default_branch`, `delivery_mode`, `herdr_workspace` — the project's
|
|
446
|
+
shared herdr workspace, NULL until its first dispatch).
|
|
447
|
+
- `roadmap` — hierarchical (`parent_id` self-reference), `status` enum
|
|
448
|
+
`planned|in_progress|blocked|done|dropped`, `priority` enum
|
|
449
|
+
`high|normal|low` (default `normal`) — triage order for the not-done
|
|
450
|
+
queue, not readiness.
|
|
451
|
+
- `agents` — one row per dispatch (`project_id`, `roadmap_item_id`,
|
|
452
|
+
`worktree_path`, `herdr_workspace`/`herdr_tab`/`herdr_pane`,
|
|
453
|
+
`coding_agent`, `authorization_basis` enum `user-explicit|pre-authorized`
|
|
454
|
+
(NULL for rows predating the C6 gate), `status` enum
|
|
455
|
+
`blocked|working|done|idle`, `outcome`, `spawned_by` self-reference).
|
|
456
|
+
- `events` — append-only, `agent_id` + `event_type` + free-form JSON
|
|
457
|
+
`payload`.
|
|
458
|
+
- `first_clerk` — single row (`id = 1`), current authority + `last_seen`
|
|
459
|
+
catch-up cursor.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# ledger
|
|
2
|
+
|
|
3
|
+
A personal agent-orchestration tool: talk to one coding-agent session (the
|
|
4
|
+
**clerk**), it dispatches work to other coding agents running in parallel,
|
|
5
|
+
each in its own isolated git worktree. Durable state — what's running,
|
|
6
|
+
what's blocked, what happened — lives in one local SQLite file, the
|
|
7
|
+
**ledger**. No daemon, no server process, nothing running when nothing is
|
|
8
|
+
happening.
|
|
9
|
+
|
|
10
|
+
Full philosophy and design rationale: [`DESIGN.md`](./DESIGN.md). This repo
|
|
11
|
+
is the implementation of it, built for one person's actual workflow, not as
|
|
12
|
+
a general product.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- [Node.js](https://nodejs.org) ≥ 20
|
|
17
|
+
- [herdr](https://herdr.dev) installed and running — the terminal
|
|
18
|
+
workspace/pane manager. `herdr status` should show a running server.
|
|
19
|
+
- [treehouse](https://github.com/kunchenguid/treehouse) installed — isolated
|
|
20
|
+
git worktree pooling. No config file required; it auto-provisions a pool
|
|
21
|
+
per repo on first use.
|
|
22
|
+
- `git`
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
git clone <this-repo> ledger # or you already have it locally
|
|
28
|
+
cd ledger
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Make the CLI available.** Either:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm link # puts `ledger` on PATH globally
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
or invoke it directly / alias it:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
node dist/cli/index.js ...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Link the watcher plugin into herdr** — this is what keeps agent status
|
|
46
|
+
current automatically as dispatched agents work, without any polling:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
herdr plugin link .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This registers `herdr-plugin.toml`, so herdr invokes `dist/plugin/watcher.js`
|
|
53
|
+
whenever a pane's detected agent state changes. It's local and reversible:
|
|
54
|
+
`herdr plugin unlink ledger` removes it. Re-run `npm run build` after any
|
|
55
|
+
change to `src/plugin/watcher.ts` — herdr always invokes whatever's
|
|
56
|
+
currently in `dist/`.
|
|
57
|
+
|
|
58
|
+
## Starting a clerk session
|
|
59
|
+
|
|
60
|
+
You don't run `ledger` commands yourself day to day — you talk to **the
|
|
61
|
+
clerk** (a Claude Code or Pi session), and it runs them on your behalf. A
|
|
62
|
+
`ledger` skill is installed at `~/.agents/skills/ledger` (symlinked into
|
|
63
|
+
both `~/.claude/skills/` and `~/.pi/agent/skills/`) so either tool can pick
|
|
64
|
+
it up — it loads only when you actually ask for ledger-related work
|
|
65
|
+
(register a project, dispatch an agent, check status, ...), not on every
|
|
66
|
+
unrelated session.
|
|
67
|
+
|
|
68
|
+
The skill itself carries no machine-specific path: it just tells the clerk
|
|
69
|
+
to run `ledger docs`, which prints `LEDGER.md` by resolving it relative to
|
|
70
|
+
wherever `ledger` is actually installed (works correctly through the
|
|
71
|
+
`npm link` symlink too — proven live, see `DECISIONS.md`). That's what
|
|
72
|
+
makes the skill portable to a fresh machine as-is: install `ledger` there
|
|
73
|
+
per this README, and the skill works with no edits.
|
|
74
|
+
|
|
75
|
+
## Quick start (what the clerk actually runs)
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# Register a project (clones into ledger's own home dir — never your
|
|
79
|
+
# working checkout; see DESIGN.md for why).
|
|
80
|
+
ledger project add /path/to/repo-or-url --name myproj
|
|
81
|
+
|
|
82
|
+
# Break a feature into scoped briefs.
|
|
83
|
+
ledger roadmap add --project myproj --title "Do the thing"
|
|
84
|
+
|
|
85
|
+
# Dispatch an agent against one brief.
|
|
86
|
+
ledger agent dispatch --project myproj --task "Implement X" --roadmap-item 1
|
|
87
|
+
|
|
88
|
+
# Session start / "what's going on" — the entire catch-up operation.
|
|
89
|
+
ledger catchup
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Run `ledger --help`, or any subcommand with `--help`, for the full flag
|
|
93
|
+
reference.
|
|
94
|
+
|
|
95
|
+
## Where state lives
|
|
96
|
+
|
|
97
|
+
One file: `$LEDGER_HOME/ledger.db` (default `~/.ledger`, override with the
|
|
98
|
+
`LEDGER_HOME` env var), plus project clones under
|
|
99
|
+
`$LEDGER_HOME/projects/<name>`. It's a plain SQLite file — inspectable with
|
|
100
|
+
any SQLite client at any time, by design. There is no daemon: every `ledger`
|
|
101
|
+
command opens the DB, does one thing, and exits. If herdr isn't running, no
|
|
102
|
+
agents can be running either, and nothing here breaks — the watcher plugin
|
|
103
|
+
simply never fires.
|
|
104
|
+
|
|
105
|
+
## Documentation map
|
|
106
|
+
|
|
107
|
+
| Doc | What's in it |
|
|
108
|
+
|---|---|
|
|
109
|
+
| [`DESIGN.md`](./DESIGN.md) | The philosophy, why this exists, and the original schema/flow brief |
|
|
110
|
+
| [`LEDGER.md`](./LEDGER.md) | The operational reference — full CLI surface, what the first clerk is responsible for vs. what a dispatched agent is told, and how to extend this safely |
|
|
111
|
+
| [`DECISIONS.md`](./DECISIONS.md) | Running log of implementation decisions and why, including things verified live against the real herdr/treehouse binaries (some of herdr's actual behavior differs from its docs — see this file before assuming a documented API shape is accurate) |
|
|
112
|
+
|
|
113
|
+
## Extending
|
|
114
|
+
|
|
115
|
+
Core is deliberately small: the schema (`src/db`), the CLI (`src/cli`), and
|
|
116
|
+
the one watcher plugin (`src/plugin`) that keeps `agents`/`events` in sync
|
|
117
|
+
with herdr. Almost everything else — notifications, alternate herdr event
|
|
118
|
+
hooks, roadmap-breakdown heuristics, dashboards — is meant to live **outside
|
|
119
|
+
this repo**, as a separate script or plugin that reads/writes the same
|
|
120
|
+
SQLite file or shells out to the same `ledger` CLI. See [`LEDGER.md` §
|
|
121
|
+
"Safe ways to extend this"](./LEDGER.md#safe-ways-to-extend-this) for the
|
|
122
|
+
concrete core-vs-extension test and worked examples, and that same doc for
|
|
123
|
+
how to add a migration, a CLI command, or a new watcher hook when something
|
|
124
|
+
genuinely does belong in core.
|
|
125
|
+
|
|
126
|
+
## Example extension
|
|
127
|
+
|
|
128
|
+
[`ledger-notify`](../ledger-notify) *(sibling repo, once built)* — a
|
|
129
|
+
desktop-notification plugin that watches for agents going `blocked` or
|
|
130
|
+
`done`, built entirely outside this repo as a worked example of the
|
|
131
|
+
extension model. See [`EXTENSION-EXAMPLE-BRIEF.md`](./EXTENSION-EXAMPLE-BRIEF.md)
|
|
132
|
+
for the implementation brief.
|
|
133
|
+
|
|
134
|
+
## Status
|
|
135
|
+
|
|
136
|
+
Personal tool, not a general product. `herdr` and `treehouse` are pre-1.0
|
|
137
|
+
with a high release cadence; some of the CLI/plugin behavior this repo
|
|
138
|
+
depends on was reverse-engineered live (their docs don't fully match
|
|
139
|
+
current behavior in places — see `DECISIONS.md`) and may need
|
|
140
|
+
re-verification after either tool upgrades.
|