Browse the documentation

bmad-plus swarm

Deal, brief, check, file and tally a swarm of sub-agents within a budget: panel, jury or fanout. Flags, run tree, approval at exit 2, honesty rules, real output and errors.

On this page
  1. Synopsis
  2. The three kinds of swarm
  3. Budget, waves and approval
  4. The seven actions
  5. The run tree
  6. Honesty rules
  7. Reads, writes and limits
  8. Exit codes
  9. The sample files
  10. Real output
  11. Errors and what to do
  12. Examples

A swarm is many sub-agents doing the same kind of work once each: thirty simulated buyers reading a pitch, three jurors holding one lens each, one writer per page. bmad-plus swarm is the bookkeeping around it. It deals the units, writes one brief per sub-agent, checks every answer file, files a reply a sub-agent only printed, and counts the valid answers. It never calls a model, never opens a network connection and never starts a process: your assistant (the host) launches the sub-agents, and the command only reads and writes files under .bmad-plus/swarm/. Use it when a panel, a jury or a fan-out of independent units is requested. 0.23.0 ships no swarm definition in any pack; you or a pack supply the definition file.

Synopsis

In your project terminal
npx bmad-plus swarm init --definition FILE [--profile FILE | --units FILE] [--context NAME=FILE]... [--n N] [--seed N] [--approved]
npx bmad-plus swarm briefs RUN [--wave N]
npx bmad-plus swarm check RUN
npx bmad-plus swarm save RUN UNIT --input FILE
npx bmad-plus swarm tally RUN [--out FILE]
npx bmad-plus swarm inspect RUN
npx bmad-plus swarm plan RUN [--out FILE]

Flags of bmad-plus swarm
FlagArgumentDefaultWhat it does
<action>init, briefs, check, save, tally, inspect, planrequiredWhich step to run.
[run], [unit]run id, unit idnoneRun id such as buyer-panel-20261018-oqu7 (every action except init); unit id such as B002 (save only).
-d, --directory <path>pathcurrent directoryProject directory.
--definition <file>pathnoneSwarm definition, project-relative JSON. init only; required.
--profile <file>pathnonePanel profile, project-relative JSON. init; required for a panel, refused for other kinds.
--units <file>pathnoneFanout units, project-relative JSON. init; required for a fanout, refused for other kinds.
--context <name=file>NAME=FILEnoneContext file included verbatim in every brief. Repeatable. Every name the definition declares is required; an undeclared name is refused.
--n <count>integer 1 to 1000noneNumber of units to deal for a panel. Refused for a jury or a fanout.
--seed <number>integer 0 to 2147483647drawn once, then recordedDealing seed. Panel only. Same profile, same --n, same seed: same cards.
--approvednoneoffRecords that a human agreed to exceed the budget. Never pass it on your own.
--wave <count>integer 1 to 1000configured wave (10)Sub-agents launched together. briefs only.
--input <file>pathnoneReply a sub-agent printed. save only; required; outside .bmad-plus/; at most 1 MiB.
--out <file>pathinside the runOutput file for tally and plan. Must be under .bmad-plus/swarm/ (see Reads, writes and limits).
--jsonnoneoffMachine-readable output with schemaVersion: 1 and a status field.

The three kinds of swarm

Kinds, set by the definition file
KindUnits come fromTypical useTally
panelA profile: dimensions with shares, dealt by exact quota and seeded (--profile, --n, --seed).Simulated buyers, readers or users reading a pitch.Rate by group, counts, quotes, numbers.
juryThe definition itself: units listed one by one, each with a lens.An adversarial read of a pitch, a page or a plan.Counts and quotes per lens.
fanoutA units file (--units): each unit has its own disjoint write scope.Pages to write, modules to document: independent work in parallel.Rate and numbers; can be exported as a durable Nexus plan with plan.

A panel and a jury run as session sub-agents: the host launches them, waits, then reads their files. A fanout can run the same way, or be exported with plan when the work must survive the session, needs an independent check per unit or edits the repository (see bmad-plus nexus). The definition file (JSON) names the kind, the brief template, the answer fields (boolean, integer, number, string, enum, list), the tally and a one-sentence disclosure of 1 to 500 characters.

Budget, waves and approval

