---
name: work-beads
description: Work the project's Beads task queue end-to-end - claim a bead, build in an isolated worktree, verify, review, merge, close, report. Use when the user says "/work-beads", "/work-beads 5", "work the next N beads", "work on <bead-id>", "pick up some open tasks", or asks to work the backlog. Applies to every coding agent (Claude Code, Codex, OpenCode, Antigravity, Cursor, Grok).
---

<!-- lean:start -->
# Work Beads

Work the Beads queue with the ship loop. Multiple agents run this concurrently
with no memory of each other — the loop is self-contained on purpose; do not
skip steps.

**The loop contract (memorize this):**

```
set identity once, then repeat up to N times (one bead in flight per agent):
  refresh view -> select ONE bead -> claim atomically, then validate (lost the
  claim? next candidate; dup/conflict? cooldown-release + next) -> worktree
  -> build (draft PR on first push; renew lease on each push)
  -> verify (make check-affected) -> review (mmr, 3-round cap)
  -> enqueue (make mq-enqueue) and move on -> daemon lands + closes the bead
batch end (budget spent, queue drained, or P0/blocker): report in the slots
```

**The agent's finish line is a green gate + passing review + the PR ENQUEUED** —
the merge-queue daemon batches, lands, and closes the bead; do not wait for the
merge or `bd close` an enqueued bead yourself. (No merge queue installed? Land
via the serialized merge slot instead — same finish line, you just do the
merge.) Standing authorization: run the whole loop without asking permission. Do
not end your turn after opening a draft PR with a list of "next steps" — that is
the #1 observed agent failure. The only mid-loop stops: a verified,
still-reproducing P0, or a blocker you can name.

Invocation: `/work-beads` (1 bead) · `/work-beads N` (up to N beads, selected
**one at a time at claim time** — N is a budget, not a reservation; never
pre-pick a list) · `/work-beads N <label>` (same, scoped to a label) ·
`/work-beads <id> [<id>...]` (explicit IDs, worked in dependency order).
<!-- lean:end -->

## Step 0 — Orient (once per batch, read-only, from the primary checkout)

The primary checkout is the first entry of `git worktree list`. Run:

```bash
bd ready && bd stats
gh pr list --state open        # open + draft PRs = live registry of what others build
git worktree list
make doctor                    # wedged home base? make doctor-fix (unattended-safe)
```

**Identity — T1-ACTOR (a HARD, blocking prerequisite, not an enhancement):**
the atomic claim is a compare-and-set *keyed on the Beads actor*, and a
same-actor claim is idempotent (exit 0). Two agents sharing one identity (the
default `git user.name`) therefore BOTH "win" the same bead — the claim gives
**zero** collision protection. Establish a distinct actor BEFORE any claim:
- Already have `BEADS_ACTOR` exported in THIS shell (the multi-agent prompt set
  it, or you sourced a worktree `.agent-env` on resume)? Keep it.
- Otherwise, in THIS shell **now, before Step 1**:
  `export BEADS_ACTOR="$(scripts/agent-name.sh)"` — the generator draws a
  memorable `agent-<adjective>-<noun>-<NN>` name (e.g. `agent-turbo-walrus-07`)
  with real entropy and collision-checks it against live assignees, worktree
  actors, and `agent/*` branches. Prefer it over inventing a name yourself:
  self-picked "distinctive" names converge across concurrent agents, and two
  agents on one name get zero claim protection. No generator installed? Invent
  a distinctive name (e.g. `agent-cobalt-fox`) — or pass `--actor <name>` on
  every `bd` write. The first claim runs from the primary checkout, BEFORE any
  worktree exists, so the actor must live in your current shell; a
  not-yet-created worktree cannot supply it. The name is also your public
  ownership label — it shows up in `bd list` assignees, the reaper report,
  branch names, and git blame — so keep ONE name per session.
- Cannot get an actor distinct from the shared human identity? You MUST NOT trust
  `--claim` for safety: **fail loud** — refuse the claim path, or fall back to a
  plain non-atomic in-progress write AND record in the batch report that
  collision protection is DISABLED. Never degrade silently.

Unique across concurrent agents = mutual exclusion; stable within your session =
your resume path works. (Merge-slot commands are the exception — a separately
minted holder value; see 2.7.)

