| name | convoy |
| description | Use when asked to batch-ship a backlog — "start a convoy", "batch ship", "run the lanes", or any request to work multiple tracker issues as parallel, continuous ship streams. |
Convoy
Overview
Run parallel ship lanes continuously over a ranked queue of mechanical issues. A convoy multiplies the ship skill; every ship rule applies inside each lane — this skill adds only the coordination layer.
REQUIRED BACKGROUND: the ship skill. Its merge gate is unchanged: no agent merges, the coordinator doesn't merge, only the merge authority merges. "Keep the lanes running constantly" authorizes continuous building, never continuous merging.
Operator config
If config.md exists in this skill directory, read it first — it sets the parameters below. Ship's own config.md (merge authority, dispatch model, tracker, sensitive paths) applies inside every lane.
| Parameter | Meaning | Default |
|---|
max_lanes | Ceiling on parallel lanes | 5 |
queue | Where the ranked mechanical queue lives | Explicit list passed in the request (convoy ID-1 ID-2 …) |
repo_scope | Repos a convoy may work, with any per-repo deploy policies | Ask before claiming into a repo not yet configured |
migrations | Migrations directory + changelog file, if repos use serially numbered migrations | Detect from the repo; skip allocator if none |
usage_signal | How to estimate remaining usage budget | npx -y ccusage@latest blocks --active --json --token-limit max |
Queue
- Re-read the configured queue at every claim — the tracker wins over any cached copy. An explicit list in the request (
convoy ID-x ID-y …) overrides the configured queue.
- Only mechanical issues ride a convoy. If a claimed ticket turns out to need design input or the operator's decision, park it back with a tracker comment and claim the next.
- Respect
repo_scope and its per-repo deploy policies (e.g. a repo may be merge-only, with deploys batched separately by the operator).
Lanes
- A lane = one bundle from the queue (issues sharing a surface), worked serially. Never two lanes on one surface.
- Each lane works in a task-unique isolated clone or worktree — never the shared checkout (committing agents move refs). Branch from a verified
main ref.
- Every dispatched agent follows ship's
dispatch_model policy. Re-apply it on every relaunch — pins drop.
- Lane pipeline = the ship pipeline with the ticket as spec: plan → build → /code-review
high loop (xhigh for sensitive paths) → verify → PR to green → park.
- Lane agents are disposable per issue. Park is the seam: PR posted, tracker updated, agent retired; the coordinator spawns a fresh agent (per
dispatch_model) for the lane's next claim (the brief carries the ticket, the allocated migration number, branch-from-verified-main, the don't-touch-CLAUDE.md rule, and a line or two of surface notes). Never let one agent run multiple issues' pipelines in a single context — context rot shows up as subtle build errors, and its view of main is frozen at spawn. Never retire mid-issue just to recycle.
Park-and-continue merge gate
- A lane that reaches green posts the PR link, sets the tracker issue In Review, and immediately claims its lane's next item. It never waits for a merge.
- Parked PRs stay independently mergeable from
main — never stack one lane's branch on another's.
- The coordinator maintains a merge-ready list for the merge authority: PR links in recommended merge order, a one-line risk note each, merge-time side effects called out explicitly (env-var changes, migration applies), and declared order for any conflicting pair.
- After the merge authority merges, the coordinator rebases the remaining parked PRs onto
main and runs the post-merge doc sync itself, serially — CLAUDE.md and the migrations changelog are the conflict hot spots; exactly one writer.
Coordinator owns
- Migration allocator (when
migrations applies) — assign numbers at claim time: highest on main + open PRs + numbers already allocated this convoy, then +1. Lanes never pick their own. Lanes ship SQL + an appended changelog entry only — a lane never applies a migration (no db push, no MCP apply).
- CLAUDE.md discipline — only a lane whose issue requires it may edit it, surgically; every other lane's brief says don't touch it.
- CI watches at coordinator level — don't rely on lane self-reports.
- Lane health — instant-death spawn (sub-10s, zero tool uses, garbled output): discard the output entirely, relaunch. Stalled lane: check ground truth on the forge first (the work may have finished with an orphaned report), ping with a short deadline, then stop-and-replace pointed at the existing branch — never a duplicate agent while the original may still be alive. Same issue fails twice: park it with a tracker comment, move on.
- The ledger — write-through, compaction-proof. Conversation context is a cache, never the only copy of convoy state. Per-lane claims/status live in the tracker (lockstep IS the state store). The merge-ready list, migration-allocation table, agent roster (agent ↔ lane ↔ issue ↔ branch), and decisions with their rationale (compaction keeps conclusions, drops reasons) live in a running-log file updated in the SAME turn as every claim, park, allocation, or merge — allocate → log → brief the lane, in that order. After a compaction or restart: re-read this skill, then the ledger, then reconcile against the tracker + the forge + the migrations directory on main before resuming claims — and enumerate live background agents before relaunching anything.
- Tracker lockstep and the running log double as the operator's one-read check-in.
Usage throttle
Convoys are expensive; lane count follows the session's usage budget.
- Check the signal at convoy start, at every park/claim event, and at least every ~30 minutes of wall clock regardless. Long event-less gaps happen exactly when every lane is grinding — which is when burn peaks (review loops are the burn signature), so event-only checks detect a miscalculated estimate too late. The operator's stated numbers always win ("we're at 50%"). Otherwise estimate via
usage_signal: tokens vs. the highest historical block ≈ budget used; block endTime minus now = window time left.
- Compare budget-left % to window-left %. Burning cool (budget ≥ time) → up to
max_lanes. Burning hot (budget < time) → shed lanes by attrition: a lane that parks its PR doesn't refill. Clearly overshooting → also stop the least-progressed lane (a minutes-old lane is the cheapest kill; park its issue back with a tracker comment). Never kill a lane deep in build/review — sunk work that can still reach park is the best remaining spend — unless it can no longer plausibly reach park within the remaining budget; then it suspends too.
- Severe overshoot (a re-check shows budget dying well before the window — the earlier estimate was wrong): freeze intake, keep only the lane(s) closest to park, and suspend every other lane at its nearest coherent stopping point — finish the current unit to a compiling, committed state, push the branch, park the issue back with a tracker comment carrying exactly where it stopped (build progress, review-loop round, outstanding findings) so a later convoy resumes the branch warm instead of restarting. Never a hard kill mid-thought when one more minute buys a clean commit. Re-check the signal after the shed to confirm burn actually dropped; on budget refresh, suspended branches are the first reclaims.
- Intake test for every new claim: can it plausibly reach park before the window closes, with buffer? If not, don't claim — freeze intake and wind down: in-flight lanes finish; a lane that can't finish commits + pushes its branch and parks the issue back with a status comment (recoverable, not stranded); the final ~25 minutes are the coordinator's for the merge-ready list, tracker lockstep, and the status summary.
- Rigor is never a throttle lever. Fewer lanes, never thinner /code-review or verification — a thinly-reviewed green PR converts the operator's one-read check-in into a re-review job.
- Window reset / budget refresh → scale back up to full lanes and resume intake.
Stop conditions
Queue empty · the operator says stop · every lane parked or blocked. On stop, post the merge-ready list plus a short status summary (shipped, parked, failed, skipped).
Red flags — STOP
- Any merge "to unblock throughput" or "because it's mechanical and green"
- A lane branching from, or committing in, the shared checkout
- Two lanes holding the same migration number, surface, or concurrent CLAUDE.md edits
- Acting on a dead lane's garbled output
- A dispatched agent inheriting the session model in violation of ship's
dispatch_model policy