When to run a panel, a jury or a fanout of sub-agents, the four files that define one, seeded dealing, the 26-unit budget, honest tallies, and a worked buyer panel with real output.
A swarm is many sub-agents doing the same kind of work once each: thirty simulated buyers reading a pitch, three jurors reading it through one lens each, one writer per page of a site. The moderator is your assistant; it never does a unit’s work. bmad-plus swarm is the bookkeeper: it validates the files, deals the units, writes one brief per sub-agent, checks every answer file and counts only the valid ones. It never calls a model, never opens a network connection and never starts a process. This page is for anyone who wants to test a message, a page or a plan against many readers, or to split repeated work across sub-agents under a budget.
Ask first, then swarmThe intake questions, the budget of 26 and the approval step, waves of sub-agents, and a tally that says simulated units are not people.Transcript
Before a pack acts, it asks. The intake puts a few questions at a time and marks the must-haves. Where you have no answer, it proposes a default, or records the question as unknown with its risk. The answers become a brief, and nothing in it is invented. Next, the swarm. By default, a run may use 26 sub-agents before the assistant must ask you. Ask for more, and the run waits, marked needs approval, until a person says yes. Once approved, sub-agents work in waves of ten by default, each reading one brief and writing one answer. The runtime validates every answer file and counts only the valid ones. This tally is demo data, and it opens with its disclosure: simulated units are not people. Read the rates as an upper bound, and confirm them with real people.
A panel gives you the reading of a model playing buyers. Treat its rates as an upper bound to confirm with real people, and its quotes as what a simulated unit wrote, never as testimonials or user interviews. Every tally starts with this sentence.
A profile: units are dealt by exact quotas, from a seed.
Simulated buyers, readers or users reading a pitch, a price, a page.
A rate by group, counts, verbatim quotes, numbers.
jury
A list in the definition: one unit per entry, each with a lens.
An adversarial read of a pitch, a page or a plan, one angle per juror.
Counts and quotes per lens.
fanout
A units file you write at init: one unit per entry, each with a disjoint write scope.
Pages to write, modules to document, scenes to prepare, one sub-agent each.
A rate and numbers; can be exported as a durable Nexus plan.
A panel and a jury run as session sub-agents: your assistant launches them, waits, and reads their files. A fanout can run the same way, or be exported with swarm plan as a durable Nexus plan when the work must survive the session, needs an independent check per unit, or edits the repository.
When you can ask real people. A swarm rehearses; it does not measure.
For two or three tasks. Ask the agent directly, or use Nexus parallel.
For work where one result depends on another. Units are independent by construction.
To generate paid media. Image, video and audio generation is never covered by the budget: the assistant asks every time.
In 0.23.0 no pack ships a swarm definition: the runtime and the moderator skill are Core, and the definitions used on this page are test fixtures in the BMAD+ repository (tests/fixtures/swarm/). A pack, or you, supply the definition. These files are not in the npm package, so this page prints each one in full: you can create them as they are.
definition + profile|list|units + context
|
init ---- over budget? exit 2 "needs approval": ask a human, re-run with --approved
|
cards.json (frozen)
|
briefs ---- one brief per unit, in waves
|
one sub-agent per brief -> answers/<unit>.json
|
check ---- missing / invalid, with reasons
| (second chance once; save files a printed reply)
tally ---- tally.json + tally.md, disclosure first
01
init
Validates the definition, the profile or units file, the context files and the brief template; deals or lists the cards; applies the budget. A panel needs --n (1 to 1000); the seed is drawn and recorded when you do not give one. A run larger than the budget stops here with exit code 2.
Renders one brief per unit that has no valid answer yet, from the frozen template, the card and the frozen contexts, and prints the waves (10 units per wave unless --wave says otherwise) with the brief path of each unit.
In your project terminal
npx bmad-plus swarm briefs RUN
03
Launch a wave
Your assistant starts one sub-agent per brief with exactly one instruction: read the brief and follow it exactly; it is the whole instruction. It does not summarise the brief, add context or merge two units into one sub-agent. It waits for the whole wave before the next.
04
check
Lists every unit whose answer is missing or invalid, with the reason. Exit code 0 when all units are valid, 1 otherwise.
In your project terminal
npx bmad-plus swarm check RUN
05
Second chance, once
Run briefs again (it only renders units without a valid answer) and relaunch those units. A unit that printed its answer instead of writing it is filed with save RUN UNIT --input FILE, where the file holds that unit’s reply verbatim. A unit still missing after the second wave stays missing.
In your project terminal
npx bmad-plus swarm save RUN B005 --input swarm-inbox/B005.txt
06
tally
Counts valid answers only and writes tally.json and tally.md. Units without a valid answer are listed as skipped and counted in the denominator line. The disclosure is the first line of tally.md.
In your project terminal
npx bmad-plus swarm tally RUN
07
plan (fanout only)
Instead of running the sub-agents inside the session, exports the fanout as a Nexus plan. See the Nexus page.
In your project terminal
npx bmad-plus swarm plan RUN --out .bmad-plus/swarm/reports/RUN/plan.json
swarm inspect RUN prints the state of a run (units, briefed, valid, invalid, missing) at any time and writes nothing. Exit codes: 0 done, 2 needs-approval (a human must say yes first), 1 incomplete or error. With --json, every answer carries schemaVersion: 1 and a status.
Three keys in the swarm block of _bmad/config.yaml set the limits. The installer writes the block with its defaults; a project updated from an earlier version has no block and gets the same defaults.
The swarm block
Key
Default
Meaning
budget
26
Sub-agents one run may use before the assistant must ask you. Integer from 1 to 1000.
wave
10
Sub-agents launched together. The assistant waits for a wave before the next. Integer from 1 to 1000.
media_credits
ask
Paid generation (images, video, audio) is never approved by a budget. The only accepted value is ask.
When n is larger than the budget and --approved is absent, init creates the run with the status needs-approval, exits with 2 and writes no brief. The assistant asks you one sentence with the number ("This panel needs 30 sub-agents; the budget is 26. Run it?"). Only after your yes does it re-run init with --approved and the same seed. It never passes --approved on its own judgement. The refused run is terminal: later commands on it answer needs-approval and write nothing.
Refused, then approved: a panel of 30 against a budget of 26
$ npx bmad-plus swarm init --definition swarm/buyer-panel.json --profile swarm/profile.json --context pitch=swarm/pitch.md --n 30 --seed 7buyer-panel-20261019-g1i9: needs approval. The run needs 30 sub-agents and the budget is 26: ask the user, then re-run init with --approved --seed 7.# Exit code 2. No brief was written.$ npx bmad-plus swarm init --definition swarm/buyer-panel.json --profile swarm/profile.json --context pitch=swarm/pitch.md --n 30 --seed 7 --approvedbuyer-panel-20261019-vqkf: panel of 30 units, budget 26, approval user.Seed 7 (same seed, same cards).Next: bmad-plus swarm briefs buyer-panel-20261019-vqkf
The runtime refuses an unknown field, a wrong type and a bound breach; it never guesses. Every path is project-relative with / separators. The brief template path is relative to the definition file.
1 to 64 lowercase letters, digits or -. Opens the run id.
kind and units.source
The pair must match: panel with profile, jury with list, fanout with input.
units.idPrefix
1 to 4 letters. Required for a panel; optional for a jury whose entries have no id; absent for a fanout.
units.list
Jury only: 1 to 1000 entries, each with a lens text and any other text, number or boolean fields; an optional id.
pack
Optional pack id, recorded in the run.
title
Heading of the tally.
brief.template
Plain relative path to a Markdown file, no . or .. part.
brief.context
Names of the context files the moderator must pass at init (--context NAME=FILE). Each is required; at most 20.
answer.required
Fields that must be present. id is implicit and always required.
answer.fields
At most 50 fields, each typed (see the next table). A sub-agent may not write a field that is not declared.
tally
rate (a boolean field), groupBy (up to 5 card fields every card carries), counts (enum fields), quotes (string field to quotes kept per group, 1 to 10), numbers (integer or number fields).
disclosure
1 to 500 characters, printed first in every tally and in the briefs output.
Answer field types
Type
Options
Accepted value
boolean
none
true or false.
integer
min, max
A whole number within the bounds.
number
min, max
Any number within the bounds.
string
min, max (length)
Text; default 0 to 4000 characters, ceiling 20000; min is checked on the trimmed text.
enum
values (1 to 50 distinct)
One of the listed values.
list
maxItems (default 50, ceiling 200)
A list of non-empty texts, each up to 1000 characters.
What is being judged and who judges it. Documentation for readers of the file.
dimensions
1 to 10 dimensions, each with a name and 1 to 50 values. The shares of one dimension add up to 1.
values[].value, share
The label of a value and its share of the panel.
values[].ranges
Optional numeric ranges sampled when that value is dealt, such as "income": [38000, 85000]. Every value of a dimension declares the same range names.
values[].note
Optional sentence. It becomes the card field <dimension>_note (empty for values without one).
ranges (top level)
Ranges sampled once per card. Integer bounds deal integers; other bounds deal numbers with two decimals.
pools
Named lists of sentences. One entry of each pool is dealt per card.
Card fields are the dimension names, their range names, the top-level range names, the pool names and the notes. Names must be distinct, and id, scope and lens are reserved. Names of people are not dealt: a brief that wants one draws it from a pool the pack supplies.
id: 1 to 64 lowercase letters, digits, _ or -. It becomes a Nexus task id.
scope: one path or a list of up to 20, portable and project-relative, never .git or .bmad-plus. Scopes are pairwise disjoint: no scope may contain another.
Every other field must be a text, number or boolean; it becomes a card field available as {{card.field}} in the brief.
The definition that goes with this units file has no profile; init takes the units with --units, for instance npx bmad-plus swarm init --definition swarm/page-fanout.json --units swarm/units.json. This is the definition of the site-pages fanout used in the example at the end of this page, followed by its brief template.
swarm/page-fanout.json (the definition)
{
"schemaVersion": 1,
"id": "site-pages",
"kind": "fanout",
"title": "One sub-agent per page of the site",
"units": { "source": "input" },
"brief": { "template": "briefs/page.md", "context": [] },
"answer": {
"required": ["id", "done", "words"],
"fields": {
"done": { "type": "boolean" },
"words": { "type": "integer", "min": 0 },
"note": { "type": "string", "max": 200 }
}
},
"tally": { "rate": "done", "numbers": ["words"] },
"disclosure": "Each page was written by a separate sub-agent from the same brief; read them together before publishing."
}
swarm/briefs/page.md (the template)
# Page {{unit.id}}
Write the page "{{card.title}}" under `{{card.scope}}`, {{card.tone}} in tone. Touch nothing
outside that folder.
When the page is written, write a JSON object to `{{answer.path}}` with `id` ("{{unit.id}}"),
`done`, `words` (the word count) and an optional `note`.
{{disclosure}}
4. The brief template (Markdown) and the contexts#
Placeholders: nothing else is allowed between double braces
Placeholder
Renders
{{card.<field>}}
The card field; a list renders comma-separated.
{{context.<name>}}
The context file passed at init, verbatim and frozen in the run.
{{answer.path}}
The absolute path of the answer file the sub-agent must write.
{{run.id}}, {{unit.id}}
The run id and the unit id.
{{disclosure}}
The definition’s disclosure sentence.
Warning· The runtime filters nothing
Card values, pool entries, listed unit fields and context files reach the sub-agent word for word. A pool entry that reads like an instruction lands in the brief as an instruction. The pack owns its pools, unit fields and templates; you own the contexts you pass. Read a context before passing it, and never pass a file you have not read: it is included in every brief, secrets and instructions alike. An unknown placeholder, a card field some card lacks, an undeclared context or a literal {{ in the template makes init fail.
The same profile, the same --n and the same --seed always give the same cards. Each dimension is distributed by exact quota (largest remainder, ties by declaration order): with shares of 0.45, 0.40 and 0.15, twenty cards hold 9, 8 and 3 units of those values whatever the seed. The column of values is then shuffled by a generator based on SHA-256, and card k takes position k of every column; ranges and pool entries follow, in order. The seed (0 to 2147483647) is recorded in run.json.
This is what makes a before and after comparison honest. To test two versions of a pitch, run the same profile with the same --n and --seed and change only the context file: the buyers are the same, so the only thing that changed is the pitch. Unit ids are the prefix plus the position padded to at least three digits (B001, B030, B1000).
An answer counts only if answers/<unit>.json is a regular file, parses as a JSON object, carries id equal to the unit id and every required field, declares no unknown field and satisfies every type and bound. check reports a unit as missing (no file) or invalid (with the reasons). save applies the same rules to the first balanced JSON object found in a printed reply, and refuses when none is found, when it fails validation (a wrong id included), when the unit already has a valid answer and on a run that still needs approval. A refused reply changes nothing on disk.
A reply is filed, another is refused
$ npx bmad-plus swarm save buyer-panel-20261019-2ftt B005 --input swarm-inbox/B005.txtbuyer-panel-20261019-2ftt: answer of B005 filed at /work/demo/app/.bmad-plus/swarm/runs/buyer-panel-20261019-2ftt/answers/B005.json$ npx bmad-plus swarm save buyer-panel-20261019-2ftt B006 --input swarm-inbox/B006.txtSwarm: Reply of unit B006 refused: field "buys" must be true or false.# The first file holds a sentence followed by a valid JSON object; the second holds an object whose buys field is the text "maybe". Exit codes 0 and 1.
npx bmad-plus swarm save buyer-panel-20261019-2ftt B005 --input swarm-inbox/B005.txt
npx bmad-plus swarm save buyer-panel-20261019-2ftt B006 --input swarm-inbox/B006.txt
Output captured from a real run. Source: bmad-plus 0.23.0, 2026-10-19
Never answer for a unit: not by writing an answer file, not by "fixing" an invalid one, not by filing with save something a sub-agent did not print.
Never edit a brief. A change belongs in the template or the definition, then in a new run.
The disclosure comes once and first: the tally starts with it, and the assistant repeats it once in its report, before any figure.
Rates are an upper bound to confirm with real people. Quotes are what a simulated unit wrote.
The seed travels with the result: same seed, same cards, so a comparison is a comparison.
A valid answer is a well-formed one. Nothing proves that a sub-agent read its brief, stayed in its card or wrote the truth; quality is your reading of the tally.
Replies are filed from outside the state tree: save --input takes a project file the host wrote (for instance in swarm-inbox/), never a path under .bmad-plus/.
The scenario: a coffee bar called Orbit wants to know how an urban professional reacts to its pitch. The files below are the test fixtures of the BMAD+ repository, used here unchanged: the definition, the profile and the brief template. The pitch itself is two sentences.
{
"schemaVersion": 1,
"subject": "Orbit espresso bar",
"target": "Urban professionals who buy coffee most days",
"dimensions": [
{
"name": "segment",
"values": [
{ "value": "early career", "share": 0.45, "ranges": { "income": [38000, 85000] } },
{ "value": "mid career", "share": 0.4, "ranges": { "income": [85000, 150000] } },
{ "value": "high earner", "share": 0.15, "ranges": { "income": [150000, 210000] } }
]
},
{
"name": "behaviour",
"values": [
{ "value": "daily regular", "share": 0.2, "note": "same drink, same place, most mornings" },
{ "value": "explorer", "share": 0.5 },
{ "value": "occasional", "share": 0.3, "note": "a treat, not a routine" }
]
}
],
"ranges": { "age": [24, 38], "walk_minutes": [1.5, 12] },
"pools": {
"objection": ["too steep for a drink", "my place is closer", "I do not know the brand"],
"habit": ["you pass the street on the way to work", "you order the same thing every day"]
}
}
swarm/briefs/buyer.md (the template)
# You are unit {{unit.id}} of run {{run.id}}
You are one simulated buyer. Stay inside this card for the whole task.
- Segment: {{card.segment}}, income {{card.income}}
- Behaviour: {{card.behaviour}} ({{card.behaviour_note}})
- Age {{card.age}}, {{card.walk_minutes}} minutes on foot from the bar
- Your first objection: {{card.objection}}
- Your habit: {{card.habit}}
## The pitch
{{context.pitch}}
## Your answer
Write a JSON object to `{{answer.path}}` with the fields `id` (exactly "{{unit.id}}"), `buys`,
`reason_code` (price, habit, need, trust or other), `reason` (your own words, 600 characters at
most) and, when you have one, `price_floor`.
{{disclosure}}
swarm/pitch.md (the context)
Orbit opens at 7, two streets from the station. A flat white is 4.20, the loyalty card gives
the tenth one free, and the bar keeps your usual order under your name.
Seed 7 and 30 units deal this first card (from cards.json). The brief the sub-agent receives is the template with this card, the pitch and the answer path filled in.
The card of unit B001
{
"id": "B001",
"segment": "mid career",
"income": 114720,
"behaviour": "occasional",
"behaviour_note": "a treat, not a routine",
"age": 32,
"walk_minutes": 8.99,
"objection": "I do not know the brand",
"habit": "you order the same thing every day"
}
In the transcript below, the assistant would launch 30 sub-agents in three waves. To show the files, the tool and the tally without any model, five of the answers were written by hand for this demonstration (four as files, one through save), and a sixth reply was refused. So the tally counts 5 of 30 units and names the 25 others. The quotes below are therefore not what a model wrote either: they are demonstration text.
The demonstration answers (one file each in answers/; B005 and B006 are replies filed with save)
answers/B001.json { "id": "B001", "buys": true, "reason_code": "habit", "reason": "The bar keeps my usual order under my name, so I would stop at a tenth coffee for free.", "price_floor": 3.8 }
answers/B002.json { "id": "B002", "buys": false, "reason_code": "price", "reason": "A flat white at 4.20 is more than my usual place, and it is closer.", "price_floor": 3.2 }
answers/B003.json { "id": "B003", "buys": true, "reason_code": "need", "reason": "Opening at seven fits my route to the station, and I order the same drink every morning.", "price_floor": 4.5 }
answers/B004.json { "id": "B004", "buys": false, "reason_code": "trust", "reason": "I do not know the brand and nothing in the pitch tells me why I should switch.", "price_floor": 3.5 }
swarm-inbox/B005.txt Here is my answer as a buyer.
{ "id": "B005", "buys": true, "reason_code": "habit", "reason": "Same drink, same place, most mornings: the loyalty card would pay for itself.", "price_floor": 4.2 }
swarm-inbox/B006.txt { "id": "B006", "buys": "maybe", "reason_code": "other", "reason": "I could go either way." }
From briefs to tally, with five hand-written demonstration answers
$ npx bmad-plus swarm briefs buyer-panel-20261019-2fttbuyer-panel-20261019-2ftt: 30 of 30 units need a sub-agent, in waves of 10.Wave 1: B001 /work/demo/app/.bmad-plus/swarm/runs/buyer-panel-20261019-2ftt/briefs/B001.md B002 /work/demo/app/.bmad-plus/swarm/runs/buyer-panel-20261019-2ftt/briefs/B002.md# ... units B003 to B010, then waves 2 and 3 (B011 to B030) ...Each sub-agent: Read the brief and follow it exactly; it is your whole instruction.Disclosure: Simulated units are not people. Read rates as an upper bound and confirm with real ones.# Four answer files (B001 to B004) are now written by hand into answers/.$ npx bmad-plus swarm check buyer-panel-20261019-2fttbuyer-panel-20261019-2ftt: 4 of 30 valid answers. B005 missing: no answer file B006 missing: no answer file# ... and so on up to B030. Exit code 1.$ npx bmad-plus swarm tally buyer-panel-20261019-2ftt/work/demo/app/.bmad-plus/swarm/runs/buyer-panel-20261019-2ftt/tally.md5 of 30 units counted; 25 skipped.Disclosure: Simulated units are not people. Read rates as an upper bound and confirm with real ones.$ npx bmad-plus swarm inspect buyer-panel-20261019-2fttbuyer-panel-20261019-2ftt: tallied (panel, approved by the user)units 30, briefed 30, valid 5, invalid 0, missing 25Disclosure: Simulated units are not people. Read rates as an upper bound and confirm with real ones.
Output captured from a real run. Source: bmad-plus 0.23.0, 2026-10-19
tally.md (opening and the rate section)
> Simulated units are not people. Read rates as an upper bound and confirm with real ones.
# Simulated buyers see the pitch (run buyer-panel-20261019-2ftt)
5 of 30 units gave a valid answer; 25 skipped: B006 (no answer file), B007 (no answer file), ...
## Rate: buys
| group | yes | of | rate |
|---|---|---|---|
| all | 3 | 5 | 60.0% |
| segment = mid career | 2 | 4 | 50.0% |
| segment = high earner | 1 | 1 | 100.0% |
| segment = early career | 0 | 0 | n/a |
| behaviour = occasional | 1 | 3 | 33.3% |
| behaviour = daily regular | 2 | 2 | 100.0% |
| behaviour = explorer | 0 | 0 | n/a |
How to read it: the rate comes with its denominator ("3 of 5", and 5 of 30 units counted), not as a percentage alone. A group with no valid answer shows n/a, not 0. The same run also writes counts per reason code, the first two quotes per group verbatim, and the median, mean, minimum and maximum of price_floor. Re-run init with the same profile, --n 30 and --seed 7 against a second pitch to compare: the file cards.json of the two runs is byte for byte identical (cmp reports no difference between them).
The acceptance steps, replayed: 1 valid answer out of 30#
The acceptance recipe of 0.23.0 includes a swarm step in an empty project, carried out by a person: no sub-agent runs, the tester acts as one unit, files a valid answer for B001 and tries a bad one. The tool counts exactly that, and says so. Below is the same sequence replayed with the 0.23.0 CLI (the 30 paths of briefs are shortened).
briefs, check, save, tally with a single valid answer
$ npx bmad-plus swarm init --definition swarm/buyer-panel.json --profile swarm/profile.json --context pitch=swarm/pitch.md --n 30 --seed 7 --approvedbuyer-panel-20261019-9da2: panel of 30 units, budget 26, approval user.Seed 7 (same seed, same cards).Next: bmad-plus swarm briefs buyer-panel-20261019-9da2$ npx bmad-plus swarm briefs buyer-panel-20261019-9da2buyer-panel-20261019-9da2: 30 of 30 units need a sub-agent, in waves of 10.Wave 1:# ... one line per unit with the brief path, three waves ...Each sub-agent: Read the brief and follow it exactly; it is your whole instruction.Disclosure: Simulated units are not people. Read rates as an upper bound and confirm with real ones.# The tester writes one valid answer by hand: answers/B001.json.$ npx bmad-plus swarm check buyer-panel-20261019-9da2buyer-panel-20261019-9da2: 1 of 30 valid answers. B002 missing: no answer file# ... B003 to B030 likewise.$ npx bmad-plus swarm save buyer-panel-20261019-9da2 B002 --input swarm/reply-bad.txtSwarm: Reply of unit B002 refused: field "buys" must be true or false.# swarm/reply-bad.txt holds an object whose buys field is the text "maybe". Exit code 1.$ npx bmad-plus swarm tally buyer-panel-20261019-9da2/work/demo/app/.bmad-plus/swarm/runs/buyer-panel-20261019-9da2/tally.md1 of 30 units counted; 29 skipped.Disclosure: Simulated units are not people. Read rates as an upper bound and confirm with real ones.$ head -n 3 .bmad-plus/swarm/runs/buyer-panel-20261019-9da2/tally.md> Simulated units are not people. Read rates as an upper bound and confirm with real ones.# Simulated buyers see the pitch (run buyer-panel-20261019-9da2)
A jury needs no profile, no --n and no seed: its units are listed in the definition, one lens each. This one (swarm/pitch-jury.json, printed below with its template) reads the same pitch through price, trust and habit. The third juror carries its own id (J-habit); the first two get J001 and J002 from the prefix. Its answer has only three fields: a verdict, the weakest claim and up to two fixes, and its tally counts verdicts and keeps the first three quotes.
swarm/pitch-jury.json (the definition)
{
"schemaVersion": 1,
"id": "pitch-jury",
"kind": "jury",
"title": "Three lenses on the pitch",
"units": {
"source": "list",
"idPrefix": "J",
"list": [
{ "lens": "price", "stance": "Every claim is a cost the buyer pays." },
{ "lens": "trust", "stance": "Who vouches for this, and why believe them?" },
{ "id": "J-habit", "lens": "habit", "stance": "What has to change in a normal morning?" }
]
},
"brief": { "template": "briefs/juror.md", "context": ["pitch"] },
"answer": {
"required": ["id", "verdict", "weakest_claim"],
"fields": {
"verdict": { "type": "enum", "values": ["convincing", "unclear", "weak"] },
"weakest_claim": { "type": "string", "min": 5, "max": 300 },
"fixes": { "type": "list", "maxItems": 2 }
}
},
"tally": { "counts": ["verdict"], "quotes": { "weakest_claim": 3 } },
"disclosure": "A simulated jury is a rehearsal, not a review by the people it imitates."
}
swarm/briefs/juror.md (the template)
# Juror {{unit.id}}
Lens: {{card.lens}}. Stance: {{card.stance}}
Read the pitch below through that lens only and write a JSON object to `{{answer.path}}` with
`id` ("{{unit.id}}"), `verdict` (convincing, unclear or weak), `weakest_claim` and optional `fixes`.
{{context.pitch}}
{{disclosure}}
The demonstration answers (answers/J001.json, answers/J002.json, answers/J-habit.json)
{ "id": "J001", "verdict": "unclear", "weakest_claim": "A flat white at 4.20 is stated, but nothing says what it is compared to.", "fixes": ["Compare the price with a nearby chain.", "Show what the loyalty card is worth in euros."] }
{ "id": "J002", "verdict": "weak", "weakest_claim": "No one vouches for the bar: there is no review, no name, no photo.", "fixes": ["Add two customer sentences with a first name."] }
{ "id": "J-habit", "verdict": "convincing", "weakest_claim": "Opening at seven is good; it does not say how long the queue is.", "fixes": ["Say how long a regular order takes."] }
A jury of three, with three hand-written demonstration answers
$ npx bmad-plus swarm init --definition swarm/pitch-jury.json --context pitch=swarm/pitch.mdpitch-jury-20261019-3qxm: jury of 3 units, budget 26, approval not-needed.Units listed, no seed.Next: bmad-plus swarm briefs pitch-jury-20261019-3qxm$ npx bmad-plus swarm briefs pitch-jury-20261019-3qxmpitch-jury-20261019-3qxm: 3 of 3 units need a sub-agent, in waves of 10.Each sub-agent: Read the brief and follow it exactly; it is your whole instruction.Disclosure: A simulated jury is a rehearsal, not a review by the people it imitates.# Three answer files are written by hand into answers/.$ npx bmad-plus swarm check pitch-jury-20261019-3qxmpitch-jury-20261019-3qxm: 3 of 3 valid answers.$ npx bmad-plus swarm tally pitch-jury-20261019-3qxm3 of 3 units counted; 0 skipped.Disclosure: A simulated jury is a rehearsal, not a review by the people it imitates.
Output captured from a real run. Source: bmad-plus 0.23.0, 2026-10-19
tally.md of the jury
> A simulated jury is a rehearsal, not a review by the people it imitates.
# Three lenses on the pitch (run pitch-jury-20261019-3qxm)
3 of 3 units gave a valid answer; 0 skipped.
## Counts: verdict
| group | convincing | unclear | weak |
|---|---|---|---|
| all | 1 | 1 | 1 |
## Quotes: weakest_claim (up to 3 per group, verbatim)
**all**
- J001: "A flat white at 4.20 is stated, but nothing says what it is compared to."
- J002: "No one vouches for the bar: there is no review, no name, no photo."
- J-habit: "Opening at seven is good; it does not say how long the queue is."
Does the new pitch beat the old one with the same people?
panel
Same profile, --n and --seed; change only --context pitch=...; compare the two tallies.
Which segment objects to the price?
panel
Put the segment in groupBy, the objection in an enum field and read counts per group.
What is the weakest claim of this page?
jury
One lens per juror (price, trust, accessibility, legal); read the quotes per lens.
Does the plan survive a hostile reading?
jury
Give each juror a stance such as "assume this fails; where?" and tally the verdicts.
Can twelve pages be written without stepping on each other?
fanout
One unit per page with a disjoint scope; run as session sub-agents, or export with swarm plan for Nexus.
Compare two pitches with the same simulated buyers#
Use when you have a profile and a definition for a panel, and two versions of the text to test. Expect to be asked for approval if the panel is larger than 26.
In your AI assistant
Run the buyer-panel swarm with 30 units and seed 7 on swarm/pitch-v1.md, then again with the same seed and profile on swarm/pitch-v2.md. If a run needs approval, ask me first with the numbers. Launch the sub-agents one wave at a time and never answer for a unit. Report from the two tallies only: the disclosure first, then the buy rate by segment for each pitch with its denominator, the reason codes, and what changed between the two.
What you should get The first init stops with exit code 2 and the line "needs approval ... 30 sub-agents and the budget is 26". After your yes, three waves of 10 run, check lists any missing unit, the tally files are written, and the report quotes the disclosure before any rate. Because the two runs share profile, --n and seed, their cards.json are identical and the difference comes from the pitch alone. Read the result as an upper bound to confirm with real people.
Use with a jury definition (one lens per juror) and the text to read as a context file. No profile and no seed are needed.
In your AI assistant
Run the pitch-jury swarm on swarm/pitch.md. One sub-agent per juror, each reading its own brief only. Read each context file before passing it. When the tally is written, report the disclosure first, then the verdict counts and the weakest claim quoted per lens, and list the fixes the jurors proposed. Do not write or edit any answer yourself.
What you should get Three briefs are rendered, three sub-agents answer, check reports 3 of 3 valid answers and tally.md opens with "A simulated jury is a rehearsal, not a review by the people it imitates." It then shows how many jurors found the pitch convincing, unclear or weak, and quotes each weakest claim with the juror’s id.
Use when units edit the repository, need a check each, or must survive the session: export the fanout as a Nexus plan.
In your AI assistant
Initialise the site-pages fanout from swarm/page-fanout.json and swarm/units.json (about, pricing, contact), export it with swarm plan to .bmad-plus/swarm/reports/RUN/plan.json and create the Nexus run from it. Start one task at a time on this host, let me review the page, then verify and accept it. Do not accept a task whose check fails.
What you should getswarm plan reports "3 tasks" and writes a plan whose tasks carry the disjoint scopes; nexus create shows each task as "not started; verification=pending; acceptance=pending". For each task: start, the page is written, record, then verify runs the unit’s check and accept marks it accepted. A task whose answer file is invalid stays unaccepted.
run.json kind, pack, hashes of every input, n, seed, wave, budget, approval, status
definition.json the normalized definition, frozen
template.md the brief template, frozen
cards.json the dealt or listed units
context/<name>.md each context file, frozen
briefs/<unit>.md rendered by briefs or plan
answers/<unit>.json written by the sub-agent, or filed by save
tally.json, tally.md
checks/answer.cjs, checks/swarm-validate.cjs, nexus-plan.json (fanout, written by plan)
At most 16 MiB each; a reply given to save at most 1 MiB
Profile
10 dimensions of up to 50 values, 20 ranges, 20 pools of up to 200 entries
Answer fields
50; enum values 50; list items up to 200; string length ceiling 20000
groupBy fields; quotes per group
5; 1 to 10
Fanout scopes per unit
20
Export to Nexus
One task per unit, maxParallel at most 32 (Nexus accepts 200 tasks per run)
Safety boundaries: the runtime has no child process, no network and no eval; every file is written atomically and only under .bmad-plus/swarm/; paths that traverse, are absolute, follow a link or reach .git are refused; inputs are frozen at init, so editing a context afterwards changes nothing for that run.