> **[HOST] `.agent-env` persists the actor for the worktree, not the first claim.**
> The generated `scripts/setup-agent-worktree.sh` writes `BEADS_ACTOR=agent-<name>`
> into a worktree-local `.agent-env` so `bd` writes made FROM that worktree (and a
> resumed session) are attributable — but a child script cannot export into your
> shell, so you must `source .agent-env` yourself, and it does NOT set the actor
> for the pre-worktree claim above (that is the in-shell `export` you just did).
> Any `bd` wrapper the project ships MUST preserve `BEADS_ACTOR` (env is inherited
> across `cd`, so a wrapper only has to avoid overriding it).

**Stale-claim orient (surface + self-resume):** run the reaper in **report
mode** and list your own live claims:

```bash
scripts/reap-stale-claims.sh                              # REPORT ONLY — never releases
scripts/reap-lapsed-defers.sh                             # REPORT ONLY — lapsed cooldowns rotting out of bd ready (skip if absent)
bd list --status in_progress --assignee "$BEADS_ACTOR"   # your own crashed-session claims to resume
```

The reaper report names beads whose claim looks abandoned (lease lapsed, or —
absent a lease — stale with no open/draft PR). It NEVER mutates in report mode;
its `--apply` release is gated (see the red flags). Resume your OWN stranded
claims; for another agent's, leave it in the report — releasing a claim is an
`--apply`/operator decision, not a manual `bd` edit. (Missing script? It ships
with `scaffold agent-ops install`; feature-detect and skip if absent.)

Version gate: `bd version` must be **≥ 1.1.0** (the `bd dolt` durability
commands below require it). Older? Stop and report: upgrade with
`brew upgrade beads` or the project's equivalent — never work around the gate.

**Database safety (binding for every step):** never run `bd bootstrap`,
destructive `bd init` (`--reinit-local`/`--discard-remote`/`--destroy-token`;
legacy `--force`), or any reset against a populated `.beads/` — bootstrap
replaces local state
with the often-stale remote and silently drops unpushed beads (fresh clones
only). Before any deliberate reset, and before deleting a checkout with local
beads: `bd stats && bd dolt commit && bd dolt push`, then `make beads-snapshot`.
Drive the database only through `bd` subcommands — never a standalone `dolt`
CLI. Full runbook: docs/beads-workflow.md ("Durability & the bootstrap trap").

If `bd` or the agent-ops scripts are missing, stop and instruct:
`scaffold agent-ops install` (scripts) / see docs/beads-workflow.md (tracker).

## Step 1 — Select ONE bead (repeat before every claim)

Selection happens per bead, at claim time — never pre-select a batch. The
queue moves while you work: at high parallelism (a 12-agent fleet is
normal), any bead you "reserved" in your head at batch start will be gone
by the time you reach it. Refresh the cheap view before each selection:

```bash
bd ready --unassigned           # the claimable queue as of NOW (-u trims
                                #   open-but-assigned beads that refuse claims)
gh pr list --state open         # what others are building NOW
```

