Portable, Claude-Code-native wave-orchestration toolkit. Plan a batch of independently-grabbable issues, dispatch parallel AFK agents in isolated worktrees, review each with a schema-validated verdict, land via PRs — with cross-wave conflict/parallelism reasoning as the universal core.
Stable. The orchestration has been driving flotilla's own development across fifty-plus live waves, and runs in installed form — plugin plus published engine — in independent consumer repos beyond this one, operated by more than one person. Three surfaces are semver contracts: the engine's package-root export surface, the wave.config.json schema (both since 1.0.0), and the CLI's output surface (since 1.1.0). CHANGELOG.md names what each release added and what remains unproven.
flotilla turns a backlog of tracker issues into a wave: a batch of independently-grabbable work items that a Coordinator plans, then dispatches to parallel AFK (away-from-keyboard) agents, each isolated in its own git worktree. Every agent's work is reviewed by a second, universal Reviewer agent before anything lands — the review returns a schema-validated verdict, not free prose, so routing to approve / request-changes / stop is deterministic rather than inferred. Landing happens via pull requests against a protected default branch; nothing is ever pushed directly to it.
Prerequisites: Node 20.11 or newer (the engine's declared floor), git, a repository on GitHub or Bitbucket Cloud whose default branch is protected, and a tracker token resolvable through a lookup command.
flotilla installs as two pieces — the skills as a Claude Code plugin, the engine from the public npm registry — and nothing is copied into your repo. In Claude Code, inside the repo you want to run waves in:
-
Install the plugin:
/plugin marketplace add formtrieb/flotilla /plugin install flotilla@formtrieb -
Run the
wave-setupskill. The one-time bootstrap: it interviews you on three things (your tracker, the label set that marks an issue agent-ready, your build/test commands), installs the engine pinned into your repo, and writeswave.config.json— the single binding every other skill reads (ADR-0032). It also does the unglamorous parts for you: scaffolds the credential lookup so no token ever lands in a settings file, writes the permission allowlist a wave needs to run unattended, and preflights the live tracker and code-host preconditions before you ever plan a wave. Fix anything the preflights flag before continuing. -
Get a few issues wave-ready.
triageworks an existing issue into shape, orto-issuesslices a plan/PRD into ready issues — each carrying a declared file scope, a risk/worker classification, and acceptance criteria. Hand-authoring in the same shape works too. -
Run the wave:
wave-planshows what can run side by side (read-only — you pick the ids),wave-creatematerializes the batch,wave-startdispatches one isolated Worker per issue plus a Reviewer per Worker. It ends with every row as an open, reviewed PR — flotilla never merges on its own. -
Land the PRs, then
wave-close. It computes the advisory merge order, cleans up the agent worktrees, and archives the wave's record. Session died mid-wave?wave-resumepicks up exactly where it stopped.
The full adoption path — every step above in detail, plus the preconditions that fail silently if skipped — is docs/ONBOARDING.md. Read its checklist before your first real wave.
The docs use a precise vocabulary; these five are enough to read everything else. The full glossary is CONTEXT.md.
| Term | What it is |
|---|---|
| Wave | One batch of independently-grabbable issues, dispatched to parallel agents in isolated worktrees, reviewed, and landed via PRs. |
| Conflict map | Pure set-intersection over each issue's declared file globs — answers before dispatch which work can safely run in parallel, both inside a wave and against what other waves already claimed. |
| Claim | The coarse state a wave writes to your tracker (queued → in-flight → in-review) so humans and concurrent waves can see what is taken. One-way: the tracker is a projection, never the authority. |
| Spine | The wave's durable, repo-local markdown record — a write-ahead log. It is what makes a killed Coordinator resumable. |
| Reviewer | The independent, read-only agent that re-runs your verify commands and checks every acceptance criterion before a PR opens, returning a schema-validated verdict — the ground truth for whether the work is actually done. |
Capabilities, in three lines: GitHub Issues and Linear both ship full tracker adapters — claim ledger, needs-attention, frontier — with Linear alone able to mirror that frontier back as a native Project/Initiative update. GitHub and Bitbucket Cloud both create and land PRs through the engine's own host-pr seam, never gh. Only GitHub can arm a PR to land itself once checks go green — Bitbucket Cloud's API has no per-PR auto-merge call, so --auto there merges what's already clean and leaves the rest for a human. The full dated, per-cell matrix: docs/CAPABILITIES.md.
flowchart LR
setup["wave-setup<br>one-time bootstrap"]
subgraph planning["Planning"]
direction LR
g["goal<br>the finish line"] --> t["triage"] --> p["to-prd"] --> i["to-issues"]
end
subgraph lifecycle["Wave lifecycle"]
direction LR
wp["wave-plan"] --> wc["wave-create"] --> ws["wave-start<br>Workers + Reviewers"] --> wx["wave-close"]
wr["wave-resume"] -. "Coordinator died?<br>reconcile + re-dispatch" .-> ws
end
setup --> planning --> lifecycle
| Skill | Phase | What it does |
|---|---|---|
wave-setup |
Bootstrap, once per repo | Interviews you on tracker, eligibility labels, and verify commands; installs the engine and writes wave.config.json — the one engine.cli binding every other skill reads. |
goal |
Planning | Manages a named finish line as a container on your tracker: cuts its opening frontier as bare placeholder tickets, curates who belongs, and reports what is still open. Read-only status pass; it never stamps readiness, never dispatches, and never declares the goal reached. |
triage |
Planning | Works an incoming issue into shape: categorize, reproduce, gather what's missing, mark it ready for an agent or a human. |
to-prd |
Planning | Captures a design conversation as a PRD, published as a tracker issue ready for slicing. |
to-issues |
Planning | Slices a plan or PRD into independently-grabbable, wave-eligible issues — each with a declared file scope, risk/worker classification, and acceptance criteria. |
wave-plan |
Wave lifecycle | Draws the wave-eligible candidate set and cross-checks it against what other waves already claimed. Read-only and advisory — you pick the ids. |
wave-create |
Wave lifecycle | Materializes the chosen ids into a durable spine (a write-ahead log) after DoR and conflict checks; sets the soft queued claim on each issue. |
wave-start |
Wave lifecycle | Dispatches a worktree-isolated Worker per row, then a universal Reviewer per Worker; routes each schema-validated verdict deterministically. Ends with every row in-review — it never merges. |
wave-close |
Wave lifecycle | Computes the advisory merge order, cleans up agent worktrees, archives the spine. Opt-in --auto arms the order-free PRs for auto-merge. |
wave-resume |
Wave lifecycle | Reconstructs a killed Coordinator's state from the spine, the live worktrees, and on-disk sidecars; re-dispatches only what actually needs it. |
wave-reviewer |
Wave lifecycle | The read-only pre-PR quality gate wave-start dispatches for every row: re-runs verify against the wave anchor, checks each acceptance criterion with evidence, predicts sibling merge conflicts. |
report |
Utility, consumer-side | Files a fully-analyzed finding about flotilla itself — found while running the installed plugin/engine in your repo — upstream at flotilla's repo in the house format. Consent-first: it never files without your explicit go. |
grill-with-docs |
Utility | Stress-tests a design decision against the domain model and the ADRs before it's built, updating the docs inline as decisions settle. |
wave-shared |
Library | The shared schemas and conventions the execution skills load; invoked by its siblings, never directly. |
The thing that stays true regardless of stack or tracker is the conflict/parallelism reasoning: every issue declares the file globs it touches, and a pure set-intersection over those globs answers "how much work goes into one wave, and can two waves run side by side?" Everything else — which tracker, which verify commands, which code host — is an adapter around that core.
flotilla is two layers: a pure engine that is already harness-agnostic, and adapters that diverge freely per consumer.
flowchart TB
subgraph skills["Claude Code skills — the dispatch driver"]
S["wave-* · goal · triage · to-prd · to-issues · report"]
end
subgraph engine["Engine (tools/wave) — pure TypeScript, imports only node:* + fast-glob + micromatch"]
CM["computeConflictMap<br>glob-set math"]
SM["state machine<br>+ DoR gate"]
MO["merge-order<br>algorithm"]
SCH["WorkerReport /<br>ReviewerVerdict schemas"]
end
subgraph stores["IssueStore adapters — one conformance suite, unchanged across all three"]
MD["MarkdownFsStore"]
GH["GitHubIssuesStore"]
LN["LinearIssuesStore"]
end
SP["SpineStore<br>per-wave spine as local markdown (WAL)"]
S -->|"engine.cli<br>(setup-time binding, ADR-0032)"| engine
engine -->|"IssueView"| stores
engine --> SP
| Seam | What it is |
|---|---|
IssueView |
The canonical contract. Every adapter's whole job is read(id) → IssueView (id, risk, worker, declared files, blocked-by, acceptance criteria, coarse status) — the engine never knows which tracker an issue came from. |
IssueStore |
create · read · transition · close · listOpen, plus facets for triage state, needs-attention flagging, closing-probe reads, minimal authored-content amends, and the Goal container whose frontier is derived rather than written. |
SpineStore |
The per-wave orchestration spine as durable local markdown — the write-ahead log a killed Coordinator resumes from. |
| Two-scope state | Fine-grained states live only in the spine. The tracker sees a coarse projection — available → queued → in-flight → in-review → done, plus an orthogonal needs-attention flag — so humans and concurrent waves can see what is claimed. |
| Conflict map | computeConflictMap is wave-agnostic pure glob-set math: feed it (candidate wave) ∪ (everything queued or in-flight) and it answers directly whether two waves can run side by side. |
Two properties worth knowing before you read further: the engine ships as raw TypeScript with no build step (tsc --noEmit is the type gate), and its public API is a deliberate, drift-guarded list — every module export is either deliberately public at the package root or on a reason-carrying allowlist, and a symbol in neither fails the test suite. There is deliberately no dispatch-host abstraction: the engine calls no agent-harness primitives; the Claude Code skills are the dispatch driver, and the schema-validated-subagent-return guarantee (agents cannot silently fabricate a result) is a property of that driver.
- Adopting flotilla in your repo — docs/ONBOARDING.md: the Quickstart above in full detail, the preconditions checklist, credential mechanics, and the vendor-copy fallback for repos that cannot install a plugin. Before the setup-time binding exists,
npx @formtrieb/flotilla-engineprints the engine's verb list (exploration only — unpinned and slow;wave-setupreplaces it with the pinned binding). - Why it works this way — docs/CHARTER.md for the architecture, docs/adr/ for the individual decisions with the options that were rejected and why.
- The vocabulary in full — CONTEXT.md, the domain glossary.
- Contributing to flotilla itself? Start with CLAUDE.md. Cutting a release? docs/RELEASING.md.
flotilla is licensed under Apache-2.0. Parts of it were seeded from other sources under their own terms — see PROVENANCE.md for the seed points and the retained upstream notices.