Three settings in the swarm block of _bmad/config.yaml bound a run. An absent file or block means the defaults below; any other value is refused. init compares the number of units with the budget: a larger run is created in state needs-approval, writes no brief and exits with code 2. The assistant then has to ask you, in one sentence with the number, and only after a yes re-run init with --approved and the seed the refusal printed.

The swarm block of _bmad/config.yaml
KeyDefaultAllowedEffect
budget26integer 1 to 1000Sub-agents one run may use before the host must ask you.
wave10integer 1 to 1000Sub-agents launched together; briefs groups the pending units in waves of this size (override with --wave).
media_creditsaskexactly askPaid image, video or audio generation is never covered by a budget: the host asks before each such run.

Warning Approval belongs to a person

--approved is stored in the run as approval: user. The swarm skill allows it only after a human yes, whatever the number of units. A run is never approved by the assistant’s own judgement.

The seven actions

What each action reads and writes
ActionReadsWritesExit
initDefinition, template, profile or units, contexts, _bmad/config.yaml.The run tree (frozen inputs and run.json).0 ready, 2 needs approval, 1 refused.
briefs RUNThe run tree and the answers.A brief for every unit without a valid answer; the answers/ directory.0, or 2 if the run needs approval.
check RUNThe run tree and the answers.Nothing.0 complete, 1 incomplete.
save RUN UNIT --input FThe run tree and the reply file.answers/<unit>.json.0 saved, 1 refused.
tally RUN [--out F]The run tree and the answers.tally.json and tally.md (or --out).0, or 2 if the run needs approval.
inspect RUNThe run tree, answers, briefs.Nothing.0.
plan RUN [--out F]The run tree (fanout only).All briefs, checks/, nexus-plan.json (or --out).0 planned, 2 needs approval, 1 not a fanout.

Typical order: init, then briefs, the host launches one sub-agent per brief with exactly the instruction "Read the brief and follow it exactly; it is your whole instruction", then check. Run briefs once more to give missing units a second chance (it only renders units without a valid answer), file any printed reply with save, then tally. A unit still missing after the second wave stays missing and is named in the tally. inspect summarises a run at any time without writing.

The run tree

Created under your project by init
.bmad-plus/swarm/runs/<definition-id>-<yyyymmdd>-<4 chars>/
  run.json            inputs hashes, 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    written by briefs (and 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)

The 4-character suffix is random and an existing directory is never reused. Inputs are frozen at init: editing a context or the definition afterwards changes nothing for that run. Statuses: needs-approval (terminal), ready, briefed, tallied. A tally can be re-run, and briefs can be re-run after a tally. Reports requested with --out go under .bmad-plus/swarm/reports/<run id>/.

Honesty rules

  • Simulated units are not people. The tally opens with the definition’s disclosure sentence, before any figure. Rates from a panel are an upper bound to confirm with real people, never a measurement.
  • Quotes are simulated: present them as what a simulated unit wrote, never as a testimonial or an interview.
  • The moderator never answers for a unit: no hand-written answer file, no "fixing" an invalid one. save only extracts and validates what a sub-agent printed.
  • Briefs are never edited. A change belongs in the template or the definition, then a new run.
  • Same seed, same cards: a before/after comparison of two pitches re-runs the same profile with the same --n and --seed, so only the context changed.
  • The runtime filters nothing: card values, pool entries and context files reach the sub-agent word for word. Read a context before passing it; it is copied into every brief, secrets and instructions alike.
  • A valid answer is a well-formed one. Nothing proves that a sub-agent read its brief or told the truth.

Reads, writes and limits

  • Writes only under .bmad-plus/swarm/. --out for tally and plan must stay in .bmad-plus/swarm/reports/<run id>/ or in the run’s own tree, never over answers/, briefs/, context/, checks/, run.json, cards.json, definition.json, template.md, nexus-plan.json, tally.json or tally.md.
  • Every path you name (--definition, --profile, --units, --context, --input) is project-relative with /; absolute paths, backslashes, .., .git and anything through a symlink are refused. Those files may not come from .bmad-plus/.
  • Limits: 1 to 1000 units per run; budget and wave 1 to 1000; seed 0 to 2147483647; inputs at most 16 MiB; a printed reply for save at most 1 MiB; at most 50 answer fields; at most 20 contexts per definition; at most 5 groupBy fields.
  • Unit ids are letters, digits, _ or - (up to 64 characters; fanout ids are lowercase). Dealt panel ids are the prefix plus the position padded to at least three digits (B001).
  • Files are written through a temporary file and renamed into place. No network, no child process, no eval.

