Skip to content

Workflows: when a workflow beats a single run ​

After this page you can tell when a workflow beats a single run, write the program it executes, validate and launch that program, change the plan while the run is live, and act on what a finished run hands back.

When a workflow beats a single run ​

bullswarm run sends one bounded outcome to one agent: a review, a localized fix, a study with one deliverable. When one worker can hold the whole input and make one deliverable (triage a file of 40 tickets, a research brief, one feature), use a run, with --answer-schema when you need a checkable answer. Do not split an input into chunks unless one worker cannot hold it: chunks lose judgement across items. In a 0.37.0 comparison, a 40-ticket triage split into four chunks scored 31-32 of 40 on priority, and one run scored 34 of 40 in fewer caller turns and less time.

Reach for a workflow instead when the work splits into parallel territories, when shared files need an integration step after the writers finish, when acceptance must be judged by someone other than the author, or when you need a gate or a loop. Keep a loop's critique to what the sources can show (a claim that something is missing cannot cite a line), cap its rounds at 2 unless a round is cheap, and decide before launch what you do when it runs out: continuing it is recorded as continued-unmet.

Shape of the workEntry point
One bounded outcome, one agentbullswarm run — see Run
Parallel territories, integration, or independent acceptancebullswarm workflow goal — the rest of this page

You are the planner ​

The calling agent writes the program. The kernel is Bullswarm's own runtime, not an agent: it validates the graph, applies each step's route, schedules dependencies, retries each step once, and computes the result.

Start from the contract. It prints the program v3 format (bullswarm.workflow.contract.v3: the step, gate and loop fields, the rules, and an example that validates) as JSON:

bash
# the v3 program format, its rules and an example that validates
bullswarm workflow plan contract "Make the acme tests pass" --cwd /abs/path/to/acme --json

workflow goal never plans on your behalf: with no --program it exits 2, launches nothing, and prints the commands that come next. To survey first, make the survey the first step and have the steps that need it depend on it. To have an agent plan, give a step an answer that is a list of steps and append them with bullswarm workflow add <run> --from-answer <step>. The preflight scout (--scout) and the dispatched Workflow Planner (--orchestrator) were removed in 0.38.0; their flags exit 2 with one sentence naming that replacement.

FlagMeaningDefault
--program <file.json>the v3 program you authored; validated against its contract before launch, then executed. A v2 program is refused (exit 2) before any run folder existsrequired
--isolationper-worker worktrees and strict exact-file ownership checksoff (shared workspace, advisory territories)
--concurrency <n>maximum parallel dispatches4
--retry-attempts <0..3>automatic retries per step before it comes back to you1

Program v3: steps, gates and loops ​

New work is written as a v3 program (bullswarm.workflow.program.v3). It has four blocks:

  • steps: the work. Each step is a run: id, prompt, dependsOn, and optional phase, label, lane, effort, reasoning, route, answer, evidence, deliverable, files, retry (0 or 1) and timeBox.
  • phase: a label on a step that groups steps on the dashboard. It changes nothing else.
  • gates: {id, dependsOn, when?, note?}. The steps behind a gate wait until you run bullswarm workflow continue <run> <gate>. With when, the gate waits only when its condition holds and passes by itself otherwise.
  • loops: {id, steps, until, maxRounds} (1-5 rounds). The loop runs every one of its steps in every round and reads until when the round is over.

A step passes by facts: its worker ended cleanly, its deliverable was produced, its evidence passed, and its answer (when it declares one) matched the schema. An answer is a JSON schema: the worker writes JSON to a file Bullswarm names, and the file is checked, handed to the steps that depend on it, and read by conditions. A check is an ordinary step with an answer and/or evidence. v3 has no requirement IDs, evidenceFor, roles, kinds or verifyRounds: validate refuses them, and a v3 run reports facts per step, never verified.

