All mechanics live in the bro CLI. This skill is policy only.
bro flow next schedules one bead; bro flow loop runs the queue to exhaustion —
claim → worktree → agent → push onto the gate stack → repeat — with no
per-item "should I continue?" The backlog's existence is the approval.
Configure the agent once
Prefer the provider lane — a providers.<name> registry entry
resolves the spawn exactly like bro fleet agents up:
{
"providers": {
"kilo-cli": { "type": "acp", "command": "kilo --acp", "model": "typesafe/jev-1.13" }
},
"loop": { "provider": "kilo-cli" }
}
An acp entry spawns bro acp-worker — a stdio ACP server driven by
the protocol, headless by construction (no TUI can open on WSL interop).
A cli entry substitutes its command for the template. The picks:
--provider/--agent <name> flags → fleet.profiles preset
(--profile, loop.profile) → loop.provider →
agents.native.provider. --model and --auto-approve tune the lane.
bro flow loop --agent kilo-cli works too — a bare --agent value that
exactly names a configured provider IS a provider pick.
Per-bead routing — declare fleet.routing and each claimed bead's
class picks its own provider chain, same as bro agents up: the
class:<name> bead label wins, unclassed beads take default, and
--class <name> pins the whole run to one class:
{
"fleet": {
"routing": {
"default": { "chain": ["devin"] },
"sweep": { "chain": ["kilo-free"] }
}
}
}
The chain head supplies the provider (and its inline model pin) unless
a flag-tier pick already named one — explicit --provider,
--agent <provider>, --profile still pin the lane for every bead,
with the resolved class stamped as provenance (BRO_AGENT_CLASS); the
config picks (loop.provider, loop.profile, agents.native.provider)
are standing defaults the declared table outranks. A resolved class
missing from the table is a config error — default is preflighted
before the first claim, a bad class: label parks its bead instead of
silently falling through. fleet.router (shadow/enforce) classifies
unclassed beads by judge verdict, same as agents up.
Raw template — loop.agent (or an --agent value that names no
provider) is the escape hatch:
{ "loop": { "agent": "devin --prompt-file {promptFile} -p" } }
{promptFile} is replaced with the work-order file bro writes into the
fresh worktree (no placeholder → the path is appended as the last arg).
Examples: claude -p "$(cat {promptFile})", codex exec "$(cat {promptFile})". The spawn env carries BRO_BEAD_ID, BRO_BEAD_TITLE,
BRO_PROMPT_FILE (provider lane also pins BRO_AGENT_PROVIDER,
BRO_AGENT_MODEL). Whatever permission flags your agent needs for
unattended work are yours to choose — e.g. devin's
--permission-mode dangerous --respect-workspace-trust false skips all
human confirmation, which is the point of the loop but obviously grants
the agent full autonomy; scope it to machines/repos you trust. A
template --agent flag wins over a configured loop.provider — the
provider flags can't sit beside it. It also bypasses fleet.routing
entirely (a warn line says so): every bead runs the literal template.
Other loop keys: bootstrap (runs once per worktree),
stallMin (advisory output-silence flag at check-in — never a kill),
mergeTimeoutMin, fixRounds, maxItems, maxOpen — the gate stack's
open-PR cap (default 3) — worktreeMb / diskMinSlots, the disk
floor below — and batch / batchMinPriority, the clump
knobs below.
Commands
| Command | What it does |
|---|---|
bro flow loop |
Run the queue until idle or gated |
bro flow loop --max N |
At most N beads this run |
bro flow loop --max-open N |
Cap the gate stack's open PRs — a full stack only services gates until a merge frees a slot |
bro flow loop --disk-min-slots N |
Disk floor in slot units — pushes hold while free disk < N×loop.worktreeMb on any filesystem the run writes to (default 2; 0 disables) |
bro flow loop --dry-run |
Print the top item's plan (claim, worktree, agent cmd) — changes nothing |
bro flow loop --agent '<tpl>' |
One-off agent override — a provider name resolves through the registry |
bro flow loop --provider <name> |
Provider pick (--profile, --model, --auto-approve tune it) |
bro flow loop --class <name> |
Pin every claimed bead to one fleet.routing class (default: the bead's class: label, else default) |
bro flow loop --label a,b |
Declared scope — only beads carrying one of these labels are claimable; the rest of the shared queue stays untouched. "Loop the debt beads" never bleeds into unrelated work |
bro flow loop --batch N |
Batch claims — a claim pulls up to N same-affinity beads into one worktree + worker + PR (default 1 = solo). Affinity binds on the lead's spec/epic/area:/path-prefix key; loop.batchMinPriority (default 3) keeps urgent work solo; a solo label opts a bead out |
--merge-timeout MIN, --interval SEC |
Gate budget and poll overrides |
What happens per bead
- Claim — top
bd readyitem,bro flow next's rules: HUMAN GATE beads, epics, and molecule steps (convoy-owned) are never auto-claimed. - Worktree — sibling
<repo>--<bead-id>on branchloop/<id>offorigin/main;loop.bootstrapruns once per bead, before the agent. - Agent — the work-order prompt is written to the worktree and the
agent runs to completion — no per-agent wall clock (a hard kill on a
working session is an anti-pattern; the run record is how a check-in
judges it instead). stdout/stderr append to
<git-common>/bro/loop/<slug>.logand a<slug>.jsonrecord pins the pid —bro review watch/bro fleet statusreport output silence pastloop.stallMin(45) as an advisory; the orchestrator decides, bro never kills. Its env pinsBEADS_DIRto the runner's store (bd where), sobdwrites inside the worktree reach the shared db regardless of version or a tracked.beadscopy — and an agentbd closeis honored as a verdict (closed), not reopened as a failure. - Gate stack — the PR is discovered via
gh pr list --headand joins the run's gate stack instead of blocking the queue. Each tick services the stack oldest-first, then pushes the next bead while aloop.maxOpenslot is free — a pending gate is claimed work, never orchestrator idle. A gate event is the preemption point: green →bro review act merge(merge slot +--match-head-commitapply); threads → the agent is respawned with the thread list;CONFLICTING→ a rebase order onto the PR's base;BEHINDas the sole blocker → update-branch. Fix and rebase rounds shareloop.fixRounds; each member waits out its ownmergeTimeoutMin. - Close —
bd close <id> --reason "landed via PR #N", worktree and branch removed; the freed slot claims the next bead. A member's own loop IS the supervisor —bro review drivenever needs to spawn fixers against loop-owned worktrees.
Policy
- The loop ends at
idle,gated, or--max N—idlemeans the backlog is empty;gatedmeans only human gates / epics / molecule steps remain. Report which and stop. - Failures are visible, never silent — an agent that exits without a
PR gets the bead reopened with a
loop:note; a stalled PR parks the bead: aloop:note names the blocker, the claim releases into a deferred snooze forloop.parkedWakeMin(default 2h — then it wakes toopenon the next ready read;0ices it untilbd undefer), and the worktree stays as the resume cache. - The open-PR set is bounded —
loop.maxOpen(default 3) caps how many gates sit on the stack; a full stack services gates only until a merge frees a slot. Independent branches drift against shared files fast — keep the cap tight rather than wide, or run--stackso members chain onto each other and conflicts surface at rebase-time. - Pushes also hold on the disk floor — a slot is priced at
loop.worktreeMb(default 400 MB: worktree + bootstrap/agent litter); while free disk on the worktree parent or tmpdir drops belowloop.diskMinSlots× that price (default 2), pushes hold like a full fleet — nothing claimed, nothing parked — until merges free the space.--disk-min-slots 0is the emergency escape. - Already-attempted beads aren't re-picked within a run — a reopened failure can't spin the loop forever.
- Batch claims are fail-safe, never silent — with
loop.batch/--batch Na claim is a clump: one worker carries N compatible beads through one worktree and one PR. The contract is per-bead commits naming the bead id (fix(cli): drift (bro-x1)) — at merge the gate closes exactly the members the branch's commit log names and re-queues the uncovered tail. A mid-flight batch loses only the in-flight bead, never the landed prefix; a member that merged without a covering commit comes back as open work, not a false close.--maxbounds beads, not items — a clump fills only up to the remaining claim budget. --dry-runfirst on a new repo — verify the agent template and worktree path before the loop starts claiming.- One
bro flow loopper repo — two runners would race the same top beads (claim is atomic, so the loser just takes the next one — wasteful, not corrupting).
Done means verified-clean, not exited
A loop iteration is done only when the bead reached a terminal,
authoritatively verified state — PR MERGED per gh pr view and the
bead closed per bd show, or parked with a loop: note naming the
blocker and the claim snoozed deferred. A process exiting is not a
verdict.
The loop runs this audit itself at every exit — idle, gated, or error
(try/finally around the claim cycle). It first reaps provably-done
litter — clean loop/* worktrees and branches whose bead is closed or
whose PR merged (bro work prune --loop runs the same sweep by hand) —
then names every surviving tail in the run summary: open loop/* PRs,
kept worktrees and branches, beads still in_progress, and cleanup
failures collected during the run, then bro work sync pushes artifacts and
bead state. A clean run prints clean — no loop tails. Tails that
survive the sweep — dirty trees, open PRs, live claimants — still need
a human or a follow-up session to clear.
The process itself is held to the same contract: a ref'd heartbeat
keeps the event loop open while a run is live (loop: alive — <stage>
lines name the suspension point, so a stuck await is a visible stall,
never a silent drain-exit), and an exit listener prints
loop: process exiting mid-run — <stage> plus a bead note if the
process dies with the queue open — signals included. A run can stall
or die; it cannot vanish without a trail.
Cleanup after a merge wait is &&-sequenced or command-owned — never
;: a failed act wait must not touch the PR, branch, worktree, or
bead.