Exit codes

Exit codes of bmad-plus swarm
CodeMeaning
0The action succeeded; for check, every unit has a valid answer.
1check found units missing or invalid (status incomplete), or any error (printed as Swarm: <message>).
2Status needs-approval: the run has more units than the budget and no --approved. Ask a person, then re-run init --approved --seed <seed>. briefs, save, tally and plan on such a run also exit 2 and write nothing.

The sample files

The transcripts and the examples below use the files printed here. They live in the repository under tests/fixtures/swarm/ and are not part of the npm package, so they are given in full: create them under swarm/ in your project and the commands reproduce the transcripts, except for the run ids (a date and a random suffix). The first six belong to the buyer panel; the last three belong to the fan-out example at the end of the page.

swarm/buyer-panel.json, the definition of the panel
{
  "schemaVersion": 1,
  "id": "buyer-panel",
  "kind": "panel",
  "pack": "marketfit",
  "title": "Simulated buyers see the pitch",
  "units": { "source": "profile", "idPrefix": "B" },
  "brief": { "template": "briefs/buyer.md", "context": ["pitch"] },
  "answer": {
    "required": ["id", "buys", "reason_code", "reason"],
    "fields": {
      "buys": { "type": "boolean" },
      "reason_code": { "type": "enum", "values": ["price", "habit", "need", "trust", "other"] },
      "reason": { "type": "string", "max": 600 },
      "price_floor": { "type": "number", "min": 0 },
      "visits_per_week": { "type": "integer", "min": 0, "max": 14 },
      "would_tell": { "type": "list", "maxItems": 3 }
    }
  },
  "tally": {
    "rate": "buys",
    "groupBy": ["segment", "behaviour"],
    "counts": ["reason_code"],
    "quotes": { "reason": 2 },
    "numbers": ["price_floor"]
  },
  "disclosure": "Simulated units are not people. Read rates as an upper bound and confirm with real ones."
}