A gate's when and a loop's until take a condition: {"step": "critique", "field": "passed"} reads a boolean the step's answer schema requires (add "equals": false to invert it), and {"step": "check", "evidence": "passed"} reads whether the step's evidence passed. When until is the evidence form, a failed check on that step reads as "not passed" and the loop goes on; with the field form, a failed check fails the step as it would outside a loop. A loop's until also takes a third form, {"step": "review", "dry": 1}: the loop ends after a review round that finds nothing new to fix (Workflow program).

A loop that fixes until the tests pass:

json
{
  "schemaVersion": "bullswarm.workflow.program.v3",
  "steps": [
    {
      "id": "fix",
      "lane": "build",
      "prompt": "In /private/tmp/v37fix/acme, `npm test` fails. Find the cause in src/ and fix it; do not change tests/. From round 2 on, the Previous round block lists what the last check saw: fix that."
    },
    {
      "id": "check",
      "dependsOn": ["fix"],
      "prompt": "In /private/tmp/v37fix/acme, run `npm test` and list every failing test with its first error line. Change no file.",
      "answer": {
        "type": "object",
        "required": ["problems"],
        "properties": { "problems": { "type": "array", "items": { "type": "string" } } }
      },
      "evidence": [{ "type": "command", "cmd": "npm test", "timeoutSec": 120 }]
    }
  ],
  "loops": [
    { "id": "until-green", "steps": ["fix", "check"], "until": { "step": "check", "evidence": "passed" }, "maxRounds": 3 }
  ]
}
bash
bullswarm workflow plan validate "Make the acme tests pass" --cwd=/private/tmp/v37fix/acme --program=/private/tmp/v37fix/loop.json
text
✓ program v3 valid: 2 steps, 0 gates, 1 loop (nothing launched)
  fix                      build/medium deliverable=files
  check                    analyze/medium evidence=command answer after fix
  checks   not run · add --try-checks to run each command check once now against the current tree (it may take time and must not change files)
  loop until-green         steps fix, check · until check's evidence passed · at most 3 rounds
  launch   bullswarm workflow goal 'Make the acme tests pass' --cwd /private/tmp/v37fix/acme --program /private/tmp/v37fix/loop.json --json

A gate that stops before publishing, validated the same way:

text
✓ program v3 valid: 2 steps, 1 gate, 0 loops (nothing launched)
  count                    analyze/medium answer
  post                     build/medium deliverable=files after approve
  gate approve             after count · waits for you · Read the count and decide whether to write DONE.md
  launch   bullswarm workflow goal 'Count and publish' --cwd /private/tmp/v37fix/proj --program /private/tmp/v37fix/gate.json --json

A goal over 120 characters, or with a line break, shows as "<goal>" in the launch line: put your goal back in its place before you run it.

A run that waits for you ​

A gate stops only the steps behind it; other branches keep running. When only waiting gates or loops are left (a gate waiting, or a loop out of rounds), the run parks with status waiting. bullswarm workflow watch <run> --until trouble wakes on it, and runs result prints where it waits:

text
outcome: waiting
waiting: gate approve · Read NOTE.md and decide whether to write DONE.md
next: bullswarm workflow continue 2fne62 approve

bullswarm workflow wait <run> <id...> reads until the named steps, gates or loops settle, and prints their facts and checked answers (exit 0, 1 when one failed or the run stopped short, 2 on a timeout or an unknown id):

text
✓ count succeeded · grok · grok-4.7 · 35s
  output   /private/tmp/we-home/workflows/wf-mulitifp-c82a30/out-count-attempt-1.md
  answer
    {
      "lines": 4,
      "short": true
    }
⧖ gate approve waiting · Read NOTE.md and decide whether to write DONE.md
  continue bullswarm workflow continue 2fne62 approve

What moves a waiting run:

