swarmlab
Run a swarmlab multi-agent experiment end to end with the swarmlab CLI. Use when asked to design, smoke, launch (locally or on HF Jobs), analyse, report on, or publish a swarmlab experiment, or to resume or fork a swarmlab run.
Source facts
- Repository
- huggingface/swarmlab
- Last source activity
- October 8, 2026 at 08:36
- Detected SKILL.md language
- English
- Stars
- 7
- Forks
- 1
Install options
The review-first prompt is selected by default. You can switch to a direct command or download a local copy.
Review the source files
Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.
Showing SKILL.md
SKILL.md
Source instructions · Read-only preview- name
- swarmlab
- description
- Run a swarmlab multi-agent experiment end to end with the swarmlab CLI. Use when asked to design, smoke, launch (locally or on HF Jobs), analyse, report on, or publish a swarmlab experiment, or to resume or fork a swarmlab run.
# swarmlab experimenter workflow
A swarmlab experiment runs LLM-agent swarms on a task world in rounds and logs every event. You change the
hypothesis in a YAML spec; the CLI does the execution. Every step below is a command; every
command takes `--json` (one JSON object on stdout) and exits 0 on success, 2 on an invalid spec,
1 otherwise. `swarmlab COMMAND --help` is the reference for flags.
Spend is real money. Each step that spends ends on a check you can see before the next one
starts: a run summary, an estimate, a replay that matches.
## Install
From a clone of the repo (`uv sync --extra anthropic --extra hub` gives the CLI as
`uv run swarmlab`), copy this file into your agent's skill directory:
| agent | user-level path |
|---|---|
| Claude Code | `~/.claude/skills/swarmlab/SKILL.md` |
| Codex | `~/.agents/skills/swarmlab/SKILL.md` |
| OpenCode | `~/.config/opencode/skills/swarmlab/SKILL.md` (it also reads `~/.claude/skills/`) |
```bash
for d in ~/.claude/skills ~/.agents/skills ~/.config/opencode/skills; do
mkdir -p "$d/swarmlab" && cp skill/SKILL.md "$d/swarmlab/SKILL.md"; done
```
Project-level copies work too: `.claude/skills/swarmlab/`, `.agents/skills/swarmlab/`,
`.opencode/skills/swarmlab/` inside the repo.
## First real run: the checklist
Size it to the budget first. Put the user's total in the spec as `budget: {total_usd: T}` (top
level): `swarmlab run` then never starts a run that could push the experiment past T. The
spend is shared through `runs/<experiment>.ledger.jsonl`, so separate invocations, parallel
shells and `swarmlab run --parallel N` all count against the same T (same `--out`). A run whose
`hard_usd` fits only after runs in flight end waits for them (`total cap: waiting to start ...`);
hard ceilings far above the real cost per run make runs wait, so size `hard_usd` from measured
spend.
- **Total under $2: the short checklist.** A separate smoke run and a second-agent review would
eat a large share of the money, and one round's worst-case estimate can be a fifth of the
total, so size the caps from measured spend, not from the estimate:
1. `swarmlab doctor SPEC.yaml` and `swarmlab validate SPEC.yaml` (items 1-2 below).
2. Dry run with `fake:reader` (item 3; free): checks the metrics and the replay. A dry arm
(only `fake:` models and scripted agents) needs no `hard_usd` and does not count toward
`total_usd`.
3. Prompt review without a second agent: `swarmlab prompts SPEC.yaml --arm A > A.txt` for
every arm, then `diff A.txt B.txt` -> only the manipulated text differs, no correctness
hints, every needed tool named.
4. Caps go in each arm's own `budget:` (`arms.A.budget: {soft_usd: 0, hard_usd: X}`), not the
top level: budgets are part of the spec hash, so changing a top-level cap later would make
the finished first arm look like a different run. Keep only `total_usd: T` at the top.
5. `swarmlab preflight SPEC.yaml --arm A` for every arm with a real model -> exit 0: one real
request per participant group with the arm's exact `extra` and tools; it shows the
provider's HTTP error if a body field is rejected (cents at most; `--max-usd` caps it).
6. First real arm = smoke = data, at N <= 6 agents, with `hard_usd` = 50% of the total and no
soft cap (`soft_usd: 0`: a soft cap at this size stops the run a round early):
`swarmlab run SPEC.yaml --arm FIRST --seed S` -> `end=max_rounds`. Note its actual spend
(`spend=$X` on the status line, `spend_usd` under `--json`) and X / rounds = its measured
per-round cost. The run is kept as the arm's data, not repeated.
7. Set the second arm's `hard_usd` from that measurement: about 1.5 x the first arm's actual
`spend_usd` (same model and N, one variable changed), and no more than T minus that spend;
`soft_usd: 0`. `swarmlab run SPEC.yaml` then skips the finished first arm and prints an
`existing:` line with its actual spend and per-round cost; `total_usd` counts that spend
(including anything added by `resume`) before starting the second arm.
8. A `hard_ceiling` end (round in flight discarded) or `hard_ceiling_probes` (last round kept,
some probes skipped): `swarmlab resume RUN_DIR --add-budget D` if T minus the spend so far
allows (resume itself does not check `total_usd`), else report the rounds you have.
9. `swarmlab report runs/ --out report.md`; it lists skipped probes per arm.
- **Total of $2 or more: the full checklist below**, all eleven items.
Do these in order. Each item is done when its check holds; a failing check is fixed before the
next item.
1. **Environment.** `swarmlab doctor SPEC.yaml` -> exit 0 (keys, extras, provider reachability,
clean git). A new experiment starts from `swarmlab init NAME` (writes `NAME.yaml`, `NAME.py`)
in a project directory outside the swarmlab checkout (`uv run --project <repo> swarmlab ...`
from there); `swarmlab run` refuses to write `runs/` inside the checkout unless `--out` is
given.
2. **Spec.** `swarmlab validate SPEC.yaml` -> every arm resolves; its table shows each arm's
agents, models, soft/hard/measurement caps, probes and metrics: check them against the plan.
`swarmlab spec-reference` lists every YAML key and its shape (e.g. `medium: {topology:
{type: gossip, params: {k: 1}}}`, per-arm `budget:`); a shape error names the key and the
shape expected there.
`swarmlab metrics` lists every metric name with what it measures (e.g. `comm.post_rate`,
`comm.posts_per_round` per agent).
Model ids come from
`swarmlab models --tools --search QWEN` (prices are looked up, never typed). Every arm that
calls a paid model has `budget: {hard_usd: X}`; `hard_usd: 0` means no ceiling.
3. **Dry run with scripted agents.** Copy the arm into a dry-run arm whose participants are
`{type: evidence_aggregator, count: N}` or `{type: llm, params: {model: "fake:reader"}}`, then
`swarmlab run SPEC.yaml --arm dry --seed 0 --yes` -> `status=ended`, metrics present, no spend.
`swarmlab replay RUN_DIR` -> exit 0.
4. **Prompts review by a second agent.** `swarmlab prompts SPEC.yaml --arm A > prompts-A.txt` for
every arm (no model call). Hand the files to a reviewer agent that did not write the spec; it
checks that each arm's system prompt and round-1 message differ only in the manipulated
variable, carry no correctness hints, and name every tool the arm needs. Fix the spec and
re-render until the reviewer signs off.
5. **Preflight, then smoke at N=4 with a hard ceiling.** First `swarmlab preflight SPEC.yaml
--arm A` for every arm with a real model -> exit 0, every group `tool call parsed` (one real
request per participant group with the arm's exact `extra` and tools; a rejected body field
shows the provider's HTTP error here instead of as a run of errored turns). Then a smoke arm: the real model, `count: 4`,
`options: {max_rounds: 3}`, `budget: {hard_usd: 0.50, measurement_usd: 0.10}`.
`swarmlab run SPEC.yaml --arm smoke --seed 0` -> `end=max_rounds` (not `hard_ceiling`), then
`swarmlab report RUNS_DIR` -> tool protocol health shows no errored turns and few `length`
/ `max_tokens` finishes (truncated replies). A `hard_ceiling` end (the round in flight was
discarded) or `hard_ceiling_probes` (the last round was kept, some of its probes were
skipped) means raise the budget or cut rounds; `swarmlab resume RUN_DIR --add-budget D`
continues the same run with D more dollars (`--budget-hard X` sets the run's
total, spend so far and discarded rounds included).
6. **Estimate.** `swarmlab estimate SPEC.yaml --from runs/SMOKE_RUN_ID` -> total for every arm
x seed, priced with the smoke run's measured calls per turn, prompt-token growth per round,
completion tokens and probe cost; without `--from` the worst case (each group's `max_calls`,
else the runner cap) overstates real spend 4-6x. The user approves the total before launch.
7. **Launch.** Local: `swarmlab run SPEC.yaml` (every arm x seed; asks before spending; already
finished runs are skipped). Self-hosted model on HF Jobs (spec models `vllm:<model>`):
`swarmlab job run SPEC.yaml --model ORG/MODEL` prints the plan and estimate; add `--launch`
after approval.
8. **Watch.** Local runs print a summary per run. Jobs: `swarmlab job status JOB_ID`,
`swarmlab job logs JOB_ID --follow`.
9. **Fetch** (jobs): `swarmlab job fetch RUN_ID --out runs/` for each run id the log names ->
`swarmlab replay runs/RUN_ID` exits 0.
10. **Report.** `swarmlab report runs/ --out report.md` -> one row per arm (dry runs on `fake:`
models are listed as simulated and kept out of the tables and spend); read it before
claiming any effect, and say how many seeds stand behind each number.
11. **Publish.** `swarmlab publish runs/ --repo OWNER/EXPERIMENT` -> private dataset repo with
tables, sessions, raw logs and a card. `swarmlab view RUN_DIR --publish OWNER/EXPERIMENT`
adds the replay page. `--public` (makes the repo public and tags it for the Hub's trace
viewer) only when the user asks for it, after they have read the traces.
## Rules
- **Change the spec, never the code, to change the hypothesis.** One arm per condition; arms of
one experiment differ only in the manipulated variable. `swarmlab prompts` shows what differs.
- **Change prompts through params, then diff.** An `llm` group's `system_prompt_append: "..."`
adds text after the default prompt's task section (before the tool list); `system_prompt`
replaces the whole Jinja2 template (variables `agent`, `role`, `description`, `tools`,
`system_prompt_append`; `file:<path>` works). Prefer the append: copying the template invites
accidental differences. `swarmlab prompts SPEC.yaml --arm A > A.txt` per arm, then `diff`, must
show only the manipulated text. The default prompt already lists every tool (`read_board`,
`post`), so a "mention the board" arm is a nudge, not awareness.
- **Resume or fork instead of rerunning.** A crashed or budget-ended run continues with
`swarmlab resume RUN_DIR [--add-budget D]`; a counterfactual from round R is
`swarmlab fork RUN_DIR --at R --spec edited.yaml`. Both reuse the inference cache, so the
shared prefix costs nothing.
- **Re-running a finished run is a skip.** `swarmlab run` skips run dirs with the same spec hash;
`--rerun` writes `RUN_ID__rN` beside it when you need a repeat. After a spec edit the hash
differs and `run` refuses that run (both hashes in the error, outcome `failed`): `--rerun`
(new `__rN` dir) or `--out` elsewhere; nothing is overwritten.
- **Replay before you trust a run.** `swarmlab replay RUN_DIR` recomputes metrics and score from
the log with zero provider calls and exits 1 on any mismatch.
- **Analyse from exports.** `swarmlab export RUN_DIR` writes `export/tables/<family>.parquet`
(turns, tool_calls, posts, deliveries, reads, actions, inference, probes, metrics,
interventions, rounds, run, other, discarded_inference; keys `experiment, arm, seed, run, round,
agent`; `run.json` has `spend` from the ledger and `spend_discarded_usd`) and one pi
session per agent in `export/sessions/` for reading a conversation turn by turn.
- **Look at one run by eye** with `swarmlab view RUN_DIR` (writes `view.html`, no server).
- **Restore a published run** with `swarmlab fetch-published OWNER/EXPERIMENT RUN_ID --out runs/`;
it replays like a local one.
- **Slow providers and sequential commit do not mix**: `commit: immediate` puts every call on the
critical path; keep it to fast providers or small N (`swarmlab estimate` does not model this).
- **Reasoning models need room**: set `max_tokens: 2048` or turn thinking off through `extra`,
in the serving provider's own field: `{chat_template_kwargs: {enable_thinking: false}}` for
Qwen3 on vLLM/SGLang/DeepInfra, `{reasoning_effort: "none"}` on Cerebras (which rejects
`chat_template_kwargs`; Qwen3.8 there reasons by default). `swarmlab preflight` checks the
field is accepted; `swarmlab report` counts `length` and `max_tokens` finishes.
- **An `errored` run is not data.** `WARNING: E/T turns errored (first error: ...)`, outcome
`errored` and exit 1 mean most turns raised (usually a provider error); fix the spec (run
`preflight`) and rerun with `--rerun`, do not analyse it.
View on GitHub