bullswarm 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +35 -13
- package/docs/experiments/2026-08-29-dogfood-bullswarm-builds-bullswarm.md +82 -0
- package/package.json +1 -1
- package/skill/SKILL.md +21 -7
- package/src/help.js +14 -6
- package/src/workflow/cli.js +98 -10
- package/src/workflow/dashboard.js +434 -27
- package/src/workflow/decision.js +10 -3
- package/src/workflow/goal.js +45 -13
- package/src/workflow/result.js +78 -1
- package/src/workflow/runner.js +15 -2
- package/src/workflow/runtime.js +49 -8
- package/src/workflow/validate.js +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 — exact routes, cheaper plans, clearer results
|
|
4
|
+
|
|
5
|
+
- `workflow goal` can now guarantee an exact planner model and a separate exact
|
|
6
|
+
worker route with `--orchestrator-model`, `--worker-pool`, and
|
|
7
|
+
`--worker-model`. The worker lock is runtime-owned and propagates through the
|
|
8
|
+
scout, ordinary actions, fan-out items, verification repairs, reverification,
|
|
9
|
+
and extraction helpers; unsupported or excluded models fail closed instead
|
|
10
|
+
of silently substituting another model.
|
|
11
|
+
- An action-bearing planner `proceed` is normalized to the schema-equivalent
|
|
12
|
+
`needs_more_work` program before validation. This removes a redundant
|
|
13
|
+
correction turn without changing the proposed graph or weakening any safety
|
|
14
|
+
check (the prior real run spent four frontier planner turns correcting this
|
|
15
|
+
exact representation mismatch).
|
|
16
|
+
- `workflow runs result` now selects the latest successful verifier that
|
|
17
|
+
transitively covers the delivery, so a final suite verifier depending on
|
|
18
|
+
unit verifiers is surfaced ahead of a narrower direct unit check.
|
|
19
|
+
- The same result envelope now adds a backward-compatible `deliveries[]`
|
|
20
|
+
frontier for parallel multi-worker outcomes while preserving the singular
|
|
21
|
+
`delivery` field for existing callers.
|
|
22
|
+
- The autonomous planner now batches cheap homogeneous edits instead of paying
|
|
23
|
+
for a worker and unit verifier per tiny file; substantial independent units
|
|
24
|
+
still fan out and retain focused verification before the final suite.
|
|
25
|
+
- Narrow SSH and phone terminals now open on a full-width workflow timeline;
|
|
26
|
+
`t` toggles between that overview and the phase browser without affecting the
|
|
27
|
+
existing Enter/Esc agent drill-down.
|
|
28
|
+
- Goal-level `--orchestrator <pool>` is now a preference with immediate
|
|
29
|
+
fallback when that pool is quota-gated, ineligible, or unavailable. Exact
|
|
30
|
+
provider testing moves to `--strict-orchestrator <pool>`. Quota waits now
|
|
31
|
+
refresh the durable runner heartbeat at least every 30 seconds, preventing a
|
|
32
|
+
live waiting run from being falsely reconciled as interrupted and making
|
|
33
|
+
cooperative cancellation responsive during long meter-poll intervals.
|
|
34
|
+
|
|
35
|
+
- The runtime now owns the acceptance bar for every verify and re-verify. Each
|
|
36
|
+
verifier's instructions end with a fixed "Acceptance standard (runtime-owned;
|
|
37
|
+
it overrides any stricter rule in the instructions above)": `ok:false` means
|
|
38
|
+
the work is unusable — its acceptance command fails, a required deliverable
|
|
39
|
+
is missing, or the answer is nonsense — and everything else (style, scope,
|
|
40
|
+
cosmetic mismatches, process rules the goal never stated such as append-only,
|
|
41
|
+
files changed by other actions in the shared tree) goes in `concerns` under
|
|
42
|
+
`ok:true`. A re-verify rejects only when the work is still unusable or the
|
|
43
|
+
repair broke the acceptance checks. Direction from the user after `8ebi8a`:
|
|
44
|
+
"unless it is completely nonsense or unable to finish I don't see a reason to
|
|
45
|
+
reject so easily".
|
|
46
|
+
- Repair prompts carry a runtime-owned shared-tree rule: edit only the files
|
|
47
|
+
the reviewed work owns; a concern about other files is not the repair's to
|
|
48
|
+
resolve; never revert, checkout or delete other actions' changes. Earned on
|
|
49
|
+
`8ebi8a`: `verify-docs` rejected on a repo-wide `git diff --stat` scope check
|
|
50
|
+
while siblings were writing, and its repair reverted five `src/` files it did
|
|
51
|
+
not own to satisfy the concern.
|
|
52
|
+
- Planner contract: rule 2 requires exactly one owner per file, including any
|
|
53
|
+
existing test the change breaks (the `workflow-adaptive.test.js:206` gap for
|
|
54
|
+
the fourth time); rule 7 restates the lenient bar above; the validator line
|
|
55
|
+
now says ids are unique across the whole run, finished and failed actions
|
|
56
|
+
included (turn 2 of `8ebi8a` re-proposed the blocked id `verify-suite` and
|
|
57
|
+
spent a 97 s correction turn on it).
|
|
58
|
+
- Goal-4 rerun on `7724da1` (`8ebi8a`, rule 7 + PR #5): 42 min 03 s, three
|
|
59
|
+
planner turns (775 s, 31 %), parallelism 1.34, 23 dispatches (20 on
|
|
60
|
+
`kaihk/gpt-5.6-luna`), three repair rounds each rejected on re-verify for
|
|
61
|
+
reasons the prompts caused, tail of five actions blocked, recovery program
|
|
62
|
+
auto-completed, 315/315, existing tests +179/−1. Goal-4 line:
|
|
63
|
+
44 → 72 → 37 → 25 → 36 → 42 min.
|
|
64
|
+
|
|
65
|
+
## 0.17.0 — the timeline tells the execution story
|
|
66
|
+
|
|
67
|
+
- Workflow timeline (PR #5) hardened after a 16-agent adversarial review against
|
|
68
|
+
real run state (23 findings, 21 confirmed): worker rows now name their phase
|
|
69
|
+
(`├─✓ [Verify] verify-impl`) because concurrent phases interleave in time
|
|
70
|
+
order and the tree glyph alone hung a row under the wrong phase; a phase whose
|
|
71
|
+
actions never started (a blocked tail) is shown as `[Phase: X] blocked`
|
|
72
|
+
instead of vanishing; the header line is truncated so widths down to 20
|
|
73
|
+
columns really hold; PgUp now scrolls the timeline to earlier rows (it was a
|
|
74
|
+
dead key at the newest view) and scroll state resets when the pane changes;
|
|
75
|
+
below 100 columns the footer and status line no longer advertise a timeline
|
|
76
|
+
the narrow layout does not render. Confirmed minors left open are listed on
|
|
77
|
+
PR #5.
|
|
78
|
+
- Reworked the autonomous workflow TUI around a human-readable execution story:
|
|
79
|
+
the existing Workflow Planner and phase sidebar now sits beside a timestamped
|
|
80
|
+
timeline of completed preflight, planner, phase, and worker milestones; active
|
|
81
|
+
workers and the waiting/running planner are isolated in a Live section with
|
|
82
|
+
their latest normalized action and stream heartbeat, and future work stays in
|
|
83
|
+
a distinct Next section. `v` keeps raw action-ledger and event evidence one key
|
|
84
|
+
away without mixing it into the default view.
|
|
85
|
+
- Rule 7: a verify checks the goal's own acceptance criteria and never adds a
|
|
86
|
+
process rule the goal does not state (append-only, existing tests
|
|
87
|
+
untouched); when the implementation changes what an existing assertion
|
|
88
|
+
pins, a worker must own updating it. Earned three times on goal 4 (attempt
|
|
89
|
+
3, `r2vu9i`, `euh622`): the planner wrote "EXTEND BY APPENDING only" into
|
|
90
|
+
the test worker and its verify while the goal said "do NOT modify existing
|
|
91
|
+
tests except to extend them" and item 5 forced `programFeatures` to grow,
|
|
92
|
+
so the assertion at `workflow-adaptive.test.js:206` had no owner, the
|
|
93
|
+
re-verify rejected the mandated extension, and a planner turn recovered.
|
|
94
|
+
- Goal-4 rerun on v0.16.0 (`euh622`): 36 min 00 s, four stage phases in the TUI
|
|
95
|
+
(implement, tests, verify, report) instead of sixteen one-action rows, 22/24
|
|
96
|
+
dispatches on `kaihk/gpt-5.6-luna`, auto-completed, 319/319; two planner
|
|
97
|
+
turns because of the false rejection above (planner turn 2: "an append-only
|
|
98
|
+
rule that the goal itself makes unsatisfiable").
|
|
99
|
+
|
|
3
100
|
## 0.16.0 — the planner sets the width; a re-verify judges the repair
|
|
4
101
|
|
|
5
102
|
- A re-verify after a repair round now receives the concerns it raised and the
|
package/README.md
CHANGED
|
@@ -188,8 +188,23 @@ Resume a process-interrupted autonomous run from its persisted workflow:
|
|
|
188
188
|
bullswarm workflow goal --resume <shortId> --json
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
`--orchestrator <pool>`
|
|
192
|
-
|
|
191
|
+
`--orchestrator <pool>` expresses a preference and immediately falls back to
|
|
192
|
+
another eligible pool if that provider is quota-gated or unavailable. Ordinary
|
|
193
|
+
use can leave selection on `auto`. For controlled provider QA only,
|
|
194
|
+
`--strict-orchestrator <pool>` requires that exact pool and may wait for its
|
|
195
|
+
quota window. Controlled comparisons can additionally pin the exact planner
|
|
196
|
+
and worker routes without changing global strategy:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
bullswarm workflow goal "Implement and verify the change" --cwd . \
|
|
200
|
+
--strict-orchestrator codex --orchestrator-model gpt-5.6-sol \
|
|
201
|
+
--worker-pool opencode2 --worker-model kaihk/gpt-5.6-luna
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The worker lock covers the scout, ordinary runs, fan-out items, repairs,
|
|
205
|
+
re-verification, and runtime extraction helpers. A pool that cannot guarantee
|
|
206
|
+
the requested model is ineligible rather than silently substituting another
|
|
207
|
+
model. `--max-agents` and `--max-workflow-seconds` are
|
|
193
208
|
advisory planning targets; `--max-expansion-rounds` is also an advisory
|
|
194
209
|
convergence target. Hard structural safeguards are adjusted with
|
|
195
210
|
`--max-actions` and `--max-items-per-expansion`.
|
|
@@ -258,8 +273,9 @@ Values accept ISO timestamps, local `YYYY-MM-DD` dates, `today`, `yesterday`,
|
|
|
258
273
|
After a workflow reaches a terminal state, agents should consume
|
|
259
274
|
`workflow runs result <id> --json` instead of probing `state.json`, task files,
|
|
260
275
|
or provider-specific output. The versioned `bullswarm.workflow.result.v1`
|
|
261
|
-
envelope
|
|
262
|
-
|
|
276
|
+
envelope retains the primary `delivery`, adds a `deliveries[]` frontier when
|
|
277
|
+
parallel workers jointly form the outcome, and identifies their strongest
|
|
278
|
+
matching verification verdict. It also includes progress, step logs, tokens, and an explicitly
|
|
263
279
|
complete-or-partial tool-call total. `runs show` remains the low-level debugging
|
|
264
280
|
surface.
|
|
265
281
|
Goal launch output includes an `instructions` handoff with four named paths:
|
|
@@ -289,21 +305,27 @@ bullswarm workflow watch <shortId> --verbose # detailed agent/action view
|
|
|
289
305
|
```
|
|
290
306
|
|
|
291
307
|
`workflow tui` is the interactive, Claude-style `/workflows` view. For an
|
|
292
|
-
autonomous goal its left navigation stacks a compact
|
|
293
|
-
the Phases panel; internal planner turns never appear as workers or phases.
|
|
294
|
-
|
|
295
|
-
|
|
308
|
+
autonomous goal its left navigation stacks a compact Workflow Planner panel
|
|
309
|
+
above the Phases panel; internal planner turns never appear as workers or phases.
|
|
310
|
+
The default desktop main panel is a timestamped workflow timeline: completed
|
|
311
|
+
preflight, planner-checkpoint, phase-transition, and worker-result events stay
|
|
312
|
+
above a live section containing the waiting/running Workflow Planner and workers,
|
|
313
|
+
each with its latest semantic action and stream heartbeat. Planned work is kept
|
|
314
|
+
in a separate Next section so it cannot be mistaken for execution evidence.
|
|
315
|
+
Select Workflow Planner and press Enter, or press `o`
|
|
316
|
+
anywhere, to open a summary-first planner overview: what it is doing now,
|
|
296
317
|
its latest decision in plain language, why it chose that path, what happens
|
|
297
|
-
next, progress, and the last three semantic actions. Press `v`
|
|
298
|
-
technical
|
|
299
|
-
and artifact paths. Status marks are consistent throughout the tree: `○` not started,
|
|
318
|
+
next, progress, and the last three semantic actions. Press `v` from the timeline
|
|
319
|
+
for workflow technical state, or from Workflow Planner for provider session,
|
|
320
|
+
every checkpoint turn, usage, prompt, and artifact paths. Status marks are consistent throughout the tree: `○` not started,
|
|
300
321
|
an animated Braille spinner for active work, `⧖` waiting, `✓` finished, and
|
|
301
322
|
`✗` failed or interrupted. The non-emoji `⧖` avoids the inconsistent cell
|
|
302
323
|
width of `⌛` across terminal fonts. It watches ongoing runs from disk and supports `j`/`k` or arrow-key selection, Enter for
|
|
303
324
|
details, Esc to go back, `c` to request a confirmed cooperative stop, `r` to
|
|
304
325
|
refresh, and `q` to detach. Its responsive drill-down fits both desktop and
|
|
305
326
|
mobile SSH terminals without squeezing phase, agent, and activity into three
|
|
306
|
-
narrow columns.
|
|
327
|
+
narrow columns. Below 100 columns it opens on a full-width timeline; press `t`
|
|
328
|
+
to toggle Timeline and Phases, then use Enter/Esc for agents and activity.
|
|
307
329
|
|
|
308
330
|
```bash
|
|
309
331
|
bullswarm workflow tui
|
|
@@ -431,7 +453,7 @@ appending anything. It executes ready actions, observes their durable results,
|
|
|
431
453
|
and calls the planner again. `events.jsonl`, `state.json`, the TUI, and JSON
|
|
432
454
|
inspection expose the same plan, actions, attempts, decisions, budgets, and
|
|
433
455
|
artifacts. See `workflows/adaptive-code-review.json` for a complete example.
|
|
434
|
-
Planner actions cannot set `pool`, `addDir`, or `taskFile`. If those need to be
|
|
456
|
+
Planner actions cannot set `pool`, `model`, `addDir`, or `taskFile`. If those need to be
|
|
435
457
|
fixed by the initiator, declare them under the `decide` step's `actionDefaults`;
|
|
436
458
|
otherwise eligible capable pools are ranked by live quota surplus.
|
|
437
459
|
|
|
@@ -307,3 +307,85 @@ is met: each round fixed a defect the deliverable needed fixed. Prediction befor
|
|
|
307
307
|
rounds and the 10-min recovery turn vanish, wall ≈ 30 min"; observed 25 min 13 s with one planner turn.
|
|
308
308
|
|
|
309
309
|
Cost: 22 of 23 dispatches on the unmetered opencode2 seat; Claude quota spent on one 247 s planner turn.
|
|
310
|
+
|
|
311
|
+
## Goal-4 rerun on v0.16.0 (phase = stage) — `euh622` (wf-mtej85ws-18a3c0), 15:25:14 → 16:01:14 Z — **36 min 00 s, auto-completed; one planner-authored false rejection cost the recovery turn**
|
|
312
|
+
|
|
313
|
+
Runtime `4bfd7f4` = released v0.16.0 (rule 3 "a phase is a pipeline stage … never one per action"); workers pinned to
|
|
314
|
+
`opencode2`/`kaihk/gpt-5.6-luna` (cleared after); orchestrator `claude-code`/`claude-opus-5`; fresh fixture `g4-bs-v5`.
|
|
315
|
+
|
|
316
|
+
| metric | `r2vu9i` | `bizp4s` | **`euh622`** |
|
|
317
|
+
| --- | ---: | ---: | ---: |
|
|
318
|
+
| wall | 36 min 58 s | 25 min 13 s | 36 min 00 s (2 157 s) |
|
|
319
|
+
| planner turns / plannerSec | 2 / 648 s | 1 / 247 s | 2 / 728 s (34 %) — 462 s + 266 s |
|
|
320
|
+
| dispatches | 26 | 23 | 24 (22 luna + 2 opus) |
|
|
321
|
+
| max concurrent / parallelism | 4 / 1.55 | 4 / 1.77 | 4 / 1.5 |
|
|
322
|
+
| phases in the TUI | 19 one-action rows | 16 one-action rows | **4 stages** (implement 2, tests 3, verify 6 + repairs, report 2) + 2 recovery phases |
|
|
323
|
+
| repairs (rounds / repaired ok / re-verify rejected) | 4 / 2 / 2 | 3 / 2 / 1 | 4 / 2 / 2 |
|
|
324
|
+
| tests after | 314 | 319 | **319/319**; existing tests +116/−1 (the mandated `:206` extension) and +153/−0 |
|
|
325
|
+
|
|
326
|
+
What the phase change did: the planner wrote `implement` (impl ∥ docs), `tests` (three test writers), `verify` (six
|
|
327
|
+
verifies), `report` — the layout asked for, with no scheduling change (impl ∥ docs started together; three test writers
|
|
328
|
+
and verify-impl started the second impl landed; verify-docs ran while impl was still running). Width was one docs
|
|
329
|
+
worker (three files merged; off the critical path) but three test writers — comparable to `bizp4s`.
|
|
330
|
+
|
|
331
|
+
The four repair rounds:
|
|
332
|
+
- `verify-impl` r1: fan-out items dispatched through plain `dispatch()`, so `stepTemplate.outputSchema` was never applied
|
|
333
|
+
— real. Re-verify rejected: the repair tested `step.outputSchema` instead of `itemStep.outputSchema` — the listed
|
|
334
|
+
concern still unresolved, exactly the rejection the re-verify rule permits. r2 repaired; re-verify ok.
|
|
335
|
+
- `verify-test-runtime` r1: the retry case did not assert the `errors` payload of `action.output_schema_retry` — real.
|
|
336
|
+
- `verify-test-refs` r1: the acceptance command failed on the pre-existing assertion at `workflow-adaptive.test.js:206`
|
|
337
|
+
(`programFeatures` pinned to three entries) because goal item 5 mandates adding `outputSchema` to it. The repair
|
|
338
|
+
extended the assertion (+116/−1). Re-verify rejected BECAUSE an existing assertion changed — the planner had written
|
|
339
|
+
"EXTEND BY APPENDING new test cases only" into `test-refs` and "shows APPENDED cases only" into `verify-test-refs`,
|
|
340
|
+
and "Do NOT modify existing tests" into `impl`, while the goal says "do NOT modify existing tests except to extend
|
|
341
|
+
them". No worker owned the assertion; the verifier treated its prompt's rule as an unresolved concern. `verify-suite`,
|
|
342
|
+
`report`, `verify-report` blocked → planner turn 2 (266 s), whose reason is exact: "verify-test-refs ended ok:false
|
|
343
|
+
on an append-only rule that the goal itself makes unsatisfiable — goal item 5 mandates adding 'outputSchema' to
|
|
344
|
+
programFeatures". Recovery program `verify-suite-full` → `final-report` → `verify-final-report`, auto-completed.
|
|
345
|
+
Third occurrence of this shape (attempt 3, `r2vu9i`, here); `bizp4s` avoided it only because its implementation
|
|
346
|
+
happened to keep the old assertion true.
|
|
347
|
+
|
|
348
|
+
Cost of the false rejection: the 266 s planner turn plus the serialised tail ≈ 5–6 min; the rest of the gap to
|
|
349
|
+
`bizp4s` is variance (planner turn 1 462 s vs 247 s on the same contract; `impl` 445 s vs 377 s).
|
|
350
|
+
|
|
351
|
+
Fix committed after the run, unreleased (`71960ae`): rule 7 — "A verify checks the goal's own acceptance criteria …
|
|
352
|
+
never add a process rule the goal does not state (append-only, tests untouched); when the implementation changes what
|
|
353
|
+
an existing assertion pins, a worker must own updating it." Proof pending a rerun on that commit.
|
|
354
|
+
|
|
355
|
+
## Run `8ebi8a` — runtime `7724da1` (rule 7 `71960ae` + PR #5 merge), luna pinned, fixture g4-bs-v6
|
|
356
|
+
|
|
357
|
+
Launched 2026-08-29 16:42 Z as the live proof of rule 7. Result: **42 min 03 s**, worse than `euh622` (36 min) and
|
|
358
|
+
`bizp4s` (25 min). Measured (`bs-g4-v6-metrics.json`): 3 planner turns / 775 s (31 % of wall; turn 2 430 s, correction
|
|
359
|
+
turn 97 s, turn 1 ≈ 248 s derived), parallelism 1.34, max 3 concurrent, 23 dispatches (20 workers on
|
|
360
|
+
`kaihk/gpt-5.6-luna`, 3 planner turns on claude-code/opus), 3 repair rounds — every re-verify rejected — 1 validator
|
|
361
|
+
correction, auto-completed by `program-completion`, `npm test` 315/315, existing tests +179/−1 (the mandated `:206`
|
|
362
|
+
extension, finally done by a named action `fix-pinned-test`).
|
|
363
|
+
|
|
364
|
+
The three rejections were each legitimate under the re-verify rule of `9af8fdf`; the defect was in the prompts the
|
|
365
|
+
planner wrote, and rule 7 did not stop it:
|
|
366
|
+
- `verify-impl` r1: real concern. Repair 1 removed `outputSchema` from `programFeatures` so the OLD assertion at
|
|
367
|
+
`workflow-adaptive.test.js:206` would pass — because `impl` was told "do NOT modify existing tests", `tests-runtime`
|
|
368
|
+
(owner of that file) was never told to extend `:206`, and `verify-impl` expected `impl` to have done it. Re-verify
|
|
369
|
+
rejected (a regression: item 5 mandates the entry). r2 re-added it; the old assertion failed again; rejected.
|
|
370
|
+
Turn 2's reason names it: "my round-1 prompt asked for it". Nobody owned the assertion — the fourth run with this gap.
|
|
371
|
+
- `verify-docs` r1: its prompt said "only those three doc files were changed by this worker (`git diff --stat`)"; the
|
|
372
|
+
repo-wide diff showed `impl`'s files, so it rejected on scope. The repair, told "Do not touch src/", still reverted
|
|
373
|
+
five `src/` files to make `git diff --name-only` show three files (its report: "git diff --name-only reports exactly
|
|
374
|
+
the three requested documentation files… 299 tests"). Re-verify rejected on the missing implementation.
|
|
375
|
+
- Tail blocked: `verify-tests-schema`, `verify-tests-runtime`, `verify-suite`, `report`, `verify-report` depended on
|
|
376
|
+
`verify-impl` (a verdict, chosen so repairs would not edit the same files) → `failed_terminal` → planner turn 2,
|
|
377
|
+
which re-proposed the blocked id `verify-suite` → validator rejection → 97 s correction → recovery program
|
|
378
|
+
`restore-src` → `fix-pinned-test` → `verify-src` / `verify-tests` → `verify-full-suite` → `final-report` →
|
|
379
|
+
`verify-final-report`, all ok.
|
|
380
|
+
|
|
381
|
+
Where the extra time went (vs `bizp4s`): ≈ 17 min in the two failed verify loops, the blocked tail, turn 2 and the
|
|
382
|
+
correction; `impl` 493 s vs 377 s is variance.
|
|
383
|
+
|
|
384
|
+
Conclusion and fix (unreleased, committed after the run): three runs in a row failed on a different planner-authored
|
|
385
|
+
constraint the goal never stated (append-only → contradictory ownership → repo-wide scope check), so contract text
|
|
386
|
+
alone is whack-a-mole. The runtime now owns the bar: every verify/re-verify instruction ends with a fixed acceptance
|
|
387
|
+
standard (ok:false = unusable; everything else is a concern under ok:true; other actions' files are never this unit's
|
|
388
|
+
defect), every repair prompt says to edit only the reviewed work's files and never revert others' changes, rule 2
|
|
389
|
+
requires one owner per file including an existing test the change breaks, and the validator line states run-wide id
|
|
390
|
+
uniqueness. Direction from the user: "unless it is completely nonsense or unable to finish I don't see a reason to
|
|
391
|
+
reject so easily". Claim to test on the next rerun: none of the three `8ebi8a` rejection reasons can produce ok:false.
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -130,8 +130,15 @@ terminal-owned execution.
|
|
|
130
130
|
The detached runner does not depend on the initiating CLI remaining alive.
|
|
131
131
|
Resume a process-interrupted run from its persisted definition with
|
|
132
132
|
`bullswarm workflow goal --resume <shortId> --json`. Leave orchestrator
|
|
133
|
-
selection automatic in normal use
|
|
134
|
-
|
|
133
|
+
selection automatic in normal use. `--orchestrator=<pool>` is a preference
|
|
134
|
+
that falls back when the pool is quota-gated or unavailable;
|
|
135
|
+
`--strict-orchestrator=<pool>` is the exact-provider control for QA and may
|
|
136
|
+
wait for that pool's quota window. For a controlled model comparison, add
|
|
137
|
+
`--orchestrator-model=<model>`, `--worker-pool=<pool>`, and
|
|
138
|
+
`--worker-model=<model>`. The worker constraints cover scout, runs, fan-out
|
|
139
|
+
items, repairs, re-verification, and extraction helpers; a pool that cannot
|
|
140
|
+
guarantee the model is excluded rather than silently substituting it.
|
|
141
|
+
`SIGTERM`/`SIGINT` cooperatively terminate the active delegate and
|
|
135
142
|
persist `interrupted`; later workflow commands also reconcile dead or stale
|
|
136
143
|
owners into that explicit resumable state.
|
|
137
144
|
|
|
@@ -223,8 +230,9 @@ ISO timestamps, local dates, today/yesterday/tomorrow/now, or durations such as
|
|
|
223
230
|
normal ongoing-only scope.
|
|
224
231
|
|
|
225
232
|
When the run is terminal, use `workflow runs result <id> --json` as the
|
|
226
|
-
handoff contract. Its versioned result envelope
|
|
227
|
-
|
|
233
|
+
handoff contract. Its versioned result envelope keeps one primary `delivery`,
|
|
234
|
+
adds every jointly delivered parallel artifact under `deliveries[]`, and points
|
|
235
|
+
to the strongest dependent verification verdict, progress, and usage. Do not guess
|
|
228
236
|
the output schema by scraping task files or assume the last provider response is
|
|
229
237
|
the deliverable; `runs show` is for low-level debugging.
|
|
230
238
|
|
|
@@ -277,12 +285,18 @@ supports explicit model selection, Bullswarm pins an allowed model in the same
|
|
|
277
285
|
effort tier; otherwise that pool is excluded because its implicit default
|
|
278
286
|
cannot be guaranteed. Restore eligibility with `strategy include-model`.
|
|
279
287
|
|
|
280
|
-
In the human TUI, the autonomous orchestrator
|
|
281
|
-
stacked above the phase tree.
|
|
288
|
+
In the human TUI, the autonomous orchestrator is presented as Workflow Planner
|
|
289
|
+
in a compact selectable panel stacked above the phase tree. The default desktop
|
|
290
|
+
view pairs that unchanged navigation with a timestamped workflow timeline:
|
|
291
|
+
finished events stay above a live Planner/worker section with each participant's
|
|
292
|
+
latest semantic action, while unexecuted work stays in a separate Next section.
|
|
293
|
+
Select Workflow Planner and press Enter, or press
|
|
282
294
|
`o`, to see a summary-first overview of its current role, latest decision,
|
|
283
295
|
reason, next action, progress, and recent semantic activity. Press `v` for the
|
|
284
296
|
durable provider session, checkpoint prompts and turns, usage, and artifact
|
|
285
297
|
paths; `v` returns to the overview and Esc returns to phases.
|
|
298
|
+
Below 100 columns the TUI opens on a full-width timeline; `t` toggles Timeline
|
|
299
|
+
and Phases, and Enter/Esc continues through agents and activity.
|
|
286
300
|
The shared state marks are `○` not started, animated Braille spinner active,
|
|
287
301
|
`⧖` waiting, `✓` finished, and `✗` failed or interrupted.
|
|
288
302
|
|
|
@@ -400,7 +414,7 @@ orchestrator for the rest of the run and tries one other eligible pool
|
|
|
400
414
|
`completed_with_concerns` with a ready best-effort artifact when useful work
|
|
401
415
|
exists, or `blocked` when it does not; neither hides failed verification.
|
|
402
416
|
Use `workflows/adaptive-code-review.json` as the starting template.
|
|
403
|
-
Planner proposals cannot choose `pool`, `addDir`, or `taskFile`. Those fields
|
|
417
|
+
Planner proposals cannot choose `pool`, `model`, `addDir`, or `taskFile`. Those fields
|
|
404
418
|
are runtime-owned. An initiator may constrain them with a decide step's
|
|
405
419
|
`actionDefaults`; absent a pinned default, normal capability and quota routing
|
|
406
420
|
selects the worker.
|
package/src/help.js
CHANGED
|
@@ -581,7 +581,11 @@ const workflowGoalText = rich({
|
|
|
581
581
|
{ flag: '--watch', desc: 'immediately follow low-noise progress until terminal; only valid for a new human-readable independent launch — cannot combine with --detach, --foreground, --json, --resume, or --request', default: 'off' },
|
|
582
582
|
{ flag: '--foreground', desc: 'keep execution attached to this terminal instead of detaching', default: 'off (detaches into a background process)' },
|
|
583
583
|
{ flag: '--json', desc: 'print the launch/report document as JSON', default: 'human-readable launch instructions' },
|
|
584
|
-
{ flag: '--orchestrator <pool|auto>', desc: '
|
|
584
|
+
{ flag: '--orchestrator <pool|auto>', desc: 'prefer this orchestrator pool for a new goal or resumed run, falling back immediately when it is quota-gated, ineligible, or unavailable', default: 'auto (capability- and quota-based selection)' },
|
|
585
|
+
{ flag: '--strict-orchestrator <pool>', desc: 'require exactly this orchestrator pool for controlled provider QA; waits when that pool is quota-gated instead of falling back; mutually exclusive with --orchestrator', default: 'off' },
|
|
586
|
+
{ flag: '--orchestrator-model <model|auto>', desc: 'pin the exact model used by the autonomous planner; only pools that can guarantee this model remain eligible', default: 'auto (effort-tier strategy or connector default)' },
|
|
587
|
+
{ flag: '--worker-pool <pool|auto>', desc: 'pin every non-planner dispatch, including scout, fan-out items, repairs, and verifiers, to one pool', default: 'auto (normal routing)' },
|
|
588
|
+
{ flag: '--worker-model <model|auto>', desc: 'pin the exact model for every non-planner dispatch; only pools that can guarantee it remain eligible', default: 'auto (effort-tier strategy or connector default)' },
|
|
585
589
|
{ flag: '--max-agents <n>', desc: 'planning target for total dispatched agents (soft, not a hard stop)', default: '30 (max 500)' },
|
|
586
590
|
{ flag: '--max-expansion-rounds <n>', desc: 'planning target for planner replanning rounds', default: '8 (max 50)' },
|
|
587
591
|
{ flag: '--max-actions <n>', desc: 'planning target for total dispatched actions', default: '40 (max 1000)' },
|
|
@@ -605,6 +609,7 @@ const workflowGoalText = rich({
|
|
|
605
609
|
],
|
|
606
610
|
examples: [
|
|
607
611
|
{ cmd: 'bullswarm workflow goal "Audit this repo for TODOs and file a one-page summary" --cwd .' },
|
|
612
|
+
{ cmd: 'bullswarm workflow goal "Implement and verify the change" --cwd . --strict-orchestrator codex --orchestrator-model gpt-5.6-sol --worker-pool opencode2 --worker-model kaihk/gpt-5.6-luna', note: 'controlled Sol-planner/Luna-worker run' },
|
|
608
613
|
],
|
|
609
614
|
next: 'bullswarm workflow watch <shortId> to follow progress, or bullswarm workflow tui for the interactive browser.',
|
|
610
615
|
});
|
|
@@ -660,7 +665,7 @@ const workflowCapabilitiesText = rich({
|
|
|
660
665
|
options: [{ flag: '--json', desc: 'accepted for consistency with other commands, but has no effect', default: 'output is always JSON regardless of this flag' }],
|
|
661
666
|
safety: ['read-only — performs live pool discovery to populate pool/meter state; nothing is written'],
|
|
662
667
|
examples: [{ cmd: 'bullswarm workflow capabilities' }],
|
|
663
|
-
next: 'bullswarm workflow goal "<goal>" --orchestrator <pool> to
|
|
668
|
+
next: 'bullswarm workflow goal "<goal>" --orchestrator <pool> to prefer one of the reported pools with fallback, or bullswarm strategy show to review model tier assignments.',
|
|
664
669
|
});
|
|
665
670
|
|
|
666
671
|
const workflowInspectText = rich({
|
|
@@ -676,8 +681,9 @@ const workflowInspectText = rich({
|
|
|
676
681
|
|
|
677
682
|
const workflowTuiText = rich({
|
|
678
683
|
usage: 'bullswarm workflow tui [<runId>] [--json] [--all] [--show <runId>] [--cancel <runId>]',
|
|
679
|
-
purpose: 'Open the interactive full-screen
|
|
680
|
-
+ 'and historical runs, or print a
|
|
684
|
+
purpose: 'Open the interactive full-screen workflow timeline with Workflow Planner, phase, '
|
|
685
|
+
+ 'live-agent, and technical drill-down views for ongoing and historical runs, or print a '
|
|
686
|
+
+ 'static/JSON snapshot for a non-interactive caller.',
|
|
681
687
|
args: [{ name: '[<runId>]', desc: 'shortId or runId to open directly in detail view; omit to see the run picker' }],
|
|
682
688
|
options: [
|
|
683
689
|
{ flag: '--json', desc: "print a JSON snapshot instead of opening the interactive browser (list of ongoing runs, or one run's state/report/events when a runId is given)", default: "opens the interactive browser on a TTY; without a TTY, a given runId instead prints one static text detail tree" },
|
|
@@ -689,6 +695,8 @@ const workflowTuiText = rich({
|
|
|
689
695
|
'interactive mode and the --json/--show/--all views are read-only',
|
|
690
696
|
'--cancel writes state.json (cancelRequested=true, status=cancelling) — cooperative, not a force-kill: the workflow stops at its next safe checkpoint',
|
|
691
697
|
'inside the interactive browser, q detaches without stopping the underlying workflow; c requests the same cancellation with a confirmation prompt',
|
|
698
|
+
'the default timeline is derived from durable state and events; press v for raw action-ledger and event evidence',
|
|
699
|
+
'below 100 columns the timeline remains full-width; press t to toggle Timeline and Phases, then Enter/Esc to drill into agents and activity',
|
|
692
700
|
],
|
|
693
701
|
examples: [
|
|
694
702
|
{ cmd: 'bullswarm workflow tui', note: 'interactive run picker' },
|
|
@@ -867,8 +875,8 @@ const workflowRunsShowText = rich({
|
|
|
867
875
|
|
|
868
876
|
const workflowRunsResultText = rich({
|
|
869
877
|
usage: 'bullswarm workflow runs result <shortId|runId> [--json]',
|
|
870
|
-
purpose: 'Print the stable
|
|
871
|
-
+ 'and usage
|
|
878
|
+
purpose: 'Print the stable caller envelope: primary delivery, parallel deliveries[] frontier, '
|
|
879
|
+
+ 'strongest verification verdict, progress, and usage for one run — the intended integration point for scripts and agents.',
|
|
872
880
|
args: [{ name: '<shortId|runId>', desc: 'run identifier' }],
|
|
873
881
|
options: [{ flag: '--json', desc: 'print the full result document as JSON', default: 'human-readable summary (delivery preview truncated to 64KB)' }],
|
|
874
882
|
safety: ['read-only'],
|
package/src/workflow/cli.js
CHANGED
|
@@ -295,21 +295,83 @@ export function shouldAutoWatchGoal(opts) {
|
|
|
295
295
|
opts.json !== true && opts.resume == null && opts.request == null;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
export function applyResumeOrchestratorOverride(doc, requested) {
|
|
299
|
-
if (!requested) return doc;
|
|
300
|
-
|
|
298
|
+
export function applyResumeOrchestratorOverride(doc, requested, strictRequested = null) {
|
|
299
|
+
if (!requested && !strictRequested) return doc;
|
|
300
|
+
if (requested && strictRequested) {
|
|
301
|
+
throw new Error('--orchestrator and --strict-orchestrator are mutually exclusive');
|
|
302
|
+
}
|
|
303
|
+
const strict = Boolean(strictRequested);
|
|
304
|
+
const selected = strictRequested ?? requested;
|
|
305
|
+
const pool = selected === 'auto' ? null : selected;
|
|
301
306
|
doc.intent ??= {};
|
|
302
307
|
doc.orchestration ??= {};
|
|
303
308
|
doc.intent.requestedOrchestrator = pool ?? 'auto';
|
|
304
309
|
doc.orchestration.requestedPool = pool;
|
|
310
|
+
doc.orchestration.strictPool = strict ? pool : null;
|
|
305
311
|
doc.orchestration.selection = pool
|
|
306
|
-
? 'user-
|
|
312
|
+
? (strict ? 'user-strict-for-testing' : 'user-preferred-with-fallback')
|
|
307
313
|
: 'capability-strategy-and-quota';
|
|
308
314
|
for (const phase of doc.phases ?? []) {
|
|
309
315
|
for (const step of phase.steps ?? []) {
|
|
310
316
|
if (step.type !== 'decide') continue;
|
|
311
|
-
|
|
312
|
-
|
|
317
|
+
delete step.pool;
|
|
318
|
+
delete step.preferredPool;
|
|
319
|
+
if (pool) step[strict ? 'pool' : 'preferredPool'] = pool;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return doc;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function applyResumeModelOverrides(doc, {
|
|
326
|
+
orchestratorModel = null,
|
|
327
|
+
workerPool = null,
|
|
328
|
+
workerModel = null,
|
|
329
|
+
} = {}) {
|
|
330
|
+
const normalize = (value) => value === 'auto' ? null : value;
|
|
331
|
+
const plannerModel = normalize(orchestratorModel);
|
|
332
|
+
const workers = normalize(workerPool);
|
|
333
|
+
const workerModelLock = normalize(workerModel);
|
|
334
|
+
if (orchestratorModel == null && workerPool == null && workerModel == null) return doc;
|
|
335
|
+
doc.intent ??= {};
|
|
336
|
+
doc.orchestration ??= {};
|
|
337
|
+
if (orchestratorModel != null) {
|
|
338
|
+
doc.intent.requestedOrchestratorModel = plannerModel ?? 'auto';
|
|
339
|
+
doc.orchestration.requestedModel = plannerModel;
|
|
340
|
+
}
|
|
341
|
+
if (workerPool != null) {
|
|
342
|
+
doc.intent.requestedWorkerPool = workers ?? 'auto';
|
|
343
|
+
doc.orchestration.workerPool = workers;
|
|
344
|
+
}
|
|
345
|
+
if (workerModel != null) {
|
|
346
|
+
doc.intent.requestedWorkerModel = workerModelLock ?? 'auto';
|
|
347
|
+
doc.orchestration.workerModel = workerModelLock;
|
|
348
|
+
}
|
|
349
|
+
for (const phase of doc.phases ?? []) {
|
|
350
|
+
for (const step of phase.steps ?? []) {
|
|
351
|
+
if (step.type === 'decide') {
|
|
352
|
+
step.actionDefaults ??= {};
|
|
353
|
+
if (orchestratorModel != null) {
|
|
354
|
+
if (plannerModel) step.model = plannerModel;
|
|
355
|
+
else delete step.model;
|
|
356
|
+
}
|
|
357
|
+
if (workerPool != null) {
|
|
358
|
+
if (workers) step.actionDefaults.pool = workers;
|
|
359
|
+
else delete step.actionDefaults.pool;
|
|
360
|
+
}
|
|
361
|
+
if (workerModel != null) {
|
|
362
|
+
if (workerModelLock) step.actionDefaults.model = workerModelLock;
|
|
363
|
+
else delete step.actionDefaults.model;
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (workerPool != null) {
|
|
368
|
+
if (workers) step.pool = workers;
|
|
369
|
+
else delete step.pool;
|
|
370
|
+
}
|
|
371
|
+
if (workerModel != null) {
|
|
372
|
+
if (workerModelLock) step.model = workerModelLock;
|
|
373
|
+
else delete step.model;
|
|
374
|
+
}
|
|
313
375
|
}
|
|
314
376
|
}
|
|
315
377
|
return doc;
|
|
@@ -324,6 +386,10 @@ async function wfGoal(opts) {
|
|
|
324
386
|
console.error('✗ --watch is only valid for a new human-readable independent launch; do not combine it with --detach, --foreground, --json, --resume, or --request');
|
|
325
387
|
return 2;
|
|
326
388
|
}
|
|
389
|
+
if (opts.orchestrator && opts['strict-orchestrator']) {
|
|
390
|
+
console.error('✗ --orchestrator and --strict-orchestrator are mutually exclusive');
|
|
391
|
+
return 2;
|
|
392
|
+
}
|
|
327
393
|
const { names, pools } = await livePoolNames();
|
|
328
394
|
let doc;
|
|
329
395
|
let resumeRunId = null;
|
|
@@ -345,7 +411,17 @@ async function wfGoal(opts) {
|
|
|
345
411
|
console.error(`✗ cannot load durable workflow for ${resumeRunId}: ${err.message}`);
|
|
346
412
|
return 1;
|
|
347
413
|
}
|
|
348
|
-
|
|
414
|
+
try {
|
|
415
|
+
applyResumeOrchestratorOverride(doc, opts.orchestrator, opts['strict-orchestrator']);
|
|
416
|
+
applyResumeModelOverrides(doc, {
|
|
417
|
+
orchestratorModel: opts['orchestrator-model'],
|
|
418
|
+
workerPool: opts['worker-pool'],
|
|
419
|
+
workerModel: opts['worker-model'],
|
|
420
|
+
});
|
|
421
|
+
} catch (err) {
|
|
422
|
+
console.error(`✗ invalid goal options: ${err.message}`);
|
|
423
|
+
return 2;
|
|
424
|
+
}
|
|
349
425
|
} else if (opts.request) {
|
|
350
426
|
try {
|
|
351
427
|
const request = JSON.parse(readFileSync(resolve(opts.request), 'utf8'));
|
|
@@ -364,13 +440,24 @@ async function wfGoal(opts) {
|
|
|
364
440
|
console.error(goalUsage());
|
|
365
441
|
return 2;
|
|
366
442
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
443
|
+
const requestedOrchestrator = opts['strict-orchestrator'] ?? opts.orchestrator;
|
|
444
|
+
const orchestrator = requestedOrchestrator && requestedOrchestrator !== 'auto'
|
|
445
|
+
? requestedOrchestrator : null;
|
|
446
|
+
const workerPool = opts['worker-pool'] && opts['worker-pool'] !== 'auto'
|
|
447
|
+
? opts['worker-pool'] : null;
|
|
448
|
+
const workerModel = opts['worker-model'] && opts['worker-model'] !== 'auto'
|
|
449
|
+
? opts['worker-model'] : null;
|
|
450
|
+
const orchestratorModel = opts['orchestrator-model'] && opts['orchestrator-model'] !== 'auto'
|
|
451
|
+
? opts['orchestrator-model'] : null;
|
|
369
452
|
try {
|
|
370
453
|
doc = buildGoalWorkflow({
|
|
371
454
|
goal,
|
|
372
455
|
cwd: opts.cwd ?? process.cwd(),
|
|
373
456
|
orchestrator,
|
|
457
|
+
strictOrchestrator: Boolean(opts['strict-orchestrator']),
|
|
458
|
+
orchestratorModel,
|
|
459
|
+
workerPool,
|
|
460
|
+
workerModel,
|
|
374
461
|
settings: goalSettings(opts),
|
|
375
462
|
scout: !opts.noScout,
|
|
376
463
|
worktreeIsolation: loadState(BULLSWARM_DIR()).config?.worktreeIsolation ?? 'agent-decides',
|
|
@@ -616,7 +703,8 @@ async function wfInspect(opts) {
|
|
|
616
703
|
function parseFlags(argv) {
|
|
617
704
|
const out = { inputs: {}, rest: [] };
|
|
618
705
|
const valueFlags = new Set([
|
|
619
|
-
'resume', 'after', 'cwd', 'orchestrator', '
|
|
706
|
+
'resume', 'after', 'cwd', 'orchestrator', 'strict-orchestrator', 'orchestrator-model',
|
|
707
|
+
'worker-pool', 'worker-model', 'request', 'run-id',
|
|
620
708
|
'max-agents', 'max-expansion-rounds', 'max-actions',
|
|
621
709
|
'max-items-per-expansion', 'max-workflow-seconds', 'concurrency',
|
|
622
710
|
'retry-attempts', 'interval', 'heartbeat', 'message',
|