| name | ledger-issues |
| description | Use when working a shared ledger issue board — picking and claiming tasks, closing with evidence, breaking cycles, and reconciling contested state after sync. |
Ledger issues
Doctrine for a shared ledger issue board: the picking loop, claims and
evidence, cycle-breaking, and the sync habit that keeps a board honest
across hosts. Every command shape here is spelled out in full in ledger quickstart; read that before your first real write. The using-ledger
skill covers the ledger's other roles — execution spines, coordination
scoreboards, checkpoints, resume-and-verify, investigation ledgers, and
the discipline that keeps any ledger trustworthy.
Issue board
For coordinating unblocked work on a shared board: create it with guarded
status/blocked-by fields and a labels reservation (ledger create --help has the declaration flags; this pattern is everything downstream
of that). First read is always ready — its envelope answers what to
pick, what to respect, and whether anything needs a person, including a
computed frontier verdict, so no agent re-derives graph logic by hand;
show --where status=open is the flat listing when you want one.
Picking loop: while frontier is work-available, claim the oldest
entry in ready, or reclaim a stale entry from attention — skip any
that's human-labeled; its needs_override is a stop sign for a picker,
not a form to fill in. Work it, close it, re-run ready — that re-run
is the loop, never polling. A non-zero totals.attention alongside
available work is a cue to flag triage, not a reason to wait for the
verdict to flip — and break any cycle in attention on sight (the
Break-a-cycle idiom below), verdict regardless, never merely flagged. A
contested entry is resolved the same on-sight way: read BOTH heads
with show --id on each of contest.ids before collapsing with
--expect <contest.expect>, adding --override where the collapse
trips the settled gate — a seed collision can hide two distinct tasks
under one key, and renaming or splitting them is a human call, never a
picker's.
When frontier is all-handled, leave — the tool has verified every
dependency chain ends at a live worker or a human, and — cycle detection
being holder-blind — that no dependency loop hides behind either. When
it's attention-needed, break cycles and reclaim non-human stale claims
yourself; report only what you genuinely cannot act on (statusless keys,
human-labeled stale claims).
Override ethics: the settled gate (needs_override on
settled/claim signals) exists so terminal states change only on
purpose. Override to CORRECT state — collapsing a contested close,
reopening genuinely wrong work, reclaiming from a dead claimant — and
say why in -m; never to decorate it (wording, titles, tidiness: the
record is immutable and the display won't change). A HUMAN label is
different in kind: it is a stop sign, not a gate — walk away, report,
and leave the override to a person. The field trial's one misjudgment
was a cosmetic override-reopen of a settled close; the durable record
made it auditable, but the write bought nothing.
A missing, empty, or broken-looking store is REPORTED, never
repaired: never run init, create, a seed script, or any filesystem
operation against the store, no matter how wrong the board looks — the
likeliest cause is your own working directory, and the next likeliest
needs a person, not a fix attempted alone. That's why every command below
carries its cd <board dir> && prefix alongside the absolute binary
path — working directory travels with the command, same as the binary
path did before it.
cd <board dir> && ~/path-to/ledger ready --ledger issues
On a human-labeled key, every guarded write below — touch-base and close
included, not just the idioms that spell it out — carries --override -m "<why>" per the standing-signal rule; that variant isn't repeated per
idiom below.
Titles: the seed's -m IS the key's title — immutable, carried by
every listing forever. Write a title ("fix the retry storm bug"), never
a status update ("creating retry task"): there is no title field to
fix it with later, and a note doesn't change the display. Field trial:
an agent who seeded with a procedural message spent four commands
discovering this, then overrode a settled close just to fuss with the
title — the title was never fixable; the override was pure cost.
-
Seed: set <key> status=open --expect none -m "<title>". With a
dependency, edges first — a statusless key is unpickable and in
neither ready nor blocked (a ready run inside the window shows it
only under attention as a half-seed: momentary, harmless):
cd <board dir> && ~/path-to/ledger set spike-probe status=open --expect none -m "spike probe: investigate retry storm" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set fix-retry blocked-by=spike-probe --expect none --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set fix-retry status=open --expect none -m "fix the retry storm bug" --as ash --ledger issues
Seed collision: the corrupting write is the one that SUCCEEDS — your
edge write landing on a stranger's edge-free key. Your own --expect none success proves the key had no prior edges, so recovery is
deterministic: clear what you wrote and re-seed under a new name (add
--override if the stranger's key turns out to be human-labeled — the
message names the collision). Never chain the two writes without
checking exit codes:
cd <board dir> && ~/path-to/ledger set cache-warm status=open --expect none -m "warm the cache on boot" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set cache-warm blocked-by=spike-probe --expect none -m "dependency edge" --as kit --ledger issues
cd <board dir> && ~/path-to/ledger set cache-warm blocked-by= --expect <your own edge event id> -m "reverting: seed collision" --as kit --ledger issues
cd <board dir> && ~/path-to/ledger set cache-warm-2 blocked-by=spike-probe --expect none -m "dependency edge" --as kit --ledger issues
cd <board dir> && ~/path-to/ledger set cache-warm-2 status=open --expect none -m "kit's actual issue, re-seeded after the cache-warm collision" --as kit --ledger issues
Seeding a pre-human-labeled key is a legitimate way to reserve
planned work — label first, then seed with --override; the one -m
is both the title and the override justification:
cd <board dir> && ~/path-to/ledger set design-review labels=human --expect none -m "reserving for jesse" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set design-review status=open --expect none -m "pick the retry API shape" --as ash --ledger issues # expect: exit 4 error needs_override
cd <board dir> && ~/path-to/ledger set design-review status=open --expect none --override -m "pick the retry API shape -- reserved for jesse: needs a human call on the retry contract" --as ash --ledger issues
-
Claim: set <key> status=in-progress --expect <ready id> -m "claiming". means someone beat you to it — re-run
and pick again. Your IS the assignee; provenance names who,
when, from where.
Break ticket schema: every cycle entry in attention carries a
machine-readable break ticket; its fields, exactly:
key — the key whose blocked-by you edit. Only this key.
drop — the dependency to remove from that field.
keep — the value the field should hold AFTER the write. "" means
"clear the field"; it is never a literal value to insert.
expect — the CAS ticket: the field's current event id, passed as
--expect <expect> on your write. If it fails, re-run ready — the
board moved.
human — true means the break touches a human-reserved key: stop
and report instead of writing.
A fresh cycle between task-parse and task-lex earns the ticket
{key: task-lex, drop: task-parse, keep: "", expect: <id>, human: false} — task-lex's edge is the younger one, so it's the key the
ticket names:
cd <board dir> && ~/path-to/ledger set task-parse status=open --expect none -m "parse task" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set task-lex status=open --expect none -m "lex task" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set task-parse blocked-by=task-lex --expect none --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set task-lex blocked-by=task-parse --expect none --as ash --ledger issues
cd <board dir> && ~/path-to/ledger ready --ledger issues
cd <board dir> && ~/path-to/ledger set task-lex blocked-by= --expect <task-lex's edge id> -m "breaking cycle per ready's break ticket: dropping task-parse" --as ash --ledger issues
-
Label edit: the same read-union-write pattern, --expect <the labels field's latest id> (--expect none on a key's first labels
write, including the human reservation's label step). labels is
unguarded, so the tool never demands this — but replace-wholesale
means two unprotected concurrent label edits silently clobber (no
error, nothing greppable), and labels carries the human
reservation; use the protected form, never drop a label a concurrent
writer just added.
cd <board dir> && ~/path-to/ledger set fix-retry labels=needs-triage --expect none -m "flagging for triage review" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger status fix-retry --field labels --ledger issues
cd <board dir> && ~/path-to/ledger set fix-retry labels=needs-triage,perf --expect <the labels field's latest id> -m "also perf-relevant" --as kit --ledger issues
-
Recovery (after discovering a clobber or duplication): a
handoff note with what happened, then the corrective guarded write
with --evidence and a message naming the mistake. Never quietly
re-fix.
cd <board dir> && ~/path-to/ledger set db-migrate status=open --expect none -m "run the pending schema migration" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger set db-migrate status=closed --evidence commit:wrong0000 --expect <the seed id> -m "done" --as ash --ledger issues
cd <board dir> && ~/path-to/ledger note -k handoff --key db-migrate -m "closed with the wrong evidence ref (copy-pasted from deploy); correcting below, not re-closing quietly" --as kit --ledger issues
cd <board dir> && ~/path-to/ledger set db-migrate status=closed --evidence commit:c9f1a02 --expect <the bad close's id> --override -m "correcting: evidence ref was copy-pasted from deploy, see handoff" --as kit --ledger issues
Claiming a key ready annotates unblocked_without_evidence: name it in
the claim message, persisting the warning into the key's own history.
Triage moment: work the attention list — it IS the sweep (stale
claims to reclaim or take over, statusless keys to finish seeding or
abandon, cycles to break by edge edit). Walk show --where status=open
for staleness of content (close with evidence / wontfix with the why
in -m / re-label via the Label-edit idiom above — protected, since
triage is exactly where label edits and edge edits run concurrently).
Sweep the chain for override events and review each — every override is
somebody deciding a standing signal didn't apply, and reviewing them is
the entire point of making them greppable: tail --raw emits each
event's override field as JSON, so grep for the quoted key — unbounded
with -n 0, since tail's own --limit default of 20 would silently
cover only the most recent events, not the whole chain:
cd <board dir> && ~/path-to/ledger tail --raw -n 0 --ledger issues | grep '"override"'
Evidence on wontfix is NOT required — evidence of a
non-decision is pasted-string theater; the honest signal is the
annotation itself. Any non-zero totals.attention is a triage cue on
its own, regardless of what frontier says.
cd <board dir> && ~/path-to/ledger show --where status=open --ledger issues
Dup defense: search titles before seeding (ready/show carry titles
for live keys; tail --raw — never the curated view — for closed ones;
rollup summaries SHOULD retain key names verbatim, advisory). Dups close
wontfix -m "dup of [[key]]" — [[key]] is a plain-text grep
convention, no rendering semantics; the docs-typo example above is one.
What no mechanism supplies: honoring what the id you fetched actually
said. --expect proves you read the state; the signal gate makes
ignoring it visible; judgment does the rest.
Waiting for others (only when told to wait): watch with the full
status vocab as --value terms — watch matches any field's value,
unscoped, so a label token that happens to equal a status word (e.g.
labels=open) causes a rare spurious wake; harmless, just re-run
ready. Every watch timeout is also a cue to re-run ready — staleness
fires no event, so a timeout is how it gets noticed.
cd <board dir> && ~/path-to/ledger watch --value open,in-progress,closed,wontfix --timeout 1 --ledger issues # expect: exit 2
Run ledger quickstart for general mechanics; ledger create --help
for the board declaration flags.
Sync
Sync and push are cross-host doctrine, not board-only — the habit
applies to every ledger you touch, coordination boards most of all.
Start of a session: ledger sync fetches and merges remote history,
never pushes. End of a session, or whenever a handoff needs to reach
someone else: ledger push. Bare push publishes every local slug;
naming slugs publishes only those — the privacy lever for a ledger
that isn't ready to be seen, since everything pushed is readable by
anyone with read access to the repo.
Clock skew is an asymmetric threat to claim staleness: board horizons
MUST exceed expected inter-host clock skew, so claims are not born
stale. That covers only one direction — a peer whose clock runs ahead
has no horizon setting that helps; clock discipline is the only
defense there.
A contested attention entry is a partition's fingerprint: two
replicas raced the same guarded field. Recovery reads BOTH heads with
show --id on each of contest.ids before collapsing anything — a
seed collision can hide two distinct tasks under one key, and the
title alone won't reveal it. Collapse with --expect <contest.expect>; where the collapse re-asserts a settled value it
trips the settled gate, so add --override and say why in -m, the
same as any other settled-outcome revision. Renaming or splitting a
seed-collided key is a human call, never a recovery agent's to make
alone.
A multi-root refusal (a grafted or foreign chain arriving via sync)
wedges that slug for the whole fleet: push is non-force and sync
refuses, so no tool operation can repair or worsen it locally. The
refusal error names the tracking ref
(refs/ledger-remote/<remote>/<slug>) so the operator can inspect the
refused chain with plain git — the fix is remote-side ref surgery, the
same class of human-run repair as a leaked secret: an admin deletes or
force-replaces the poisoned ref, and the slug stays wedged until they
do.
ledger sync
ledger push
Run ledger quickstart for mechanics.
A partition, healed — worked example
What multi-replica recovery actually looks like, compressed from a live
six-agent trial. Two replicas worked the same board through a network
partition. On one side an agent seeded task-signup and a colleague
closed it; on the other side a different agent independently seeded the
SAME key with a different title, a third closed it, and the seeder
overrode the close to reopen it. Then the network healed.
The first recovering agent ran sync (result: no-op — its side was
strictly ahead) and push. The second ran sync and got one sentinel
merge plus EIGHT contested attention entries — every key both sides
had written during the partition, including task-signup showing
open-vs-closed. For each entry it read both heads (show --id on each
of contest.ids), collapsed with --expect <contest.expect> (adding
--override where the settled gate tripped), and said which side it
kept and why in -m. Eight writes later: attention: [], frontier: all-handled, one push, and both replicas byte-identical — with every
resolution carrying a permanent contested_resolved: [<losing ids>]
record in the history. Total agent confusion across the recovery: zero.
That is the intended shape of a heal: sync, read the tickets, collapse
with their expect, push.