Ranking, strict order over the WHOLE ready queue: (1) priority P0 > P1 > P2 >
P3; (2) beads labeled with a `critical_labels` entry from
`.scaffold/agent-ops.yaml`, if any; (3) work that unblocks other beads.
**Capability fit is a within-tier TIE-BREAKER only — applied AFTER the priority
sort to separate otherwise-equal candidates, NEVER a pre-filter.** Ranking always
sees the whole queue, so an out-of-slice P0 is always taken over an in-slice P2.
Do NOT run `bd ready -l <your-slice>` first with a fallback-when-empty: that is
exactly the pre-filter that wrongly picks an in-slice P2 over an out-of-slice P0
(§6.2). (The capability→label mapping is project-specific; a project may ship an
example in `.scaffold/agent-ops.yaml` — treat an empty/absent mapping as "no
tie-breaker.")

Maintain a per-pass SKIP-SET of bead IDs you have already lost-raced or
cooldown-released this invocation, and skip them when you re-rank.

**`Owner:` is NOT an assignment — never skip or park a bead because of it.** In
bd, `Owner` is the immutable identity of whoever CREATED the bead, so it goes on
naming a long-departed agent forever. Only `Assignee` + `in_progress` means
someone holds it, and the atomic claim at 2.1 is what detects that. Beware that
some bd builds have omitted the assignee field from `bd list --json` output —
if a listing shows no assignee, confirm with `bd show <id>` (which does print
it) before concluding a bead is unheld; `bd ready --unassigned` and
`bd list --assignee <name>` are the reliable holder queries.

Cheap pre-claim exclusions (knowable from the queue view — skip before claiming):
- the merge-slot infrastructure bead: the project's `<prefix>-merge-slot` bead
  (or anything labeled `gt:slot`) is the merge LOCK, not work — it sits open at
  P0 whenever the slot is free, and an unfiltered claim would hold the global
  merge lock and block every other agent. Exclude it (`--exclude-label gt:slot`).
- a bead already `in_progress` under another agent (`bd ready --unassigned`
  trims open-but-assigned beads; a lingering assigned one just refuses your claim
  at 2.1 — normal traffic, not an error).

The expensive gates — duplicate-work scan, open-PR-surface conflict, and
epic-sibling re-poll — are **validation gates that run AFTER the claim** (Step
2.1): claim first so the bead is invisible to peers while you evaluate it.

Queue drained (or no candidate survives) before the budget is spent? The batch
ends early — go to Step 3 and report `queue drained after <k> of N`.

For explicit-ID invocations: topologically sort the listed IDs by dependency
(blockers first); stop and report if they form a cycle. Before claiming each
ID at its turn (Step 2.1), re-verify its blockers are all closed
(`bd show <id>`): an ID already claimed by another agent is skipped and
reported with its holder, and every listed ID that depends on a skipped or
still-open blocker is skipped too (report as `blocked by <id>`) — never
start downstream work whose prerequisite isn't done.

## Step 2 — Per-bead loop

**2.1 Claim first, then validate (atomic; from the primary checkout).** The bead
leaves `bd ready` the instant you claim it, so claim FIRST and evaluate it while
it is invisible to peers — this shrinks the collision window to zero.

a. **Claim** the ranked candidate: `bd update <id> --claim` — one atomic
   round-trip that sets assignee + `in_progress`, and fails (exit 1,
   `already claimed by <actor>`) if anyone else holds it. Never claim by editing
   the status field — a plain write cannot detect a concurrent claimant.
   - **exit 1 = LOST RACE** (normal traffic at high parallelism, not an error):
     add the ID to the SKIP-SET and take the next candidate. Do **not** retry it
     and do **not** defer it — it is another agent's LIVE claim; deferring would
     sabotage them.
   - **exit 0 = you hold it.** Immediately stamp a lease (§6.1) so a crash frees
     it: `bd update <id> --set-metadata lease_until=<now+TTL>` (default TTL 4h —
     compute the stamp as `date -u +%Y-%m-%dT%H:%M:%SZ` plus your TTL: UTC, whole
     seconds, `...Z`, the exact form the reaper parses; do NOT use `--defer` for
     the lease, which would only HIDE the bead instead of releasing it on expiry).
     Then run the validation gates in (b). If the project has build observability (a
     `.scaffold/` directory + the `scaffold` CLI), also
     `scaffold observe event claim --task <id>` — feature-detect, skip silently.

b. **Validation gates** (they read shared state, so they run AFTER the claim):
   - **stale-park re-check — run this FIRST; it is cheap and usually clears the
     bead.** If the bead carries a park/cooldown/`BLOCKED` note, RESOLVE every
     artifact that note names before you honor it: `gh pr view <n> --json state`
     for each cited PR, `bd show <id>` for each cited bead, `git worktree list`
     plus `gh pr list` for a cited peer worktree or branch. A park note is
     evidence about the moment it was written, not a standing verdict — once
     every cited PR is MERGED/CLOSED and every cited bead is closed, the park is
     STALE: append a dated unpark note recording what you verified, and proceed.
     **Never re-park a bead by restating an earlier note you did not
     re-verify** — that ratchet is what silently freezes a backlog, because each
     pass adds a "recheck: still parked" line that reads as fresh evidence to the
     next agent. A merged PR is not a conflict; a closed bead is not a blocker;
     a branch left behind by a squash-merge is not live work.
   - duplicate-work scan: `scripts/setup-agent-worktree.sh --preflight-only --task "<bead title>"`
   - open-PR-surface conflict: does any open/draft PR touch the same module,
     migration sequence, or shared single-writer code? (docs/git-workflow.md)
   - **epic-sibling re-poll (§6.3 — Window C / semantic-dup defense):** if the
     bead's parent/epic is under active work, re-poll `gh pr list` (open AND
     draft) for a PR referencing ANY sibling under the same parent (`bd dep tree`
     / `bd children <parent>`); a sibling PR on the same surface is a conflict.
     Re-poll again right before each rebase — siblings can appear mid-flight.