swarm/profile.json, the profile the 30 buyer cards are dealt from
{
  "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/pitch.md, the text the buyers read
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.

swarm/briefs/buyer.md, the brief template (each placeholder in double braces is filled per unit)
# 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/reply-ok.json, a valid reply for unit B001
{ "id": "B001", "buys": true, "reason_code": "habit", "reason": "The loyalty card and my usual order under my name suit my mornings." }

swarm/reply-bad.txt, a reply that the validator refuses (buys must be true or false)
{ "id": "B002", "buys": "maybe", "reason_code": "price", "reason": "Too steep for a drink." }

swarm/units.json, the three pages of the fan-out example
{
  "schemaVersion": 1,
  "units": [
    { "id": "about", "scope": "site/about", "title": "About us", "tone": "warm" },
    { "id": "pricing", "scope": ["site/pricing", "site/data/prices.json"], "title": "Pricing", "tone": "plain" },
    { "id": "contact", "scope": "site/contact", "title": "Contact", "tone": "short" }
  ]
}

swarm/page-fanout.json, the fan-out 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 brief template of the fan-out
# 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}}

Real output

These transcripts come from the human acceptance run of 0.23.0: a panel of 30 simulated buyers in a demo app, against the default budget of 26. The npm notice lines and the shell prompt are removed. The definition (swarm/buyer-panel.json) and profile are the repository’s test fixtures, copied into the demo app.

30 units against a budget of 26: refused (exit code 2)
npx bmad-plus swarm init --definition swarm/buyer-panel.json --profile swarm/profile.json --context pitch=swarm/pitch.md --n 30 --seed 7 --directory .\demo-app
buyer-panel-20261018-ungp: needs approval. The run needs 30 sub-agents and the budget is 26: ask the user, then re-run init with --approved --seed 7.

Output captured from a real run. Source: Captured during the 0.23.0 acceptance run, 2026-10-18 (step 6)

After a human yes: --approved with the same seed
npx bmad-plus swarm init --definition swarm/buyer-panel.json --profile swarm/profile.json --context pitch=swarm/pitch.md --n 30 --seed 7 --approved --directory .\demo-app
buyer-panel-20261018-oqu7: panel of 30 units, budget 26, approval user.
Seed 7 (same seed, same cards).
Next: bmad-plus swarm briefs buyer-panel-20261018-oqu7

Output captured from a real run. Source: Captured during the 0.23.0 acceptance run, 2026-10-18 (step 6)

Briefs, in waves of 10
npx bmad-plus swarm briefs buyer-panel-20261018-oqu7 --directory .\demo-app
buyer-panel-20261018-oqu7: 30 of 30 units need a sub-agent, in waves of 10.
Wave 1:
  B001  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B001.md
  B002  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B002.md
  B003  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B003.md
  B004  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B004.md
  B005  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B005.md
  B006  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B006.md
  B007  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B007.md
  B008  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B008.md
  B009  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B009.md
  B010  C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\briefs\B010.md
Waves 2 (B011 to B020) and 3 (B021 to B030) follow in the same format; they are left out here.
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.

Output captured from a real run. Source: Captured during the 0.23.0 acceptance run, 2026-10-18 (step 6; waves 2 and 3 shortened)

One valid answer, one refused reply, the tally
npx bmad-plus swarm save buyer-panel-20261018-oqu7 B001 --input swarm/reply-ok.json --directory .\demo-app
buyer-panel-20261018-oqu7: answer of B001 filed at C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\answers\B001.json
npx bmad-plus swarm check buyer-panel-20261018-oqu7 --directory .\demo-app
buyer-panel-20261018-oqu7: 1 of 30 valid answers.
  B002  missing: no answer file
  B003  missing: no answer file
B004 to B030 are listed the same way (27 more lines).
npx bmad-plus swarm save buyer-panel-20261018-oqu7 B002 --input swarm/reply-bad.txt --directory .\demo-app
Swarm: Reply of unit B002 refused: field "buys" must be true or false.
npx bmad-plus swarm tally buyer-panel-20261018-oqu7 --directory .\demo-app
C:\Users\you\demo-app\.bmad-plus\swarm\runs\buyer-panel-20261018-oqu7\tally.md
1 of 30 units counted; 29 skipped.
Disclosure: Simulated units are not people. Read rates as an upper bound and confirm with real ones.

Output captured from a real run. Source: Captured during the 0.23.0 acceptance run, 2026-10-18 (step 6; the 27 further missing lines of check are shortened; the first command (filing B001) was replayed afterwards with the same package; the Disclosure sentence is the one the same run printed from briefs)

How to read it: the first init created a refused run (...-ungp) and nothing else; the approved one is a different run (...-oqu7) with its own random suffix. Only B001 had answered, so the honest tally counts 1 unit and names 29 skipped; the disclosure sentence comes first, and it is also the first line of tally.md. The refused reply wrote nothing for B002.

Errors and what to do

Messages printed by bmad-plus swarm
MessageCauseWhat to do
<run>: needs approval. The run needs N sub-agents and the budget is B: ask the user, then re-run init with --approved --seed S. (exit 2)More units than swarm.budget and no --approved.Ask a person in one sentence with the number. After a yes, re-run init with --approved --seed S. Or lower --n, or raise swarm.budget in _bmad/config.yaml.
Swarm: Use swarm init --definition FILE [--profile FILE | --units FILE] …Unknown action, or a run id or unit id missing or in the wrong place.Check the synopsis: save takes a run and a unit, the other actions after init take a run only.
Swarm: init needs --definition FILE.init without a definition.Pass --definition.
Swarm: A panel needs --profile FILE. / A panel needs --n, the number of units to deal. / A panel takes --profile, not --units.Panel started without its inputs.Add --profile and --n; do not pass --units.
Swarm: Only a panel takes --profile. / A jury has as many units as it lists; --n does not apply. / A seed only applies to a dealt panel. / A fanout needs --units FILE.Flags that do not fit the kind named in the definition.Remove the flag, or use the right definition.
Swarm: The definition needs --context NAME=FILE. / Context "x" is not declared by the definition. / A context is written NAME=FILE: …A declared context is missing, an extra one was passed, or the form is wrong.Pass exactly the contexts the definition declares, as --context name=path.
Swarm: _bmad/config.yaml: swarm.media_credits must be "ask". / …swarm.budget must be an integer from 1 to 1000. / …swarm has an unknown key "x".The swarm block holds a value or a key the runtime refuses.Fix the block, or delete it to get the defaults (26, 10, ask).
Swarm: Definition does not exist: … / …is not valid JSON: … / …exceeds N bytes / …is not a regular fileThe input file is missing, malformed, too large or not a file.Fix the path or the JSON.
Swarm: … may not reach .git / … may not come from the BMAD+ state tree / … refused: … (…)The path points into .git, .bmad-plus/, outside the project or through a link.Use a project-relative path with /, outside .bmad-plus/.
Swarm: Tally groupBy field "f" is absent from unit U.A card lacks a field the tally groups by.Add the field to the profile, the list or the units file, or drop it from tally.groupBy.
Swarm: A run id reads <definition>-<yyyymmdd>-<4 characters>. / Run record is not usable: …Wrong run id, or a damaged run tree.Copy the id printed by init; if the tree was edited, start a new run.
Swarm: Reply of unit U refused: … / No JSON object found in the reply filed for unit U.The printed reply has no JSON object, or it fails the answer contract (wrong type, missing field, wrong id).Nothing was written. Ask the sub-agent again (re-run briefs) or leave the unit missing. Do not write the answer yourself.
Swarm: Unit U already has a valid answer; it is kept. / Unknown unit U in run R.save on a unit that is answered, or not in the run.Use check to see which units are open.
Swarm: save needs --input FILE, the reply the sub-agent printed.save without --input.Save the reply to a project file outside .bmad-plus/ and pass it.
Swarm: --out must stay under … or … / --out may not write inside … / --out may not replace the run file …--out points where tally or plan may not write.Use a path under .bmad-plus/swarm/reports/<run id>/.
Swarm: Only a fanout exports a Nexus plan; run R is a panel.plan on a panel or a jury.Use plan only with a fanout.

Examples

A panel of simulated buyers within a budget

You have a swarm definition, a profile and a pitch file. The default budget is 26 sub-agents; you ask for 30.

In your AI assistant
Run the buyer panel from swarm/buyer-panel.json with the profile swarm/profile.json and my pitch in swarm/pitch.md. Use 30 simulated buyers and seed 7.

What you should get The assistant runs swarm init. Thirty units exceed the budget of 26, so the command exits with code 2 and writes no brief; the assistant tells you "30 sub-agents against a budget of 26" and asks. After your yes it re-runs init with --approved --seed 7, gets a run id, runs briefs (three waves of 10), launches one sub-agent per brief with the fixed instruction, then runs check, gives missing units one second chance and finishes with tally. Its report opens with the disclosure sentence, states how many units were counted and how many were skipped, and presents rates as an upper bound.

Compare two versions of a pitch

The same panel, run twice with the same seed, differs only by the pitch file.

In your AI assistant
Run the buyer panel on swarm/pitch-v1.md, then again on swarm/pitch-v2.md, with the same profile, 24 buyers and seed 7. Compare the two tallies.

What you should get Twenty-four units fit the default budget of 26, so neither init needs approval. Because profile, --n and --seed are identical, both runs deal the same 24 cards; only {{context.pitch}} differs in the briefs. Each run has its own id and tally.md. The comparison is reported as a difference between two simulated upper bounds, with the disclosure first, not as a measured conversion change.

One writer per page, verified by Nexus

A fanout definition and a units file give each page its own write scope. The work must survive the session, so it is exported as a durable plan.

In your AI assistant
Write the three pages listed in swarm/units.json with one sub-agent per page, using swarm/page-fanout.json. Export it as a Nexus plan so each page is verified before I accept it.

What you should get The assistant runs swarm init with --definition and --units, then swarm plan RUN. The plan has one task per unit: its scope plus its answer file, its brief and the frozen files as resources, one check node checks/answer.cjs --unit=<unit>, maxAttempts 2, and maxParallel equal to the configured wave capped at 32. plan prints the plan path and Next: bmad-plus nexus create --plan <path>. From there Nexus starts, verifies and accepts each task; a unit whose answer file is malformed fails its check and is retried, not accepted.