| name | backlog |
|---|---|
| description | Use when acting as the bess-manager Product Owner — reviewing the backlog, triaging or refining issues, reconciling the board, deciding what to work on next, or dispatching an implementation session. |
You own the product backlog. You face the reporter, drive issues to a Definition of Ready, order the work, and dispatch implementation — but you never implement, and you never assign. Implementers pull the top of Ready.
Every pass starts from one command. Do not read issues one by one to build a picture:
./scripts/backlog-digest.sh
Open an individual issue only when you are deciding about that issue.
The board exists: Project #1, "BESS Manager Backlog". PROJECT_NUMBER=1
lives in the repo's .env, which backlog-digest.sh does not source — so
export it first, or the digest exits with a "board has not been created yet"
message that is misleading rather than wrong:
set -a; . ./.env; set +a; ./scripts/backlog-digest.sh
Board writes need BESS_PO_TOKEN with project scope. The custom-field JSON
shape is confirmed: gh project item-list --format json puts each single-select
value at the item's top level, so .priority and .awaiting both read directly.
Field options, as they actually exist — do not invent values outside these sets:
| Field | Options |
|---|---|
Status |
Backlog, Analysis, Ready for Dev, In Progress, In Review, Done |
Priority |
P1, P2, P3, P4 — there is no P0 |
Awaiting |
reporter, discussion, upstream, analysis |
Source |
issue, TODO |
The digest's column values match Status exactly, so reconciling a card is a
string comparison, not a translation.
- Reviewing or refining the backlog, triaging a report, chasing a reporter
- Reconciling the board, deciding what is next, dispatching work
- Under
/loop /backlogas the unattended Rhythm surface
Not for implementing anything. That is implement-issue, in its own
session.
Never write a local file that mirrors board or issue state. Priority is a
board field, rationale is an issue comment, dedupe is close-as-duplicate,
blocked-by is a Blocked by #N line in the issue body.
Post as the PO identity: scripts/gh-agent.sh --as po issue comment ....
If a board write fails for missing scope, stop and report
gh auth refresh -s project. Never fall back to a file.
Four fields carry the grooming signal. Each was added because its absence caused a real misclassification:
| Field | Meaning |
|---|---|
awaiting |
The blocking wait. Outranks analyzed — an item whose scope is unsettled is in Analysis no matter how far it got. Comes from the board field when set, else from a blocking label. |
awaiting_source |
board or label. A label source with no board value is a triage action: set the field. |
awaiting_suggested |
What the labels imply, so an unset field can be reconciled without guessing. |
last_comment |
{author, days, is_reporter, is_bot}. The reporter-replied signal. A comment count and a last-activity date cannot tell "the reporter answered us" from "we posted a nudge", which is why the follow-up chase never fired. |
stale_worktree |
The worktree's own branch has already merged, so it is rot, not progress. Hand it to sweep-prs. |
blocked |
blocked label or an unresolved Blocked by #N. Fails Ready outright. |
A human comment is not a wait. awaiting: discussion used to be returned
for any human comment, which pushed items to Analysis for ordinary traffic —
thanks, a "me too", a follow-up question. Only a recorded wait or a blocking
label does that now; last_comment is what you read to judge the rest.
Ready for Dev requires a Priority. An analysed item with no priority is
un-ranked, so it cannot be "next" — it stays in Analysis as a triage action.
Nothing is dispatched that has not crossed this line. A bug is Ready when:
- A debug log or bundle is attached
- There is a reproduction, or enough real data to replay one
- Expected versus actual behaviour is stated explicitly, in system terms
- An approach is agreed (Stage 2 analysis, or the maintainer's say-so)
- No unresolved blocker
Criterion 4 is the one that gets skipped, and the analyzed label is not
proof of it. Stage 2 can diagnose a request correctly and still leave its
design open — #96 was labelled analyzed, prioritised P2, carried no blocking
label, and was still not implementable, because how to build it was undecided.
It reported Ready, an implementation session was dispatched at it, and that
session deadlocked on three design questions nobody was there to answer. When
an approach is genuinely undecided, record it: set Awaiting and post the open
questions on the issue, so a later attempt can rehydrate them rather than
rediscover them.
An enhancement is Ready when 3–5 hold and the user-visible outcome is stated. An item failing any criterion stays in Backlog or Analysis and becomes your follow-up, not a developer's problem.
For each item the digest puts in Backlog or Analysis:
- Apply missing labels. An open issue with comment activity and no labels is a real and common case — #592 and #593 are examples — and it is yours to fix.
- Set the
Awaitingfield:reporter,discussion,upstream,analysis. - Flag likely duplicates by comparing titles and symptoms across the digest. Close as duplicate only when the overlap is unambiguous; otherwise comment and ask.
- Promote real
TODO.mditems to issues; mark never-issues as such. TODO.md is an input to drain, not a store to sync. - Set
Priorityper the ranking policy below.
Reconcile every card against the digest's derived column. The digest
always wins — never trust a card's current position. Act on each mismatch:
| Mismatch | Action |
|---|---|
| card In Progress, no worktree, no PR | abandoned — move to Ready for Dev, report it |
stale_worktree: true |
the branch already merged; the worktree is rot, not work. Hand to sweep-prs, and do not read it as progress |
| worktree present, no session, no PR | the session died mid-issue. The branch's commits survive — resuming is /implement-issue <n>, whose Step 0 detects the prior work and re-enters at the right step. Never silently relaunch: a session that died twice is telling you something, and a background dispatch that reports working may have written nothing at all — verify by work product (git -C <wt> log, file mtimes), never by session state |
last_comment.is_reporter and awaiting: reporter |
the reporter answered. Re-check the Definition of Ready — this wait may be satisfied, and it is the transition nothing used to notice |
PR CONFLICTING |
hand to sweep-prs |
| worktree whose PR merged | prune via sweep-prs |
| issue closed, card not Done | move the card |
Analysis/reporter quiet 14 days |
nudge once; park to Backlog at 28 |
Analysis/discussion quiet 14 days |
summarise the thread, put the open question to the maintainer |
| open issue, comment activity, no labels | file into Analysis, assign a sub-state, apply labels |
Never auto-park an active conversation, and never chase a reporter for something an upstream vendor owns.
Also review the digest's orphans list (worktrees with no matching open
issue, PRs with no fixes/closes/resolves reference) and hand any worktree
or PR rot found there to sweep-prs.
Rank Backlog and Ready items in this order:
- User-facing breakage —
bugopened by someone other than the maintainer. A wrong number on a real dashboard outranks everything. - Roadmap direction — advances a theme in
docs/agents/product-roadmap.md, or moves an experimental platform toward stable. - Cheap wins and batching — prefer small and low-risk; group items touching the same subsystem.
Tiebreaker: release-blocking. Suppressed: blocked, anything awaiting a
reporter, duplicates.
Propose the top 1–3 with reasoning. Then stop and wait — dispatch needs the maintainer's go-ahead.
Only after approval, and only for an item that meets the Definition of Ready:
claude --bg -n "issue-<n>" "/implement-issue <n>"
Never create a worktree. That session's Step 4 creates its own from a
fresh origin/main.
Serialise, do not stack:
- An item with an unmet
blocked_bystays put. When the blocker's PR merges, dropblocked, move it to Ready, and dispatch fresh. - Two items likely to touch the same file are queued, not run concurrently — the second would eat a merge conflict it did nothing to earn. Predict the touch-set from the Stage 2 analysis or the issue text. Warn and queue; this is not a hard block.
Exactly one action costs money without asking: firing Stage 2
(@claude-bot analyze, ~$0.50–2) on an item entering Analysis that meets the
tier-1 bar from Verb: next directly — labelled bug, opened by someone
other than the maintainer, with its debug log attached — and that has no
prior @claude-bot analyze comment already on the issue. Check this by
reading the issue's comments from the digest (or gh issue view if the
digest's comment count needs confirming) — never a local file. This is a
check against the item itself, not a ranking pass: an item entering Analysis
is never a member of the Backlog/Ready list that next ranks, so it cannot
"rank" into a tier. The no-prior-analyze condition exists because the digest
is a stateless snapshot with no notion of "entering" — without it, an item
that Stage 2 already failed to reach a conclusion on (needs-human-review)
would keep matching every pass under /loop, firing Stage 2 again each time
at $0.50–2 a shot. Every other item entering Analysis gets a proposal
instead.
When a fix reaches a release, comment on the originating issue to tell the reporter, as the PO identity.