c. **On a gate REJECT** (you hold it, but it is a dup/conflict — a PERSISTENT
   condition that would reject the next agent too), cooldown-release in ONE
   command so the whole fleet backs off (prevents a claim→reject→release
   busy-loop).

   **Two hard rules before you write the release:**

   1. **Use `bd note` / `--append-notes`, NEVER `bd update --notes`.** `--notes`
      REPLACES the field — one batch release using it has destroyed the
      accumulated investigation history on 15 beads in a single command
      (recoverable only via `bd history <id> --json`). Notes are append-only in
      practice; treat `--notes` as reserved for a bead you just created.
   2. **The defer reason MUST name something a later agent can re-resolve** — a
      PR number, a bead id, a branch, a file path, or a command that fails.
      Write the re-check condition explicitly ("resume when #1306 merges"). A
      reason that cannot be checked cannot be cleared, so the bead never comes
      back. **Banned as standalone reasons:** "not autonomously shippable this
      session", "multi-session product/surface", "soft-park", "formalized out
      of ready". Those describe YOUR capacity right now, not the bead's state.
      If the bead is genuinely too big, **split it** (children under
      `--parent`); if it needs a human decision, say which decision and who
      owns it.

   Never mass-defer to drain `bd ready`. An empty queue is not the goal; a
   queue whose every entry has a checkable blocker is. If you are deferring
   more beads than you worked, stop and report instead.

   Then append a dated cooldown-release note naming the blocker, compute an
   ABSOLUTE UTC instant, and release:

   ```bash
   bd note <id> "cooldown-release ($BEADS_ACTOR): <what rejected it — PR/bead/surface>"
   until_ts="$(date -u -v+1H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '+1 hour' +%Y-%m-%dT%H:%M:%SZ)"
   bd update <id> --assignee "" --defer "$until_ts" --unset-metadata lease_until
   ```

   That clears ownership + the lease AND leaves the bead `deferred` (out of
   `bd ready`). **bd itself NEVER wakes it** (through bd 1.1.2 —
   gastownhall/beads#5289): restore is the shipped sweeper —
   `make prune-merged` auto-applies `scripts/reap-lapsed-defers.sh` after every
   merge sweep, and report mode runs at orient — or a manual `bd undefer`. Do
   NOT add `--status open` to the release — it cancels the defer you just set
   (that cancellation is exactly what the sweeper's wake does later, on
   purpose). Do NOT pass a RELATIVE
   offset (`+1h`): bd (≤1.1.2) serializes it as local wall-clock stamped `Z`
   (gastownhall/beads#5233), so west of UTC the cooldown lands in the PAST (and
   `+30m` reads as 30 MONTHS). Add the ID to the SKIP-SET and take the next
   candidate.

   **Escalate instead of cycling:** if the bead ALREADY carries 2+ prior
   cooldown-release notes for the same cause, the condition is not going to
   clear on its own — do NOT re-defer. Append a `Wait:` note naming exactly
   what must change (`Wait: resolve duplicate against <id> — human triage`);
   the sweeper holds `Wait:` beads, so it stays parked deliberately. Report it
   in the Step 3 slots for human triage.

d. **All gates pass → this is your bead.** Go to worktree setup (2.2).

**Generic no-ranking path (§4.2):** for a bare `/work-beads` (or a materialized
plan queue via `--has-metadata-key plan_task_id`, or a label scope) you MAY
substitute the one-shot `bd ready --claim [filters] --json` for the
rank-then-claim of (a) — add `--exclude-label gt:slot`. This changes only HOW the
candidate is chosen; it does NOT skip validation: the claimed bead still stamps
the (a) lease, runs the (b) gates, and on reject follows the SAME single-command
cooldown-release in (c). It still requires a distinct `BEADS_ACTOR`.

**2.2 Worktree:** `scripts/setup-agent-worktree.sh <name> --install --task "<bead title>" --bead <id>`
with `<name>` = your existing actor minus any `agent-` prefix
(`${BEADS_ACTOR#agent-}` — never a fresh `agent-name.sh` call here, which would
mint a DIFFERENT name and desync the worktree from your claims), then
`cd .worktrees/<name>`. The setup script persists your exported `BEADS_ACTOR`
into the worktree's `.agent-env` VERBATIM (only deriving `agent-<name>` when
none is exported), so resume and heartbeats always run as the same actor that
made the claim. The `--install` flag runs the
configured worktree setup commands (dependency installs) — omitting it is a
known `make check` breaker, because a plain invocation creates the worktree but
installs nothing. `--bead <id>` puts the bead id on the branch (below); an
older installed script without the flag errors on it — then drop the flag and
keep the rest of the convention. Need a live stack? `make staging-up` **from
the worktree** (never the primary).

