Fix a backlog in parallel
A list of independent fixes and one afternoon. Decompose it into a fleet plan, run the workers concurrently under file claims, and review what actually landed.
You have a list. Eleven clippy warnings across four crates, a dozen modules
with no test coverage, every TODO(deprecate) left over from a migration.
Each item is small, none of them depend on each other, and doing them one at
a time would eat your whole afternoon watching a progress bar.
This is what stella fleet is for: many tasks at
once, each one handled by a full stella worker, coordinated so they don't
overwrite each other.
The whole thing, first
stella --spend-limit 6 fleet \
"Fix the clippy warnings in stella-store" \
"Fix the clippy warnings in stella-graph" \
"Add unit tests for the plan parser in stella-fleet"Three workers run together in your repository root, committing to your current branch as each one finishes. That's all a short list needs.
Workers commit to the branch you're on. Start from a branch you're willing
to have things committed to — not main — and start from a clean
working tree, so git log afterward clearly separates their work from
yours.
Check for shared files first
Everything else follows from one question, and it's worth thirty seconds before you type anything.
Disjoint files → inline promptsEach item touches its own crate, module, or folder. Nothing to declare. Pass the prompts on the command line and let claim-on-first-write handle the rest.
Overlapping files → a plan with claimsTwo items might both edit src/store/schema.rs. Declare the paths up
front, so the second attempt fails by name instead of racing the
first. This is the common case for anything over about five items.
Ordered work → depends_onItem B only makes sense after item A lands — a database migration before the code that reads from it. Dependencies control the order tasks run in. They are not just a speed hint.
Competing answers → isolationTwo attempts at the same file, where you want to compare them. This
is the one case for isolation = "isolated"; see
when to isolate.
Turning a backlog into a plan
For anything beyond a handful of prompts, write the list down. A plan file
is a .toml (or .json) file with one [[tasks]] entry per item:
[[tasks]]
id = "store-clippy"
title = "Clippy: stella-store"
prompt = "Fix every clippy warning in stella-store. Do not silence with #[allow]."
claims = ["crates/stella-store/src/lib.rs", "crates/stella-store/src/write.rs"]
[[tasks]]
id = "graph-clippy"
title = "Clippy: stella-graph"
prompt = "Fix every clippy warning in stella-graph. Do not silence with #[allow]."
claims = ["crates/stella-graph/src/lib.rs"]
[[tasks]]
id = "workspace-lints"
title = "Promote the shared lints"
prompt = "Move the now-common lint config into [workspace.lints] and drop the per-crate duplicates"
depends_on = ["store-clippy", "graph-clippy"]
claims = ["Cargo.toml"]stella --spend-limit 6 fleet --plan cleanup.toml --max-concurrency 3Two crates get cleaned up at the same time in the first wave. The workspace-wide tidy-up runs alone in the second wave, once both earlier tasks have actually succeeded. The full plan schema has every field.
Plans get checked before anything runs. A duplicate id, a task that
depends on itself, an unknown dependency, or a cycle gets rejected up front,
with the problem task named — so a typo costs you one message, not half a
run.
Writing prompts that survive being run alone
The mistake specific to fleets is a prompt that reads fine in a list but turns out ambiguous by itself. Each worker sees only its own prompt — not the other tasks, not your intent, not the conversation you'd have had with a person.
Three habits that pay off:
- Name the scope in the prompt itself. Write "Fix the clippy warnings in
stella-store," not "fix the clippy warnings" — which invites a worker into the whole codebase and straight into another worker's claimed files. - Say what a fix should not do. "Do not silence with
#[allow]," "do not delete the test." Left unsaid, the cheapest way to get to a green build is sometimes the wrong one. - Give it something it can check on its own. A prompt that names a test command lets the worker's verify stage finish based on evidence instead of an opinion. This is the single biggest lever on both cost and trust.
Claims are what makes it safe
By default, every worker runs in one shared folder — your repository root. That's a deliberate trade-off: the build cache stays warm and commits interleave on one branch, but two workers editing the same file would silently race each other.
claims removes that risk. A declared path is locked for the length of that
task; a second task trying to claim a locked path fails right away, by
name, in under a second, naming which task is holding it. Paths you didn't
declare get claimed automatically on first write, so you're protected even
for files you didn't think to list.
Shell commands count as writes too. A command line does not say which files
it will write, so stella looks at the folder before the command runs and
again after, and claims whatever changed. That covers a
sed -i, a redirect, a formatter, or an install that rewrites a lock file.
It never blocks the command itself — by then it has already run — so what
you get is a warning for the next worker. One gap: a folder that is not a
git repository has nothing to compare, so nothing is claimed there.
Claims are matched as exact strings — they are not folder prefixes.
Claiming src/store/ does not protect src/store/write.rs. Name each file
directly, and spell it the same way everywhere in the plan.
The locks live in .stella/private/store.db, separate from the fleet's own
records, so they also protect you across separate fleet runs — a second
stella fleet running in another terminal can't step on the first one's
files.
When to isolate instead
isolation = "isolated" gives a task its own git worktree, under
.stella/worktrees/<slug>, on a fleet/<slug>-<hash> branch. Use it when
the work is genuinely different attempts at the same thing — two takes
on the same rewrite, where you want to see both and pick one — or when a
task needs to change files outside the normal checkout.
It is not the safer default. Isolation means a cold build cache for every tree, and it pushes every merge conflict to later, when you combine the work. For a backlog of independent fixes, claims are cheaper, and they catch a collision an hour earlier.
What the spend limit actually does here
--spend-limit is a global flag, and in a
fleet it gets divided, not shared: each worker is limited to
spend limit ÷ max-concurrency. The fleet stops starting new waves once
total spend crosses the cap.
In practice, this means concurrency and per-task budget trade off against
each other. --spend-limit 6 --max-concurrency 3 gives each worker $2.
Raising concurrency to 6 cuts that in half, and a task that needed $1.50
will now stop partway through. If tasks are dying early, lower the
concurrency before raising the spend limit.
Reading the result
This is the part that separates a fleet from a script, and it's where the time you saved gets spent back if you skip it.
Nothing gets retried automatically. A failed task does not unblock the
tasks that depend on it — they end the run marked skipped, a different
outcome from "failed" that means the work was never even attempted.
Start with the ledger, .stella/private/fleet.db — a local SQLite file,
like all stella telemetry:
# What failed in the most recent run, and what the worker said about it.
sqlite3 .stella/private/fleet.db \
"SELECT a.task_id, a.branch, a.summary
FROM attempts a
WHERE a.run_id = (SELECT id FROM runs ORDER BY created_at_ms DESC LIMIT 1)
AND a.success = 0;"Then read the diff. Eleven workers each making a small commit on one branch is exactly the shape that lets a bad one hide:
git log --oneline main..HEAD
git diff main...HEAD --statPast runs also show up on the Fleet runs card of the Observatory dashboard, if you'd rather not write SQL.
Worktrees and fleet/* branches from isolated tasks are left in place on
purpose — those branches are the finished work. git worktree list shows
all of them. Nothing gets merged for you automatically.
When it doesn't go cleanly
A task failed and took its dependents with it
This is expected, and it's by design: a dependent task that ran anyway would be building on work that isn't actually there. Fix the cause, then run it again — either the whole plan (finished work is cheap to redo, and the claims still protect you) or a trimmed plan with just the failed branch of tasks.
A dispatch failed, naming another task
Your claims overlap. That's the system working as intended — it caught at
launch time what would otherwise have corrupted a file. Either split the
work so the two tasks own separate files, or add a depends_on link so
they run in different waves.
Everything is fighting over one file
If three tasks all need Cargo.toml, they aren't really three tasks.
Combine them into one prompt, or order them with depends_on. A fleet
speeds up work that's already independent. It can't turn work that's
inherently sequential into work that runs at the same time.
The workers went wider than you meant
This is almost always a prompt that didn't say enough. Re-read the item as if you knew nothing else about the codebase — that's exactly the worker's position. Naming the crate, folder, or file in the prompt fixes this more reliably than any flag does.
Following it through to CI
If your task prompts push branches and open pull requests, --watch keeps
the fleet run alive afterward, watching each branch's CI until it finishes
and checking pull request status through gh. It exits with an error if any
watched branch ends up red.
stella --spend-limit 8 fleet --plan cleanup.toml --watchIt needs gh installed and signed in, and it can only watch a branch once
it's actually pushed — there's nothing to watch on a branch that never left
your machine.
Next
The concept page: how waves are scheduled, the full plan schema, and how file claims work underneath.
The reference — every flag, the ledger layout, exit behavior.
One branch instead of many, driven to green by stella monitor.
Why per-worker budget is the number that matters, and where a stopped run leaves off.
Fix a failing CI run
A branch is red. Walk it to green with stella monitor — what it does each round, what happens when a fix doesn't hold, and what "green" means here.
Make a change in an unfamiliar codebase
A repository you have never read, and a change that has to land in it. Orient with the code graph, find the blast radius, and ship the change verified.