You wantCommand
pass a waiting gatebullswarm workflow continue <run> <gate>
give a loop out of rounds more roundsbullswarm workflow continue <run> <loop> --rounds <1-5> (without --rounds the steps behind it run, and the loop is recorded as continued-unmet)
add work that depends on an answerbullswarm workflow add <run> --steps part.json (or --from-answer <step> when the step's answer is itself a fragment {steps, gates?, loops?}), then bullswarm workflow wait <run> <added ids>

A v3 run's steps, gates and loops are never edited. workflow add appends steps, gates and loops without changing anything the run has, and reopens a finished run.

v2 programs ​

The rest of this page describes v2 programs (bullswarm.workflow.program.v2): actions with requirement IDs, roles and kinds, and a review loop. Since 0.38.0 a new run refuses a v2 program, and a run an earlier Bullswarm started is view-only: it still lists, shows, replays and counts as before, but resume, pause, steer and the step verbs exit 2 with run <id> was started by an earlier Bullswarm and is view-only; start a new run: …. What follows explains what such a saved run shows; the planner, scout, repair loop and plan revise it names no longer run.

Decompose into actions with exact-file territories ​

Plan one action per bounded outcome a single worker can finish alone. Every writer gets an ownedFiles territory — exact repo-relative files, because validate refuses a directory or a glob there, and refuses a pinned pool that cannot run the step.

All workers share one tree, so make each prompt say it: preserve other workers' edits, and report any file needed outside the territory instead of editing it.

Dependencies are inputs, not phases ​

dependsOn lists the actions whose outputs this one reads. Everything with no unmet dependency runs at once, and a dependent starts when its own inputs finish — not when unrelated siblings finish. Never add a dependency to fake a phase.

A failed action skips its dependents while other branches keep running. The graph ends completed when every action succeeded, or partial when some failed or were blocked.

Integrate after parallel writers ​

After a parallel wave, plan one integrator (kind: integration, or a combine step with deliverable files) depending on all its writers, directly or through a digest. Give it ownedFiles: [], which on a build-lane action means no territory limit, and it runs alone.

Its prompt should read the worker outputs, apply cross-territory requests, reconcile shared files, and run the repository's acceptance commands.

Verify with a check step ​

Acceptance is its own check step (or kind: adversarial-acceptance for high effort): empty affects, empty ownedFiles, evidenceFor set to the requirement IDs it judges, and dependsOn covering every writer that affects them. Describe what to inspect — the kernel owns the evidence format and rejects instructions such as "return only JSON".

verified is computed separately from completed: it records whether every mandatory requirement has fresh passing evidence, so a run can finish and still be unverified.

Have Bullswarm run checks ​

Use command evidence for tests or other facts a command can check, and schema evidence when output must match a JSON or JSONL shape. Keep commands scoped to their step; put a whole-suite check on the step that runs alone or last. Checks run after the deliverable gate and must not change the deliverable. A failure gets one same-pool retry, then returns to you. A report can be checked with file: "$output" or a command that reads $BULLSWARM_STEP_OUTPUT. A review step (one with evidenceFor) and a digest take no evidence: put the commands a reviewer must run in its prompt, or add a separate check step with evidence and an empty evidenceFor. Such a step can also prove already-finished work without rerunning it. Each check's result is in runs result <id> --json under actions[].evidenceResults. See Evidence in the program reference.

Condense with a digest ​

Use a digest action when three or more writers feed a single reader, or when a reader's dependency outputs would exceed roughly 20 KB. The kernel writes the whole digest task — your prompt is focus guidance only — and it quotes each source's delivered items, numbers, and shared-file requests verbatim, so the reader gets digestOf links instead of raw files.

A digest must depend on at least one action and owns no files. No review step depends on a digest: a reviewer reads the real artifacts.

Effort comes from the role or kind ​

A role says what a step does, and an optional deliverable says what it leaves behind: files, a report, data or media at exact paths, or outward actions such as a sent message. A role-only step takes its lane and effort from both:

roledefault deliverablefilesdata or mediareportoutward
investigatereportbuild/mediumbuild/mediumanalyze/medium—
producefilesbuild/mediumbuild/mediumanalyze/medium—
transformfileschore/lowchore/lowanalyze/low—
combine(required)build/highbuild/mediumanalyze/medium—
checkreport——analyze/medium—
actoutward———analyze/medium

A kind names a more exact nature. Each kind belongs to one role and keeps its own lane, effort and gate, so the two do not always match:

kindrolekind: lane/effortrole alone: lane/effort (default deliverable)kind gaterole gate
mechanicaltransformchore/lowchore/low (files)not judgedjudged: a file change or a commit
io-readinvestigateanalyze/lowanalyze/medium (report)not judgedreport not empty
architectureinvestigateanalyze/highanalyze/medium (report)not judgedreport not empty
implementproducebuild/mediumbuild/medium (files)a file change or a commita file change or a commit
integrationcombinebuild/highneeds a deliverableexemptfiles: exempt; data/media: paths; report: not empty
digestcombineanalyze/low, kernel-written task—not judgeda role step never gets the digest task
checkcheckanalyze/mediumanalyze/medium (report)not judgednot judged with evidenceFor, else report not empty
adversarial-acceptancecheckanalyze/highanalyze/medium (report)not judgedsame as check

Give work steps a role. Keep commit, formatter and PR steps kind: mechanical, and use a kind for a digest or when you want its exact routing. A step whose declared deliverable was not produced fails as not-produced, and so does a build-lane step with no declared deliverable that changes no file and makes no commit. Stating lane and effort on every action is the other way to route; a role or kind outside these tables is a validation error, not a runtime failure. Reasoning depth is a third, independent decision: an action's optional reasoning field sets how hard the picked model thinks on that one action. Every field, default and gate is in Program format.

Validate, then launch ​

Validate is a dry run against the exact contract a launch would enforce — the same requirement ledger, the same validator. Exit 0 means valid; exit 2 prints every issue and launches nothing.

bash
# check the program before launching; nothing is dispatched
bullswarm workflow plan validate "1. Fix the parser. 2. Update the docs." --cwd /abs/path/to/repo --program plan.json --json

# launch the same goal with the validated program; --watch follows progress here
bullswarm workflow goal "1. Fix the parser. 2. Update the docs." --cwd /abs/path/to/repo --program plan.json --watch

Use exactly the same goal text for validate and launch: requirements are derived from the goal, and numbered clauses (1., 2.) become requirement-1, requirement-2, and so on — a goal with no numbered items is one requirement-1. Exit 0 carries an advisories array — all-writers-high, docs-at-high, requirement-unchecked — which is advice, not refusal.

A complete small program ​

Goal: 1. Add --since to runs list. 2. Document it in README. 3. Write the run records file.

json
{
  "schemaVersion": "bullswarm.workflow.program.v2",
  "actions": [
    {
      "id": "since-flag",
      "role": "produce",
      "purpose": "Add --since to runs list with a unit test",
      "dependsOn": [],
      "affects": ["requirement-1"],
      "ownedFiles": ["src/workflow/runs-cli.js", "tests/runs-list.test.js"],
      "evidence": [{ "type": "command", "cmd": "node --test tests/runs-list.test.js" }],
      "evidenceFor": [],
      "prompt": "In <cwd>, add a --since <time> flag to `bullswarm workflow runs list` in src/workflow/runs-cli.js with a unit test in tests/runs-list.test.js. Others share this tree: preserve their edits and report any file you need outside your territory. Run `npm test` and quote the summary line."
    },
    {
      "id": "readme",
      "role": "produce",
      "purpose": "Document --since in README",
      "dependsOn": [],
      "affects": ["requirement-2"],
      "ownedFiles": ["README.md"],
      "evidenceFor": [],
      "prompt": "In <cwd>, document the --since <time> flag of `bullswarm workflow runs list` in the runs section of README.md, matching the style of the neighbouring flags. Edit README.md only."
    },
    {
      "id": "records",
      "role": "produce",
      "deliverable": { "type": "data", "paths": ["out/records.json"] },
      "purpose": "Write records that match the documented format",
      "dependsOn": [],
      "affects": ["requirement-3"],
      "ownedFiles": ["out/records.json"],
      "evidence": [{ "type": "schema", "file": "out/records.json", "schema": "schemas/record.json" }],
      "evidenceFor": [],
      "prompt": "In <cwd>, write out/records.json as records that match schemas/record.json."
    },
    {
      "id": "integrate",
      "role": "combine",
      "deliverable": "files",
      "purpose": "Reconcile both edits and run the full suite",
      "dependsOn": ["since-flag", "readme"],
      "affects": ["requirement-1", "requirement-2"],
      "ownedFiles": [],
      "evidenceFor": [],
      "prompt": "In <cwd>, read both dependency outputs, resolve any shared-file requests they raised, make the README wording match the flag as implemented, run `npm test`, and quote the summary line."
    },
    {
      "id": "verify",
      "role": "check",
      "route": { "independentOf": ["since-flag"] },
      "effort": "high",
      "purpose": "Independently confirm the flag works and is documented, and the records file exists",
      "dependsOn": ["since-flag", "readme", "records", "integrate"],
      "affects": [],
      "ownedFiles": [],
      "evidenceFor": ["requirement-1", "requirement-2", "requirement-3"],
      "prompt": "In <cwd>, exercise `bullswarm workflow runs list --since <time> --json` against a fixture home with runs on both sides of the bound, check that README.md describes the flag and its accepted time forms, and check that out/records.json exists. Inspect only; try to break it."
    }
  ]
}

<cwd> stands for your absolute repository path — nothing is substituted, so write the real path into every prompt. Three writers run at once, the integrator waits for the code and README writers, and the acceptance step runs last:

bash
# validate the file above against the same goal text
bullswarm workflow plan validate "1. Add --since to runs list. 2. Document it in README. 3. Write the run records file." --cwd /abs/path/to/repo --program plan.json --json

# once it validates, launch it
bullswarm workflow goal "1. Add --since to runs list. 2. Document it in README. 3. Write the run records file." --cwd /abs/path/to/repo --program plan.json --json

Steer a live run ​

The plan is never frozen: you add steps with workflow add (above), and a step runs again with workflow step rerun. A v3 run's steps are never changed or removed. plan export and plan revise, which replaced a v2 run's whole plan, were removed in 0.38.0; they exit 2 with one sentence naming workflow add and step rerun.

workflow pause <shortId> starts nothing new while running agents finish (--now stops them and reruns them after resume); workflow add applies while paused, and only workflow resume <shortId> continues the run. Steering is the softer form: workflow steer <shortId> --message "<guidance>" queues guidance for the caller, and a run that finishes before anyone acts on it lists it as steering not acted on.

WARNING

An addition never lifts a pause, and a finished run that gets an addition is reopened and finishes again.

When a run finishes ​

A v2 run never waits for its caller: when nothing more can happen on its own it finishes and hands back what is left. A v3 run finishes the same way, except that it parks with status waiting at a gate or at a loop out of rounds (see A run that waits for you), and a completed v3 run hands nothing back. The terminal lines give outcome: (completed, partial, or cancelled) plus, for a v2 run, whether the run is verified, reason: in one line, one step … / requirement … line per unfinished item, and then your call: with one command per option.

OptionWhen it fitsWhat to do
addthe plan needs a fix or a new stepbullswarm workflow add <shortId> --steps part.json, then bullswarm workflow wait <shortId> <added ids>
retrya step stopped for a reason a retry fixes: a crashed or silent worker, a usage limit, or no pool freebullswarm workflow resume <shortId>; it reruns exactly those steps and the steps blocked behind them
reruna step failed in a run started by this versionbullswarm workflow step rerun <shortId> <step> [--avoid <pool>] runs it again with its last attempt's handoff
acceptyou choose to keep a failed step as it isbullswarm workflow step accept <shortId> <step> --reason "…": recorded as your choice, never proof
take overthe rest is small, or needs something only you havedo it yourself; bullswarm workflow runs result <shortId> --json names every step's output
restartthe goal or the approach was wrongstart a new run: bullswarm workflow goal "<goal>" --cwd <dir> --program <file.json>

retry appears only when a step is retryable. When nothing is retryable, resume prints nothing to retry, starts nothing and exits 1. A step with a known return time shows its pool is back at <time>; resuming before then can fail it again (at once when no other pool is free). rerun and accept appear only when a step failed.

A saved run that is not v3 is view-only: its your call: lines were written when it finished and may name plan export/plan revise, workflow resume of a stopped planner or scout, or a check to rerun. Those commands now exit 2 on it; start a new v3 run instead.

When the review loop left a requirement failing in a saved v2 run, its your call: also offered the check that judged it: rerun is bullswarm workflow step rerun <shortId> <check> --avoid <pool> (it judges again on another pool), and accept is bullswarm workflow step accept <shortId> <check> --requirement <id> --reason "…". An accepted requirement reads requirement <id>: failed · accepted by choice "<reason>": it stays failed and the run stays not verified.

The failure rule and needs-you block ​

Each step gets one automatic retry in total. A process failure (a crash, a silent worker, a sign-in failure, a provider error, or a worker that died at start) retries on another eligible pool; a gate failure retries on the same pool with the failure attached. A started act step is never retried automatically. A usage limit is never retried, moved or waited out: the step comes back to you (see below). Only dependents wait; unrelated steps keep running. Saved runs show the rules they ran under.

When a step needs you, the watch gives one block with the failure, each try, steps still running, dependents waiting on it, and four commands. The examples below are the block a saved v2 run printed; a v3 run's block offers add steps (bullswarm workflow add <id> --steps <file.json>) and then wait in place of change the step and then edit it. For example:

text
✗ variants needs you · command evidence failed after 1 retry
  evidence  node check-assets.mjs out/ → exit 1
            banner-b.png has the wrong dimensions
  try 1  pool-a · image model · 11m00s · 3 files
  try 2  same pool, failure attached · 7m00s · 3 files
  still running: copy · waiting on this: pick
  your call:
    rerun elsewhere  bullswarm workflow step rerun <id> variants --avoid pool-a
    change the step  bullswarm workflow plan export <id> --out plan.json
      then edit it   bullswarm workflow plan revise <id> --program plan.json
    take over        output: <absolute output path>
    accept anyway    bullswarm workflow step accept <id> variants --reason "…"
  next: bullswarm workflow watch <id> --until trouble --after <sequence> --since <iso>

Choose one your call command, then relaunch the exact next: line. When no other pool could run the step, the first option reads retry here with a plain step rerun. Rerunning with --avoid keeps that pool excluded in the step's route. Accepting records choice, never proof; rerunning the step undoes acceptance. When a review still fails after its fix, the block names the check that judged the failing requirement; another check that failed one gets its own also judged by <check>: lines with its rerun and accept.

A usage limit ends the step: a spent 5-hour or weekly window, or no credit left. The step comes back to you at once. Bullswarm does not wait for the pool, move the step to another pool, or retry it, and the rest of the run keeps going. Nothing about the pool is remembered either: Bullswarm reads the pool's meter again at once (when it cannot be read, the pool counts as full until its reset only when the provider named that reset or an earlier meter reading gave it), and later steps route on that reading, so a window it shows at 100% keeps the pool out until that window resets. A limit notice that names no reset ends the step too; its block then prints back at only when every pool that can run the step is out and one of them has a known return. When the reset is known, the block says when the pool is back and adds a wait for it option:

text
✗ build needs you · out of quota · not retried
  why       <the provider's limit notice>
  back at   2026-09-25T14:00:00.000Z
  try 1  pool-a · model-a · 4m00s · 0 files
  your call:
    rerun elsewhere  bullswarm workflow step rerun <id> build --avoid pool-a
    wait for it      after 2026-09-25T14:00:00.000Z: bullswarm workflow step rerun <id> build
    change the step  bullswarm workflow plan export <id> --out plan.json
      then edit it   bullswarm workflow plan revise <id> --program plan.json
    take over        output: <absolute output path>
    accept anyway    bullswarm workflow step accept <id> build --reason "…"
  next: bullswarm workflow watch <id> --until trouble --after <sequence> --since <iso>

The same happens when no pool that can run the step is free when it is picked: each one is nearly spent, or at its 5-hour, weekly or monthly limit. The why line then names every pool and its reason, for example no pool with quota to spare: pool-a at its 5-hour limit until <time>; pool-b at its weekly limit until <time>; pool-c nearly spent (forecast 97.0%) until <time>. When one of the reasons is not a usage limit it reads no pool free: …, and the header reads no eligible pool. back at is then the earliest known return among those pools; a pool whose return is unknown is skipped. When another pool that can run the step is free, routing picks it and nothing comes back to you. A retry the step was promised (after a crash, a sign-in failure or a failed gate) that finds no free pool keeps its own failure, and its why ends · no retry: <pool> <reason>; ….

Your choices after a usage limit, or with no pool free:

  • rerun elsewhere: bullswarm workflow step rerun <id> <step> --avoid <pool>, when another pool can run the step now;
  • wait for it: after the back at time, run the printed bullswarm workflow step rerun <id> <step> yourself; nothing reruns it for you;
  • accept anyway, change the step, or take over, as for any other failure;
  • cancel the run: bullswarm workflow cancel <id>.

A short "too many requests" rate limit is not a usage limit. It backs off on the same pool at most twice (20 s, then 60 s, or the wait it names when that is at most 2 minutes), then comes back to you. Its header reads rate limited · backed off twice (once after one backoff; a backoff is never counted as the retry), and a try after a backoff reads · after a rate-limit backoff. One that names a longer wait comes back to you at once, with back at at the end of that wait. One whose pool is no longer free for the backoff (at its 5-hour, weekly or monthly limit, or nearly spent in the meantime) comes back to you at once too: as out of quota when that pool is out on a usage limit, with back at its return when that is known. A sign-in failure, a provider error or a worker that died at start still gets the step's one automatic retry by itself, on another free pool when there is one. After a sign-in failure that retry skips every pool that shares the credential; nothing is stored, so a later step can pick that pool again.

A pool whose weekly or monthly window closes soon and that this step would push past its limit (the router's "expiring but draining") is never given the step, even when it is the only pool left. The step takes another pool that can run it, or comes back to you with <pool> nearly spent (forecast <n>%) until <time> in its why. A pool you named (the run's --worker-pool, or a route that allows only that pool) is exempt. The dispatched planner and the preflight scout are never given such a pool either; only a pool you pinned is exempt (--orchestrator <pool> --orchestrator-strict for the planner, --worker-pool for the scout). Their schema correction, and the one retry on the same pool they get when no other pool can run them, never go back to a pool that has become nearly spent either: the correction moves to another free pool, and with none free the planner or scout stops and tells you, with that pool's nearly spent reason.

A step rerun (or a resume) after a failure the pool caused starts on another pool when one can take the step now. Pool-caused failures are a sign-in failure, a provider error, or a worker that exited with an error before it answered or changed a file. The rerun prints pool starts on another pool than <pool> …, and the attempt's route reason starts with moved off <pool>. The same pool runs it only when nothing else can. Unlike --avoid, this changes nothing in the step's route. A usage limit is not one of these: a rerun after one is routed as usual, so add --avoid <pool> to keep it off that pool.

A run saved by 0.37.x may have ended partial with the workflow planner stopped on a usage limit: … or the preflight scout stopped on a usage limit: …, and its watch may show ✗ planner stopped · … or ⚠ preflight scout stopped · …. 0.38.0 removed the dispatched planner and the preflight scout (the pinned-pool exemption above applies only to those saved runs), and such a run is view-only: its your call text names workflow resume and plan revise, which now exit 2 on it. Start a new run with a program you write instead.

bash
# the compact result: status, verified (v2), reason, every action with a v3 step's answer, usage, and next
bullswarm workflow runs result ab12cd --json --summary

On a finished v3 run, the text form prints each step's checked answer:

text
# workflow result  wf-mulitn5f-6c56dd  (5r8jyi)
# status  completed  result ready
# outcome  all 1 step succeeded
# proof  1 answer checked: task
# answer  task  {"words":13}
# actions  1
  task                     analyze/medium  succeeded

Next steps ​

  • Program format — every program field, kind, and enforced rule.
  • Observing runs — watch the run, wake on events, and read the TUI.
  • Run — when one bounded task is all you need.

Released under the MIT License.