**2.3 Build:** use the Superpowers discipline if available (brainstorm → plan →
TDD); otherwise write the failing test first. Commit and push frequently on
your branch. **Open a draft PR on the first push — the draft is the visible
claim.** Bead traceability (every surface a human scans carries the id):
- **Branch** = `agent/<name>/<bead-id>` (what `--bead` created): open branches
  read as the roster of in-flight beads.
- **Commit subjects** keep the normal Conventional-Commits form and append the
  bead id as a trailing tag — `type(scope): subject (<bead-id>)`
  (e.g. `feat(api): add webhook rate limiter (bd-a3f8)`). The type stays first,
  so commitlint/semantic-release work unchanged.
- **PR title** carries the same trailing `(<bead-id>)` — the squash-merge
  subject comes from the PR title, so this is what makes bead ids visible in
  `git log --oneline` on main.
- **PR body** carries `Closes <id>` — the CANONICAL machine-readable mapping
  (the stale-claim reaper and duplicate-work gates parse it); the title/branch
  copies are redundant human-first surfaces, never a substitute.

**Lease heartbeat + lost-claim check (§6.1, §5.2):** on each push, first re-read
your bead, then renew its lease. This is both the liveness signal AND your
self-heal against a wrongful reap:
1. **Re-read:** `bd show <id> --json`. If the assignee is no longer your
   `BEADS_ACTOR` (or the lease was cleared), your claim was reaped/reassigned out
   from under you — try to re-claim `bd update <id> --claim`: exit 0 = you have it
   back, keep going; **exit 1 = another agent now holds it, so STOP and report**
   (never double-work a bead someone else owns).
2. **Renew:** `bd update <id> --set-metadata lease_until=<now+TTL>`, computing the
   stamp as `date -u +%Y-%m-%dT%H:%M:%SZ` **plus your TTL** (default 4h) — always
   UTC, whole seconds, `...Z` (the exact form the reaper parses; avoid fractional
   seconds or numeric offsets). A renewed lease stays far beyond the reaper's grace
   margin, so a live agent is never evaluated as expired; a crashed agent stops
   renewing and its lease lapses, which frees the bead.

