Runbook: Fleet Loop Review¶
This is the first runbook in
docs/runbooks/. A guide (docs/guides/*) explains a feature; a runbook documents an operational procedure executed on a cadence, with a checklist and a recorded baseline. Every runbook leads with the same five sections, in this order: Purpose · Cadence · Phases · Baseline / Re-measure contract · In-scope rule.
Purpose¶
Other projects on this machine that use little-loops act as a fleet-wide test bed for
this repo's built-in loops (loops/*.yaml, ~104 at time of writing). Their real run
history — successes, stalls, and failures — is unbiased evidence this repo cannot self-grade:
a loop only knows it works because other people's projects keep converging when they run it.
This runbook turns "use other projects' logs to continuously fix and improve this repo's built-in loops" into a repeatable, documented cycle instead of an ad-hoc investigation:
HARVEST (cross-project, read-only)
→ ATTRIBUTE each failure/stall to a built-in loop
→ DIAGNOSE + FIX (in this repo)
→ RE-MEASURE (next harvest's failure delta = acceptance signal)
The final arrow is mandatory. Per the meta-loop rules in .claude/CLAUDE.md, a harness
"improvement" is only proven when an external measure moves — here, the fleet's failure rate
for that loop, not a self-graded claim that a fix "looks right."
Cadence¶
On-demand, run manually — or as one cycle of the fleet-loop-improve built-in meta-loop.
This is not a cron job. Run it whenever you suspect a built-in loop is misbehaving, before/after
landing a loop fix, or periodically as general maintenance hygiene.
The automated form of the whole cycle is:
ll-loop run fleet-loop-improve # from this repo only; preflight refuses elsewhere.
# A flagged loop whose YAML is dirty is skipped, not fatal.
It runs HARVEST with the flags above, applies the ATTRIBUTE rule, runs DIAGNOSE inside each
project that produced the runs, has the loop-specialist agent write its artifact and apply
the smallest fix, gates the fix with ll-loop validate plus the built-in loop tests, commits,
and records the fix as pending in .loops/diagnostics/fleet-loop-improve-ledger.jsonl. Its
RE-MEASURE is deliberately one invocation late: the next run's measure_externally state
compares the fresh harvest against each pending fix's recorded baseline (see § 4). See
docs/guides/LOOPS_REFERENCE.md#fleet-loop-improve
for its states and knobs. A scheduled capture (cron) remains deferred.
Phases¶
1. HARVEST (cross-project, read-only)¶
Run from this repo (little-loops) so the source repo's own dev-iteration runs don't
dominate the sample and stale pytest fixture projects don't spam stderr:
This runs the loop-fleet, scan-failures, and sequences collectors in-process against every
other project with ll activity, derives a zero-run-built-ins list, validates every flagged
loop's YAML, and writes a stamped report plus a JSON sidecar under .loops/diagnostics/ (see
the Baseline / Re-measure contract below). It never mutates anything — HARVEST is read-only by
design.
Useful variations:
--threshold N(default50) /--min-runs N(default3) — tune the flagging rule (see ATTRIBUTE below).--appendix-top N(default20,0= unlimited) /--no-appendices— thescan-failuresandsequencesappendices are context, not flagging inputs, and can be expensive (~45s combined on a full fleet); skip them for a fast RE-MEASURE-only run.--exclude-project DIR(repeatable) — exclude any project, not just.; useful when a project's runs are known-noisy (e.g. mid-refactor) and would skew the sample.--json— prints the sidecar dict to stdout and writes no files. Use this for scripting; it never enters the baseline chain.
See docs/reference/CLI.md for the full flag reference.
2. ATTRIBUTE¶
The flagging rule is computed entirely from the loop-fleet aggregates — scan-failures and
sequences are unattributed context appendices, never flagging inputs (they carry no
loop-name key). A loop is flagged when all of the following hold:
attribution == "builtin"(nevercustomorshadowed— see the In-scope rule below), andruns >= --min-runs(default3), andsuccess_pct < --threshold(default50) ortop_outcomeis one of the four failure outcomes:error,max-steps,stalled,failed.
The full outcome vocabulary emitted by _derive_loop_outcome() is:
Only error, max-steps, stalled, and failed count as flagging failures. interrupted
and signal are operator/infra exits (user stop, kernel signal), not loop-logic failures, so
they are deliberately excluded from the outcome clause — but they still count against
success_pct (which is converged / runs), so a loop dominated by interrupted runs can still
surface via the threshold clause. There is no dismissal list: a false positive is handled by
skipping it in DIAGNOSE and noting why under the report's "Reviewed, not fixed" section.
The report also lists two non-flagged categories for context:
- Zero-run built-ins — built-in loops nobody ran anywhere in the harvested window.
- Shadowed built-ins — see the In-scope rule below.
3. DIAGNOSE + FIX¶
For each flagged loop, the report gives the exact command to run from inside the project that produced the failing runs — not from this repo:
This cd is required, not cosmetic: diagnose-evaluators and calibrate-budget only read the
current project's local .loops/.history/ under its config-resolved loops_dir, and
ll-loop has no --project/--loops-dir flag. Run from this repo, both commands print "No
history" for any loop that only ran elsewhere. The project field on each flagged loop's
records (absolute path, copy-pasteable) is exactly what the report lists for this purpose.
Other projects' .loops/.history/ is gitignored and prunable, so diagnose promptly after
harvest.
Once you have a diagnosis, the actual fix work — classifying the failure mode, proposing a
YAML change, and verifying it with a real iteration — is the loop-specialist
agent's job. It also writes its own diagnosis artifact to
.loops/diagnostics/<loop-name>-<UTC-timestamp>.md, alongside this runbook's
fleet-review-<stamp>.md/.json in the same directory.
Fixes land in this repo's loops/*.yaml, go through normal review, and ship to the fleet on
the consuming projects' next little-loops update.
4. RE-MEASURE¶
Run HARVEST again later (after the fix has had time to accumulate fresh runs across the
fleet). The new report's Delta vs baseline table diffs the current sidecar against the
newest prior one, showing Δsuccess_pct, Δruns, Δconverged, and Δ for each failure outcome, per
loop. A positive Δsuccess_pct (or a failure outcome dropping out of top_outcome) for the
loop you fixed is the acceptance signal — proof against real fleet data, not a self-graded
claim that the fix "should" help. This is the meta-loop "measure-externally" step required by
.claude/CLAUDE.md.
fleet-loop-improve performs this step automatically at the start of every run: for each
ledger entry still pending, it takes the new sidecar's runs/converged minus the values
recorded when the fix landed and, once at least min_new_runs (default 3) fresh runs exist,
records improved / regressed / unchanged. A harvest with a different window_days or
--exclude-project set is reported as not comparable and the entry stays pending.
Regressions are recorded and printed, never auto-reverted — reverting is your call.
Baseline / Re-measure contract¶
- The baseline is a machine-local, gitignored JSON sidecar:
.loops/diagnostics/fleet-review-<stamp>.json, where<stamp>isYYYYMMDDTHHMMSSZ(UTC), e.g.fleet-review-20260902T214729Z.json. It is never committed —.loops/is gitignored and excluded fromll-verify-private-refs, and the sidecar may quote other projects' absolute filesystem paths (projects,excluded_projects, per-loop project breakdowns). This is by design: the baseline is per-machine, not shared across contributors. - Every
fleet-reviewrun (except--jsonruns, which write nothing) automatically loads the lexically-newest prior sidecar in.loops/diagnostics/— excluding the sidecar it is about to write itself — and renders the delta table against it. You never point at a baseline file by hand. - Two runs are only comparable if their run population matches. Changing
--window-days/--since/--until, or the--exclude-projectset, changes which runs feed the aggregates, so a delta against a differently-scoped prior run is meaningless. A comparability warning fires automatically in the report's Summary whenever the prior sidecar's window fields or itsexcluded_projects/projects_scannedlists differ from the current run's — you don't need to remember to check this yourself, but you do need to read the warning when it appears rather than trusting the delta table blindly. - A run with no prior sidecar in the directory renders a "no prior baseline" line instead of a delta table — this is expected on the very first run.
fleet-loop-improvewrites its harvest through the same command, so a loop-driven sidecar is the newest baseline for your next manual run too. The loop keeps its own per-fix baselines in the ledger and does not depend on this delta table.
In-scope rule¶
Only this repo's built-in loops are fixed here. Cross-project data is evidence, never a
mandate to change another project's files — a project's own custom loop, or its own modified
copy of a built-in, is fixed in that project, not back-ported into this repo. This is a
mechanical filter, not a judgment call: every harvested run carries an attribution of
"builtin", "custom", or "shadowed", and only "builtin" runs ever feed the flagging rule.
"Shadowed" is the trap this rule exists to prevent. ll-loop install (and manual copying)
can create a project-local .loops/<name>.yaml or .loops/<name>.fsm.yaml file with the same
name as a built-in loop. Because resolve_loop_path prefers a project's own copy over the
package copy, every run of that name in that project actually executes the project's
modified copy, not this repo's loop — even though the run history has no other way to tell
the two apart (events carry only loop and ts, no provenance). Attributing those runs as
plain "builtin" would flag this repo's loop against failures it never produced. fleet-review
detects this at harvest time (a .loops/<name>.yaml or .loops/<name>.fsm.yaml file present in
the project) and labels those runs "shadowed" instead: they are never flagged, never
counted as zero-run evidence (a loop that only ran as a shadowed copy is still zero-run
here), and are listed separately in the report's "Shadowed built-ins" section (loop, absolute
project path, run count) so you can see the divergence without misattributing it. If a shadowed
copy is genuinely broken, that is a fix for the project that owns the copy — not this repo.
Reports and their sidecars live in .loops/diagnostics/, alongside the
loop-specialist agent's own per-loop diagnosis artifacts —
both are recurring, tool-generated, gitignored, and private-refs-exempt, which is why this
runbook's output lives there rather than in postmortems/ (reserved for ad-hoc run forensics).
See also¶
docs/reference/CLI.md— fullll-logs fleet-reviewflag reference and examples.agents/loop-specialist.md— the fix step: failure-mode taxonomy, diagnosis artifact structure, and verification protocol..claude/CLAUDE.md§ Loop Authoring — the meta-loop rules this runbook's RE-MEASURE step satisfies (diagnosis-first, non-LLM evaluator, per-run artifact isolation).