A long build with no pushes could let the lease lapse — renew explicitly in 2.6
before a long-running gate. *([HOST] a project may wire the renew into a post-push
hook; the cadence is the host's to bind.)*

**2.4 Docs travel with the PR:** resolve the bead's `docs:` tail and update
every stale doc in this same PR. Check the project-invariants section of
AGENTS.md (if the project defines one) before shipping.

**2.5 Defer = bead, immediately.** Anything you decide not to do now:

```bash
bd create "<imperative title>" -t task -p 2 --deps discovered-from:<id> \
  -d "<what, why, where (file/function)>; docs: <paths or none>"
```

A TODO comment, PR note, or mental note is NOT tracking.

**2.6 Verify yourself:** personally watched — a subagent's or reviewer's claim
doesn't count. On a merge-throughput project, `make check-affected` green on the
branch HEAD is the gate (the post-merge full suite is the net for anything it
skips). On a project WITHOUT the merge queue there is NO post-merge net, so the
gate is the full `make check`. Either way, run the full `make check` when you
touched gate config, shared test utils, env files, or migrations (the
force-full list in docs/tdd-standards.md) — and whenever in doubt. Docker contention (testcontainer timeouts, DockerException)
is not a code defect: `make docker-doctor` → `make tc-reap && make
staging-prune` → re-run. Never enqueue on a red gate. Never `docker system
prune`. Long local test loops run at reduced priority so the merge lane stays
fast on a saturated machine: `taskpolicy -c utility make check-affected`
(macOS; skip the wrapper where `taskpolicy` is absent).

**2.7 Review and merge:** `mmr review --pr <N> --sync --format json`.
- Check the diff is uncontaminated first: `gh pr diff <N> --name-only` shows
  only your intended surface.
- Surface channel auth failures to the user with recovery commands; never
  silently skip a channel.
- Round budget: round 1 fixes every real finding; round 2+ fixes only P0/P1
  and files beads for P2/P3. **Hard cap: 3 rounds — then complete the
  degraded-pass merge yourself**: file a bead per unresolved finding, map them
  in a PR comment, and merge. Do not stop for user sign-off at the cap.
- The one thing that still blocks the merge: a verified, still-reproducing
  real P0 — file it, keep the PR open, post the reproduction, notify the user,
  end the batch.
- **Merge queue installed** (`scripts/mq-guard.sh` exists — the
  merge-throughput step installs it): after the review passes, tear down
  staging first if you brought a stack up this bead (from INSIDE the worktree:
  `make staging-down`; skip when staging was never installed or never started —
  a non-zero exit then must not block the enqueue). Then **enqueue and move
  on**: `make mq-enqueue PR=<N>`. Do NOT merge, do NOT wait, do NOT rebase in
  a loop — the daemon batch-tests against latest main, lands green PRs
  (closing your bead and commenting on the PR), and on ejection comments the
  failing log and REOPENS the bead so any agent picks up the fix.
  `NEEDS_REBASE` ejection means your PR no longer applies onto main: rebase,
  push, re-enqueue. Queue state: `scaffold mq status` (a `.mq/PAUSED` banner
  means merges are held — read docs/merge-queue.md before touching anything).
  Never run `gh pr merge` yourself — the mq-guard hook blocks it; the
  deliberate-override procedure is in docs/merge-queue.md and is human-only.
- **No merge queue** (`scripts/mq-guard.sh` absent): fall back to the
  serialized manual merge. If the project has a merge slot (`bd merge-slot
  check` reports one), serialize EVERY merge: loop on `bd merge-slot acquire`
  until it succeeds (acquire does NOT block; `--wait` only queues you), then
  re-verify with `bd merge-slot check --json`, merge with `gh pr merge <N>
  --squash --delete-branch`, then `bd merge-slot release` — release even if
  the merge fails, with ONE holder identity across acquire → merge → release
  (a fresh per-command `$$` strands the slot). Tear down staging first from
  inside the worktree exactly as above. Then from the primary:
  `make main-sync && make prune-merged`.

**2.8 Close out:** With the merge queue, the DAEMON closes the bead when the
PR lands — do not `bd close` an enqueued bead yourself; confirm later via
`scaffold mq status --pr <N>` or `bd show <id>`, and treat a reopened bead as
the ejection signal. Without the queue (fallback path), close manually from
the primary after the merge is verified: `bd close <id>`. Noticed a repo-file
fix after merging? Micro follow-up PR; never edit the primary checkout
directly.

## Step 3 — Batch report (required slots — answer each, say "none" out loud)

```
Beads:              <id> -> PR #<n> -> merged | enqueued (PR #<n> awaiting queue) | parked (why) | skipped (why: e.g. claim lost to <actor>) | not started (why: e.g. queue drained after <k> of N)
Docs updated in-PR: <paths - or "none needed: <why>">
Beads filed (open): <id - one-line title - or none>
Stale claims:       <id - assignee - last activity - or "none noticed">
```

Before reporting, make the batch durable and refresh the restore net. Every step
is BEST-EFFORT — none may abort the report (a purely local Beads DB has no Dolt
remote; a `bd backup` target may be absent):

1. **Push the beads off-machine when a Dolt remote is configured — this is the
   real durability:** `bd dolt commit`, then `bd dolt push` ONLY if the project
   has a Dolt remote (a purely local Beads DB has none — skip the push; its local
   Dolt DB plus the committed JSONL export is its durability). Note a push failure
   for a *configured* remote in your report, but never abort the batch over it.
2. `make beads-snapshot` — refresh the `.beads/issues.jsonl` restore copy and
   sync any configured `bd backup` full-history copy.
`.beads/issues.jsonl` (refreshed by step 2) is a LOCAL, regenerable restore copy
— `bd export` recreates it from the DB. Durability is layered: step 1's Dolt
remote is the OFF-MACHINE copy (survives a lost machine); step 2's `bd backup`
(default target `$HOME/.beads-backups`, same machine) survives checkout deletion
or a reset but NOT machine loss unless you point it at a remote (DoltHub/S3); the
JSONL copy is neither — commit it through your project's normal beads-commit flow.
Do NOT force a direct commit onto a protected base branch or push one outside the
PR flow.

One batch-end pass covers every bead closed above.

If the batch ran long and `launchpad` is installed: `launchpad notify "<summary>"`.

## Red flags — stop if you're about to…

| Temptation | Reality |
|---|---|
| Commit or edit in the primary checkout | Work happens in `.worktrees/` only |
| Start bead k+1 before bead k's PR merges | One open PR per agent, strictly sequential |
| Skip the draft PR "until it's ready" | The draft IS the claim other agents see |
| End the turn after the draft PR with "next steps" | #1 observed agent failure — finish the loop |
| Leave a TODO/FIXME comment | That work is a bead, filed now |
| Merge with a red `make check` or Docker gate | Fix or file; never merge red |
| Chase a clean review past round 3 | Degraded-pass self-merge is the documented path |
| Leave a staging stack you started running | `make staging-down` from the worktree before merging (only if you ran `staging-up`; never from the primary — it refuses there, and `prune-merged` reclaims it too) |
| `--no-verify`, plain `--force`, merge commits | Forbidden; `--force-with-lease` after rebase only |
| Close the bead when the PR opens | Close only after MERGED + verified |
| Prose summary instead of the Step 3 slots | The slots are the report format |
| Pick N beads at batch start and work the list | The queue moves under you — select ONE bead at claim time, every time |
| Claim by setting the status field | Not atomic — a concurrent claimant goes undetected; `bd update <id> --claim` is the claim |
| Claim without a per-agent `BEADS_ACTOR` | Same-actor claims are idempotent — two agents sharing the default identity both "own" the bead |
| Retry a lost claim | Normal traffic at high parallelism — take the next candidate |
| Validate a bead before claiming it | Claim first — validation reads shared state; holding the claim hides the bead from peers while you decide |
| Release a rejected bead straight to `--status open` | Persistent dup/conflict → the fleet re-claims/re-rejects it forever; cooldown-release with an ABSOLUTE UTC `--defer "$until_ts"` instead (see 2.1c) |
| Skip or park a bead because its `Owner` is another agent | `Owner` is the immutable CREATOR, not an assignee — it names a departed agent forever. Only `Assignee` + `in_progress` holds a bead (Step 1) |
| Honor an existing park note without re-resolving what it cites | Park notes go stale silently; a "recheck: still parked" line you did not verify is the ratchet that freezes a backlog (2.1b) |
| `bd update <id> --notes "..."` on an existing bead | `--notes` REPLACES — it can destroy a bead's whole investigation history in one command. Use `bd note` / `--append-notes` (2.1c) |
| Defer with a reason like "not autonomously shippable this session" | That is your capacity, not the bead's state — unfalsifiable, so it can never be cleared. Name a PR/bead/branch/command and the re-check condition (2.1c) |
| Mass-defer beads to empty `bd ready` | An empty queue is not the goal. Deferring more beads than you worked is a stop-and-report signal, not a session outcome (2.1c) |
| Cooldown-release the same bead a third time | 2+ prior cooldown notes for one cause = it will not clear itself — `Wait:` note + human triage instead of an hourly claim→reject cycle (2.1c) |
| Pre-filter the queue to your capability slice | Rank the WHOLE queue; capability fit is a within-tier tie-break only — an out-of-slice P0 beats an in-slice P2 |
| Reap/release another agent's stranded bead by hand | Surface it in the reaper report; releasing a claim is an `--apply`/operator decision |
| Bootstrap/reset a populated `.beads` DB | Wipes unpushed beads — fresh clones only; push first (`bd dolt commit && bd dolt push`) |
