bullswarm 0.10.3 → 0.10.4
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/AGENTS.md +1 -1
- package/CHANGELOG.md +50 -0
- package/README.md +79 -15
- package/bin/bullswarm.js +2 -2
- package/connectors/claude-code.json +4 -0
- package/connectors/grok.json +4 -0
- package/package.json +1 -1
- package/skill/SKILL.md +50 -7
- package/src/cli.js +14 -3
- package/src/help.js +39 -6
- package/src/lib/agent-events.js +19 -2
- package/src/lib/config.js +4 -1
- package/src/lib/route.js +2 -0
- package/src/lib/strategy.js +58 -5
- package/src/lib/verify.js +5 -5
- package/src/lib/watch.js +35 -8
- package/src/setup.js +1 -1
- package/src/strategy-cli.js +23 -0
- package/src/workflow/cli.js +67 -15
- package/src/workflow/dashboard.js +695 -15
- package/src/workflow/decision.js +7 -0
- package/src/workflow/draft-cli.js +2 -1
- package/src/workflow/goal.js +9 -6
- package/src/workflow/result.js +162 -0
- package/src/workflow/runner.js +127 -28
- package/src/workflow/runs-cli.js +56 -0
- package/src/workflow/runtime.js +78 -11
- package/src/workflow/status.js +17 -0
- package/src/workflow/steering.js +2 -1
- package/src/workflow/tui.js +5 -1
- package/src/workflow/watch-cli.js +4 -7
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ content. Published as `bullswarm` on npm.
|
|
|
37
37
|
## Development
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
npm test #
|
|
40
|
+
npm test # 256 tests, no network needed (meters read from cache)
|
|
41
41
|
node bin/bullswarm.js doctor --json # readiness report
|
|
42
42
|
node bin/bullswarm.js workflow list # discover workflows
|
|
43
43
|
node bin/bullswarm.js workflow runs # ongoing workflow instances
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.4 — durable interactive workflow viewer
|
|
4
|
+
|
|
5
|
+
- Added `workflow runs result <id> [--json]`, a stable
|
|
6
|
+
`bullswarm.workflow.result.v1` handoff for parent agents. It selects the final
|
|
7
|
+
successful delivery rather than the last orchestrator response, pairs it with
|
|
8
|
+
its dependent verification verdict, and reports progress, step logs, token
|
|
9
|
+
usage, and honest complete-or-partial tool-call counts.
|
|
10
|
+
- Goal launches now return and print a four-part operating handoff for agentic
|
|
11
|
+
inspection, low-noise watching, the human TUI, and terminal result retrieval.
|
|
12
|
+
- Plain `workflow goal` now launches independently, prints that handoff, and
|
|
13
|
+
returns. The new explicit `--watch` flag follows low-noise progress until the
|
|
14
|
+
terminal state; the human TUI is opened from the printed command.
|
|
15
|
+
- `--foreground` retains terminal-owned execution and `--detach` remains an
|
|
16
|
+
explicit backward-compatible spelling of the new default.
|
|
17
|
+
- Rebuilt interactive workflow inspection as a responsive Phase → Agent →
|
|
18
|
+
Agent-activity browser: desktop uses two contextual panes while mobile and
|
|
19
|
+
narrow SSH terminals use one full-width level. It includes arrow/Enter/Esc
|
|
20
|
+
navigation, numbered semantic actions, active-agent following, scrolling,
|
|
21
|
+
completed-agent outcomes, total semantic-action counts, terminal agent
|
|
22
|
+
progress, resize handling, safe detach, and confirmed stopping.
|
|
23
|
+
- Made the autonomous orchestrator a compact selectable control-plane panel
|
|
24
|
+
stacked above the phase tree. Arrow/Enter or `o` opens its
|
|
25
|
+
durable session, checkpoint decisions, semantic
|
|
26
|
+
activity, usage, prompt, outcomes, and artifacts without counting planner
|
|
27
|
+
turns as phase workers.
|
|
28
|
+
- Standardized workflow TUI state marks across orchestrator, phases, agents,
|
|
29
|
+
and semantic activity: `○` pending, animated Braille spinner active, `⧖`
|
|
30
|
+
waiting, `✓` complete, and `✗` failed or interrupted.
|
|
31
|
+
- Made `maxExpansionRounds` an advisory convergence target instead of a hard
|
|
32
|
+
failure boundary. Near the target the orchestrator is told to consolidate
|
|
33
|
+
existing evidence and avoid marginal expansion; essential bounded work may
|
|
34
|
+
exceed it and the overage is recorded.
|
|
35
|
+
- Added truthful qualified terminal outcomes: planner `stop` now yields
|
|
36
|
+
`completed_with_concerns` with a ready best-effort delivery when useful work
|
|
37
|
+
exists, or `blocked` when it does not. Result envelopes expose `verified`,
|
|
38
|
+
the stopping reason, and unresolved concerns without relabeling failed
|
|
39
|
+
verification as success.
|
|
40
|
+
- Made phase and agent status derive from semantic output verdicts, so a
|
|
41
|
+
verifier process that successfully returns `ok:false` is displayed as a
|
|
42
|
+
failed verification rather than a completed check.
|
|
43
|
+
- Added persisted `strategy exclude-model` / `include-model` policy. Excluded
|
|
44
|
+
models are removed from strategy recommendations and dispatch assignments;
|
|
45
|
+
connectors pin an allowed same-tier fallback or become ineligible when they
|
|
46
|
+
cannot guarantee the exclusion.
|
|
47
|
+
- Let the CLI process drain stdout before exiting, preventing large
|
|
48
|
+
`workflow tui --json` snapshots from being truncated around the platform
|
|
49
|
+
pipe-buffer boundary.
|
|
50
|
+
- Expanded `workflow --help` into an operational map for building, observing,
|
|
51
|
+
controlling, and auditing workflows.
|
|
52
|
+
|
|
3
53
|
## 0.10.3 — contextual help everywhere
|
|
4
54
|
|
|
5
55
|
- Added side-effect-free `-h` / `--help` handling for the top-level CLI and
|
package/README.md
CHANGED
|
@@ -14,6 +14,15 @@ bullswarm workflow run --help
|
|
|
14
14
|
bullswarm workflow draft step add --help
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
`workflow goal` launches a durable background runner, prints operating commands,
|
|
18
|
+
and returns by default. Add `--watch` to immediately follow low-noise progress
|
|
19
|
+
until terminal, or `--foreground` to keep execution owned by the initiating
|
|
20
|
+
terminal. Open the full-screen Phase → Agent → Activity viewer at any time with
|
|
21
|
+
`bullswarm workflow tui <shortId>`; use Up/Down, Enter, Esc, and `q` to detach.
|
|
22
|
+
Wide terminals use two
|
|
23
|
+
contextual panes (phases + agents, then agents + activity); narrow and mobile
|
|
24
|
+
terminals give each level the full screen automatically.
|
|
25
|
+
|
|
17
26
|
## The doctrine (non-negotiable)
|
|
18
27
|
|
|
19
28
|
1. **Judge by CONTENT, not exit code.** Every delegate CLI can exit 0 while
|
|
@@ -88,6 +97,7 @@ bullswarm strategy auto status
|
|
|
88
97
|
bullswarm strategy set-subscription command-code \
|
|
89
98
|
--plan GOAT --monthly-usd 10 --included-usd 70 --quota-window monthly
|
|
90
99
|
bullswarm strategy assign high --pool claude-code --model claude-opus-4-6
|
|
100
|
+
bullswarm strategy exclude-model claude-fable-5
|
|
91
101
|
bullswarm run --effort high --lane analyze --task-file /tmp/task.md --json
|
|
92
102
|
```
|
|
93
103
|
|
|
@@ -103,6 +113,12 @@ prices, and benchmarks stay `null` rather than being guessed. An assignment is
|
|
|
103
113
|
only a preference: quarantine, exhaustion, burst gates, and capability checks
|
|
104
114
|
still win.
|
|
105
115
|
|
|
116
|
+
Model exclusions are hard routing policy. An excluded model is removed from
|
|
117
|
+
recommendations and assignments, and Bullswarm pins a same-tier allowed model
|
|
118
|
+
through the connector-owned model flag whenever the provider default could be
|
|
119
|
+
excluded. A pool that cannot guarantee the exclusion is ineligible for that
|
|
120
|
+
dispatch. Reverse the policy with `bullswarm strategy include-model <model>`.
|
|
121
|
+
|
|
106
122
|
Every run and workflow attempt reports its selected agent/model and estimated
|
|
107
123
|
usage. When a delegate does not expose counters, Bullswarm labels its UTF-8
|
|
108
124
|
byte/4 token estimate. The breakdown separates standard read, cache read,
|
|
@@ -116,26 +132,33 @@ phase/step/attempt tree.
|
|
|
116
132
|
For normal multi-step work, give Bullswarm the goal—not a JSON graph:
|
|
117
133
|
|
|
118
134
|
```bash
|
|
119
|
-
#
|
|
135
|
+
# Default: starts independently, prints observation/result commands, and returns.
|
|
120
136
|
bullswarm workflow goal \
|
|
121
137
|
"Fix the failing tests with the smallest correct change and verify them" \
|
|
122
138
|
--cwd ~/some-repo
|
|
123
139
|
|
|
124
|
-
#
|
|
140
|
+
# Follow low-noise semantic progress immediately after launch.
|
|
125
141
|
bullswarm workflow goal \
|
|
126
142
|
"Audit and repair the parser, then run its acceptance tests" \
|
|
127
|
-
--cwd ~/some-repo --
|
|
143
|
+
--cwd ~/some-repo --watch
|
|
128
144
|
```
|
|
129
145
|
|
|
130
146
|
Bullswarm first honors an approved high-tier provider/model assignment when it
|
|
131
147
|
remains eligible, otherwise it selects an eligible `workflow-planning`
|
|
132
|
-
orchestrator by live quota surplus. The orchestrator
|
|
148
|
+
orchestrator by live quota surplus. The orchestrator is one durable control-plane
|
|
149
|
+
conversation: Grok and Claude resume the same provider session at later
|
|
150
|
+
checkpoints, while Bullswarm keeps each turn separately auditable for routing,
|
|
151
|
+
usage, and recovery. It observes durable evidence, proposes bounded actions,
|
|
133
152
|
and decides when another expansion or verification is necessary. Bullswarm
|
|
134
153
|
validates the proposal, owns agent/process selection, routes workers, and calls
|
|
135
154
|
the orchestrator again until completion, cancellation, failure, approval, or a
|
|
136
155
|
hard graph-growth safeguard. No initial phases, prompts, JSON schema, or agent choice are
|
|
137
156
|
required from the user.
|
|
138
157
|
|
|
158
|
+
Planner actions carry forward-only kebab-case phase names. A finished phase is
|
|
159
|
+
sealed: later planner turns must create a new phase for repair or verification,
|
|
160
|
+
so the TUI cannot jump backward or append work beneath a completed phase.
|
|
161
|
+
|
|
139
162
|
The detached response includes a short ID and exact observation commands:
|
|
140
163
|
|
|
141
164
|
```bash
|
|
@@ -155,8 +178,9 @@ bullswarm workflow goal --resume <shortId> --json
|
|
|
155
178
|
|
|
156
179
|
`--orchestrator <pool>` exists for controlled testing; ordinary use should
|
|
157
180
|
leave selection on `auto`. `--max-agents` and `--max-workflow-seconds` are
|
|
158
|
-
advisory planning targets;
|
|
159
|
-
|
|
181
|
+
advisory planning targets; `--max-expansion-rounds` is also an advisory
|
|
182
|
+
convergence target. Hard structural safeguards are adjusted with
|
|
183
|
+
`--max-actions` and `--max-items-per-expansion`.
|
|
160
184
|
Interactive setup also records a worktree-isolation
|
|
161
185
|
preference (`agent-decides`, `off`, or `required`); Bullswarm communicates that
|
|
162
186
|
policy to the orchestrator without imposing repository topology itself.
|
|
@@ -205,6 +229,7 @@ bullswarm workflow runs --historical --since yesterday --until today
|
|
|
205
229
|
bullswarm workflow runs --all --from 2026-08-20 --to 2026-08-27
|
|
206
230
|
bullswarm workflow runs --limit 20 # cap the result count
|
|
207
231
|
bullswarm workflow runs show <shortId> # state + report + summary
|
|
232
|
+
bullswarm workflow runs result <shortId> --json # stable result for the calling agent
|
|
208
233
|
bullswarm runs show <shortId> # top-level shorthand
|
|
209
234
|
bullswarm workflow runs delete <shortId> --yes # remove the run dir
|
|
210
235
|
|
|
@@ -217,6 +242,19 @@ Run-history time filters always compare when the workflow was initiated
|
|
|
217
242
|
exclusive; `--started-after`/`--from` and `--started-before`/`--to` are aliases.
|
|
218
243
|
Values accept ISO timestamps, local `YYYY-MM-DD` dates, `today`, `yesterday`,
|
|
219
244
|
`tomorrow`, `now`, or relative durations such as `30m`, `24h`, `7d`, and `2w`.
|
|
245
|
+
|
|
246
|
+
After a workflow reaches a terminal state, agents should consume
|
|
247
|
+
`workflow runs result <id> --json` instead of probing `state.json`, task files,
|
|
248
|
+
or provider-specific output. The versioned `bullswarm.workflow.result.v1`
|
|
249
|
+
envelope identifies the final delivery artifact and its matching verification
|
|
250
|
+
verdict, and includes progress, step logs, tokens, and an explicitly
|
|
251
|
+
complete-or-partial tool-call total. `runs show` remains the low-level debugging
|
|
252
|
+
surface.
|
|
253
|
+
Goal launch output includes an `instructions` handoff with four named paths:
|
|
254
|
+
`agentInspect` for a machine-readable snapshot, `watch` for low-noise progress,
|
|
255
|
+
`humanTui` for the interactive browser, and `result` for the terminal delivery.
|
|
256
|
+
Use `--watch` when the initiating terminal should immediately follow progress;
|
|
257
|
+
otherwise the command returns after printing this handoff.
|
|
220
258
|
Time filters preserve the existing scope, so use `--all` or `--historical` when
|
|
221
259
|
auditing completed runs.
|
|
222
260
|
|
|
@@ -234,9 +272,20 @@ bullswarm workflow watch <shortId> --jsonl # automation-friendly stream
|
|
|
234
272
|
bullswarm workflow watch <shortId> --once # one current/terminal snapshot
|
|
235
273
|
```
|
|
236
274
|
|
|
237
|
-
`workflow tui` is the interactive, Claude-style `/workflows` view.
|
|
238
|
-
|
|
239
|
-
|
|
275
|
+
`workflow tui` is the interactive, Claude-style `/workflows` view. For an
|
|
276
|
+
autonomous goal its left navigation stacks a compact Orchestrator panel above
|
|
277
|
+
the Phases panel; internal planner turns never appear as workers or phases.
|
|
278
|
+
Select Orchestration and press Enter, or press `o`
|
|
279
|
+
anywhere, to inspect the logical orchestration conversation: provider session,
|
|
280
|
+
checkpoint turns and decisions, recent semantic actions, usage, prompt, and
|
|
281
|
+
artifacts. Status marks are consistent throughout the tree: `○` not started,
|
|
282
|
+
an animated Braille spinner for active work, `⧖` waiting, `✓` finished, and
|
|
283
|
+
`✗` failed or interrupted. The non-emoji `⧖` avoids the inconsistent cell
|
|
284
|
+
width of `⌛` across terminal fonts. It watches ongoing runs from disk and supports `j`/`k` or arrow-key selection, Enter for
|
|
285
|
+
details, Esc to go back, `c` to request a confirmed cooperative stop, `r` to
|
|
286
|
+
refresh, and `q` to detach. Its responsive drill-down fits both desktop and
|
|
287
|
+
mobile SSH terminals without squeezing phase, agent, and activity into three
|
|
288
|
+
narrow columns.
|
|
240
289
|
|
|
241
290
|
```bash
|
|
242
291
|
bullswarm workflow tui
|
|
@@ -281,12 +330,18 @@ declaratively map provider events into a common semantic action record:
|
|
|
281
330
|
{"id":"provider-action-id","at":"...","kind":"shell_command|read_file|edit|response","status":"running|completed|failed","summary":"safe scalar preview"}
|
|
282
331
|
```
|
|
283
332
|
|
|
284
|
-
The live workflow pane retains the latest three logical actions per agent.
|
|
333
|
+
The live workflow pane retains and numbers the latest three logical actions per agent.
|
|
285
334
|
Repeated updates for the same tool call replace its status, and streaming text
|
|
286
335
|
chunks coalesce into one response action. Heartbeats, token/thought deltas,
|
|
287
336
|
usage messages, hooks, and unparsed output remain liveness evidence but do not
|
|
288
|
-
occupy the action pane.
|
|
337
|
+
occupy the action pane. The viewer tracks the total logical-action count so it
|
|
338
|
+
can display `last 3 of N`, and completed-agent detail includes a scrollable
|
|
339
|
+
Outcome read from the durable output artifact. Connector-specific flags, paths, and mappings live in
|
|
289
340
|
`connectors/*.json` under `eventStream`; core contains no provider event names.
|
|
341
|
+
Raw structured stdout is treated as an agent transcript, not a provider error
|
|
342
|
+
channel, so reading source text such as an auth-signature matcher cannot falsely
|
|
343
|
+
quarantine Grok or Command Code. Error-shaped semantic results and stderr
|
|
344
|
+
diagnostics still trigger the auth/quota guard.
|
|
290
345
|
|
|
291
346
|
After ten minutes without transport, parsed-event, or semantic-action evidence,
|
|
292
347
|
an active child is labeled `suspected_stalled`. This is an inspection signal,
|
|
@@ -335,13 +390,22 @@ limits:
|
|
|
335
390
|
}
|
|
336
391
|
```
|
|
337
392
|
|
|
338
|
-
`maxAgents` and `
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
393
|
+
`maxAgents`, `maxWorkflowSeconds`, and `maxExpansionRounds` are advisory inputs
|
|
394
|
+
to the orchestrator. Approaching them strongly biases the planner toward
|
|
395
|
+
consolidating existing artifacts and returning the best useful outcome;
|
|
396
|
+
crossing them is recorded but never stops a worker, skips verification, or
|
|
397
|
+
fails a run. `maxActions` and `maxItemsPerExpansion` remain hard structural
|
|
398
|
+
safeguards. Reaching one returns a qualified outcome when useful work exists,
|
|
399
|
+
rather than discarding the run as a blanket failure. Delegates have no
|
|
342
400
|
implicit wall-clock timeout; set a step's `timeoutSec` (or direct-run
|
|
343
401
|
`--timeout`) only when an operator explicitly wants a hard termination timer.
|
|
344
402
|
|
|
403
|
+
An autonomous `complete` remains strictly verified. A planner `stop` produces
|
|
404
|
+
`completed_with_concerns` when a useful delivery exists, including unresolved
|
|
405
|
+
verification concerns and the stopping reason; it produces `blocked` only
|
|
406
|
+
when no useful delivery exists. `workflow runs result` treats the qualified
|
|
407
|
+
delivery as ready while reporting `verified:false`.
|
|
408
|
+
|
|
345
409
|
The planner returns versioned JSON. It may propose `needs_more_work` with
|
|
346
410
|
bounded `run`, inline-`fanout`, or `verify` actions. The deterministic runtime
|
|
347
411
|
validates IDs, dependencies, operation types, capabilities, and budgets before
|
package/bin/bullswarm.js
CHANGED
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
"capabilities": ["strong-analysis", "code-reading", "file-editing", "workflow-planning"],
|
|
54
54
|
"knownModels": ["claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5"],
|
|
55
55
|
"modelSelection": { "flag": "--model", "mode": "replace-or-append" },
|
|
56
|
+
"conversation": {
|
|
57
|
+
"newArgs": ["--session-id", "{sessionId}"],
|
|
58
|
+
"resumeArgs": ["--resume", "{sessionId}"]
|
|
59
|
+
},
|
|
56
60
|
"modelProfiles": [
|
|
57
61
|
{ "match": "^claude-fable-5$", "tier": "high", "qualityRank": 6, "pricing": { "inputUsdPerMillion": 10, "cacheReadUsdPerMillion": 1, "outputUsdPerMillion": 50 }, "pricingSource": "https://platform.claude.com/docs/en/about-claude/pricing", "pricingUpdatedAt": "2026-08-27" },
|
|
58
62
|
{ "match": "^claude-opus-(5|4-[5-8])$", "tier": "high", "qualityRank": 5, "pricing": { "inputUsdPerMillion": 5, "cacheReadUsdPerMillion": 0.5, "outputUsdPerMillion": 25 }, "pricingSource": "https://platform.claude.com/docs/en/about-claude/pricing", "pricingUpdatedAt": "2026-08-27" },
|
package/connectors/grok.json
CHANGED
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"knownModels": ["grok-4.6", "grok-4.5"],
|
|
49
49
|
"model": "grok-4.6",
|
|
50
50
|
"modelSelection": { "flag": "--model", "mode": "replace-or-append" },
|
|
51
|
+
"conversation": {
|
|
52
|
+
"newArgs": ["--session-id", "{sessionId}"],
|
|
53
|
+
"resumeArgs": ["--resume", "{sessionId}"]
|
|
54
|
+
},
|
|
51
55
|
"modelProfiles": [
|
|
52
56
|
{ "match": "^grok-4\\.6$", "tier": "high", "qualityRank": 5, "pricing": { "inputUsdPerMillion": 2, "cacheReadUsdPerMillion": 0.5, "outputUsdPerMillion": 6 }, "pricingSource": "https://docs.x.ai/developers/models/grok-4.6", "pricingUpdatedAt": "2026-08-27" },
|
|
53
57
|
{ "match": "^grok-4\\.5$", "tier": "medium", "qualityRank": 4 }
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -86,16 +86,26 @@ Prefer this for ordinary goal-driven work. The caller supplies intent only:
|
|
|
86
86
|
```bash
|
|
87
87
|
bullswarm workflow goal \
|
|
88
88
|
"Fix the failing parser tests with the smallest correct change and verify them" \
|
|
89
|
-
--cwd=<abs/path/to/repo> --
|
|
89
|
+
--cwd=<abs/path/to/repo> --json
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
The returned JSON contains `runId`, `shortId`, logs, and exact observation
|
|
93
|
-
commands.
|
|
93
|
+
commands. Its `instructions` object separates `agentInspect`, low-noise `watch`,
|
|
94
|
+
interactive `humanTui`, and terminal `result` retrieval. Bullswarm chooses a capable orchestrator by quota surplus, supplies
|
|
94
95
|
the internal planning contract, validates every proposed graph expansion,
|
|
95
96
|
routes workers independently, requires verification evidence, and replans until
|
|
96
97
|
a truthful terminal state. The initiating agent does not create phases, action
|
|
97
98
|
IDs, dependency JSON, planner prompts, or pool assignments.
|
|
98
99
|
|
|
100
|
+
The autonomous orchestrator is one durable control-plane conversation, separate
|
|
101
|
+
from execution phases. Connectors that declare conversation continuation (Grok
|
|
102
|
+
and Claude Code) resume the provider session at later evidence checkpoints;
|
|
103
|
+
individual turns remain visible in JSON audit state for spend and recovery, but
|
|
104
|
+
the human TUI displays one orchestrator thread rather than several agents.
|
|
105
|
+
Planner-created actions also have forward-only named phases. Once a phase has
|
|
106
|
+
executed, a later turn must use a new phase name; Bullswarm rejects attempts to
|
|
107
|
+
append new work beneath a completed phase.
|
|
108
|
+
|
|
99
109
|
Observe from any other shell or agent:
|
|
100
110
|
|
|
101
111
|
```bash
|
|
@@ -106,6 +116,13 @@ bullswarm workflow events --json <shortId> --after 0
|
|
|
106
116
|
bullswarm workflow action show --json <shortId> <actionId>
|
|
107
117
|
```
|
|
108
118
|
|
|
119
|
+
`workflow goal` starts the durable runner in the background, prints operating
|
|
120
|
+
instructions, and returns by default. Add `--watch` to immediately follow
|
|
121
|
+
low-noise progress until terminal. Open the full-screen Phase → Agent →
|
|
122
|
+
Agent-steps viewer with the printed `humanTui` command; Up/Down, Enter, Esc, and
|
|
123
|
+
`q` navigate or detach while work continues. `--foreground` explicitly restores
|
|
124
|
+
terminal-owned execution.
|
|
125
|
+
|
|
109
126
|
The detached runner does not depend on the initiating CLI remaining alive.
|
|
110
127
|
Resume a process-interrupted run from its persisted definition with
|
|
111
128
|
`bullswarm workflow goal --resume <shortId> --json`. Leave orchestrator
|
|
@@ -183,6 +200,7 @@ bullswarm workflow runs --name <workflow> # filter by workflow name
|
|
|
183
200
|
bullswarm workflow runs --all --since 7d # initiated in the last 7 days
|
|
184
201
|
bullswarm workflow runs --historical --since yesterday --until today
|
|
185
202
|
bullswarm workflow runs show <shortId> # state + report + summary
|
|
203
|
+
bullswarm workflow runs result <shortId> --json # stable delivery for the caller
|
|
186
204
|
bullswarm workflow runs delete <shortId> --yes
|
|
187
205
|
```
|
|
188
206
|
|
|
@@ -193,6 +211,12 @@ ISO timestamps, local dates, today/yesterday/tomorrow/now, or durations such as
|
|
|
193
211
|
`7d`. Add `--all` or `--historical`; time filters do not silently change the
|
|
194
212
|
normal ongoing-only scope.
|
|
195
213
|
|
|
214
|
+
When the run is terminal, use `workflow runs result <id> --json` as the
|
|
215
|
+
handoff contract. Its versioned result envelope points to the selected delivery
|
|
216
|
+
artifact, the dependent verification verdict, progress, and usage. Do not guess
|
|
217
|
+
the output schema by scraping task files or assume the last provider response is
|
|
218
|
+
the deliverable; `runs show` is for low-level debugging.
|
|
219
|
+
|
|
196
220
|
Before authoring or choosing a workflow, agents can inspect the live execution
|
|
197
221
|
fabric and the workflow document itself:
|
|
198
222
|
|
|
@@ -219,6 +243,7 @@ bullswarm strategy show --json
|
|
|
219
243
|
bullswarm strategy apply --yes --refresh-hours 24
|
|
220
244
|
bullswarm strategy auto status
|
|
221
245
|
bullswarm strategy assign high --pool <pool> --model <model>
|
|
246
|
+
bullswarm strategy exclude-model <model>
|
|
222
247
|
```
|
|
223
248
|
|
|
224
249
|
Connector-declared discovery, dated benchmark/pricing evidence, live quota,
|
|
@@ -230,6 +255,19 @@ an assignment, but it never bypasses capability, quarantine, exhaustion, or
|
|
|
230
255
|
burst-gate safety. Each attempt records the chosen agent/model and labeled
|
|
231
256
|
token, cost, and normalized-quota estimates in the workflow tree.
|
|
232
257
|
|
|
258
|
+
`exclude-model` is a persisted hard policy, not a prompt hint. Excluded models
|
|
259
|
+
are removed from recommendations and ignored in assignments. When a connector
|
|
260
|
+
supports explicit model selection, Bullswarm pins an allowed model in the same
|
|
261
|
+
effort tier; otherwise that pool is excluded because its implicit default
|
|
262
|
+
cannot be guaranteed. Restore eligibility with `strategy include-model`.
|
|
263
|
+
|
|
264
|
+
In the human TUI, the autonomous orchestrator has a compact selectable panel
|
|
265
|
+
stacked above the phase tree. Select it and press Enter, or press
|
|
266
|
+
`o`, to inspect its durable provider session, checkpoint turns and decisions,
|
|
267
|
+
recent semantic actions, usage, prompt, and artifacts. Esc returns to phases.
|
|
268
|
+
The shared state marks are `○` not started, animated Braille spinner active,
|
|
269
|
+
`⧖` waiting, `✓` finished, and `✗` failed or interrupted.
|
|
270
|
+
|
|
233
271
|
Run state also exposes the versioned plan, action ledger, aggregate usage, every attempt,
|
|
234
272
|
planner decisions and reasons, budgets, `currentPhase`, `currentStep`, and
|
|
235
273
|
`activeAgents` in `workflow tui --json <shortId>`. Each attempt includes its
|
|
@@ -265,11 +303,13 @@ blocks into the same action shape and retains the latest three. Ten minutes
|
|
|
265
303
|
without any transport/event/action evidence becomes `suspected_stalled` with
|
|
266
304
|
`autoTerminate:false`; it is deliberately not treated as proof of death.
|
|
267
305
|
|
|
268
|
-
For adaptive work, declare a `decide` step plus
|
|
269
|
-
|
|
270
|
-
`
|
|
271
|
-
remaining headroom and overage to the orchestrator
|
|
272
|
-
skip required verification.
|
|
306
|
+
For adaptive work, declare a `decide` step plus advisory
|
|
307
|
+
`maxExpansionRounds` and the hard structural safeguards (`maxActions` and
|
|
308
|
+
`maxItemsPerExpansion`). `maxAgents`, `maxWorkflowSeconds`, and
|
|
309
|
+
`maxExpansionRounds` expose remaining headroom and overage to the orchestrator
|
|
310
|
+
but never stop a worker or skip required verification. Near the targets the
|
|
311
|
+
planner is instructed to converge, consolidate existing artifacts, and avoid
|
|
312
|
+
optional work. The loop is durable:
|
|
273
313
|
|
|
274
314
|
```text
|
|
275
315
|
execute -> observe -> decide -> validate proposal -> append -> execute -> observe
|
|
@@ -278,6 +318,9 @@ execute -> observe -> decide -> validate proposal -> append -> execute -> observ
|
|
|
278
318
|
Allowed planner decisions are `proceed`, `complete`, `needs_more_work`,
|
|
279
319
|
`retry`, `escalate`, `wait_for_approval`, and `stop`. Expansion decisions must
|
|
280
320
|
contain bounded actions; malformed or over-budget output executes nothing.
|
|
321
|
+
`complete` still requires successful delivery and verification. `stop` returns
|
|
322
|
+
`completed_with_concerns` with a ready best-effort artifact when useful work
|
|
323
|
+
exists, or `blocked` when it does not; neither hides failed verification.
|
|
281
324
|
Use `workflows/adaptive-code-review.json` as the starting template.
|
|
282
325
|
Planner proposals cannot choose `pool`, `addDir`, or `taskFile`. Those fields
|
|
283
326
|
are runtime-owned. An initiator may constrain them with a decide step's
|
package/src/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { cmdWorkflow } from './workflow/cli.js';
|
|
|
18
18
|
import { cmdStrategy, maybeRefreshStrategy } from './strategy-cli.js';
|
|
19
19
|
import { cmdIntegrate, installIntegration } from './integrate.js';
|
|
20
20
|
import { helpForArgs } from './help.js';
|
|
21
|
+
import { resolveDispatchModel } from './lib/strategy.js';
|
|
21
22
|
|
|
22
23
|
export function getBullswarmDir() {
|
|
23
24
|
const h = process.env.BULLSWARM_HOME?.trim();
|
|
@@ -118,7 +119,15 @@ async function cmdRun(opts) {
|
|
|
118
119
|
// Burst gate (M3): a pool whose 5h window is >=90% used is excluded from
|
|
119
120
|
// dispatch entirely this run — it paces nothing, it's just out of burst room.
|
|
120
121
|
const gated = pools.filter((p) => p.burstGate);
|
|
121
|
-
const
|
|
122
|
+
const ungatedPools = gated.length ? pools.filter((p) => !p.burstGate) : pools;
|
|
123
|
+
const assignment = state.strategy?.assignments?.[effortTier] ?? null;
|
|
124
|
+
const eligiblePools = ungatedPools.map((pool) => ({
|
|
125
|
+
...pool,
|
|
126
|
+
modelPolicy: resolveDispatchModel(pool.connector ?? pool, effortTier, {
|
|
127
|
+
assignment,
|
|
128
|
+
excludedModels: state.strategy?.excludedModels ?? [],
|
|
129
|
+
}),
|
|
130
|
+
})).filter((pool) => pool.modelPolicy.eligible);
|
|
122
131
|
|
|
123
132
|
const route = pickPool(lane, eligiblePools, {
|
|
124
133
|
callerEligible: opts['no-caller'] !== true,
|
|
@@ -146,8 +155,8 @@ async function cmdRun(opts) {
|
|
|
146
155
|
// connector spec lives one level down.
|
|
147
156
|
const poolView = route.pick.connector ?? { name: route.pick.pool };
|
|
148
157
|
const connector = poolView.connector ?? poolView;
|
|
149
|
-
const
|
|
150
|
-
|
|
158
|
+
const selectedModel = poolView.modelPolicy?.model
|
|
159
|
+
?? (assignment?.pool === connector.name ? assignment.model : null);
|
|
151
160
|
const runtimeConnector = {
|
|
152
161
|
...connector,
|
|
153
162
|
subscription: poolView.subscription ?? connector.subscription ?? null,
|
|
@@ -237,6 +246,8 @@ function emit(verdict, opts) {
|
|
|
237
246
|
|
|
238
247
|
function cmdHealth(opts) {
|
|
239
248
|
const state = loadState(getBullswarmDir());
|
|
249
|
+
const released = sweepQuarantines(state, Date.now());
|
|
250
|
+
if (released.length) saveState(getBullswarmDir(), state);
|
|
240
251
|
const runsDir = join(getBullswarmDir(), 'runs');
|
|
241
252
|
const findings = [];
|
|
242
253
|
|
package/src/help.js
CHANGED
|
@@ -44,13 +44,15 @@ const HELP = {
|
|
|
44
44
|
release: { _text: leaf('bullswarm release <patch|minor|major> [--dry-run]') },
|
|
45
45
|
strategy: {
|
|
46
46
|
_text: leaf('bullswarm strategy <command> [options]',
|
|
47
|
-
'Commands: refresh, apply, show, assign, clear-assignment, set-subscription, auto.'),
|
|
47
|
+
'Commands: refresh, apply, show, assign, clear-assignment, exclude-model, include-model, set-subscription, auto.'),
|
|
48
48
|
refresh: { _text: leaf('bullswarm strategy refresh [--json] [--apply --yes] [--refresh-hours <n>]') },
|
|
49
49
|
recommend: { _text: leaf('bullswarm strategy recommend [--json] [--apply --yes]', 'Alias for strategy refresh.') },
|
|
50
50
|
apply: { _text: leaf('bullswarm strategy apply --yes [--refresh-hours <n>]') },
|
|
51
51
|
show: { _text: leaf('bullswarm strategy show [--json]') },
|
|
52
52
|
assign: { _text: leaf('bullswarm strategy assign <high|medium|low> --pool <pool> --model <model>') },
|
|
53
53
|
'clear-assignment': { _text: leaf('bullswarm strategy clear-assignment <high|medium|low>') },
|
|
54
|
+
'exclude-model': { _text: leaf('bullswarm strategy exclude-model <model>', 'Persistently prevent this exact model from orchestration and worker dispatches.') },
|
|
55
|
+
'include-model': { _text: leaf('bullswarm strategy include-model <model>', 'Remove a persisted model exclusion.') },
|
|
54
56
|
'set-subscription': { _text: leaf('bullswarm strategy set-subscription <pool> [--plan <name>] [--monthly-usd <n|unknown>] [--included-usd <n|unknown>] [--quota-window <name>]') },
|
|
55
57
|
auto: {
|
|
56
58
|
_text: leaf('bullswarm strategy auto <status|off> [--yes]'),
|
|
@@ -60,15 +62,44 @@ const HELP = {
|
|
|
60
62
|
},
|
|
61
63
|
workflow: {
|
|
62
64
|
_text: leaf('bullswarm workflow <command> [options]',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
`Create, execute, observe, and audit durable multi-agent workflows.
|
|
66
|
+
|
|
67
|
+
Build and execute:
|
|
68
|
+
goal <goal> autonomously plan, execute, verify, and replan a goal
|
|
69
|
+
run <workflow> run an existing workflow file or saved draft
|
|
70
|
+
draft ... incrementally build a fixed workflow graph
|
|
71
|
+
validate <target> validate without executing
|
|
72
|
+
inspect <target> show the document, semantics, and validation details
|
|
73
|
+
list list available workflow definitions
|
|
74
|
+
|
|
75
|
+
Observe and control:
|
|
76
|
+
runs search ongoing and historical workflow instances
|
|
77
|
+
tui [runId] full-screen phase → agent → step/detail browser
|
|
78
|
+
watch <runId> follow low-noise progress until terminal
|
|
79
|
+
events <runId> replay durable events after a sequence cursor
|
|
80
|
+
action show ... inspect one action and all of its attempts
|
|
81
|
+
steer <runId> queue guidance for the next planner checkpoint
|
|
82
|
+
approval ... approve or reject a waiting decision gate
|
|
83
|
+
|
|
84
|
+
Execution fabric:
|
|
85
|
+
capabilities show pools, lanes, models, meters, and routing constraints
|
|
86
|
+
|
|
87
|
+
Common examples:
|
|
88
|
+
bullswarm workflow goal "Audit this repository" --cwd=.
|
|
89
|
+
bullswarm workflow runs --all --since=7d
|
|
90
|
+
bullswarm workflow tui <shortId>
|
|
91
|
+
bullswarm workflow draft --help
|
|
92
|
+
|
|
93
|
+
Run bullswarm workflow <command> --help for complete command options.`),
|
|
94
|
+
goal: { _text: leaf('bullswarm workflow goal "<goal>" [--cwd <dir>] [--watch|--foreground] [--json] [planning options]',
|
|
95
|
+
'Default: launch independently, print operating instructions, and return. --watch immediately follows low-noise progress until terminal. --foreground keeps execution terminal-owned. Use --resume <shortId|runId> to resume. Planning options include --orchestrator, --max-agents, --max-expansion-rounds, --max-actions, --max-items-per-expansion, --max-workflow-seconds, --concurrency, and --retry-attempts.') },
|
|
66
96
|
run: { _text: leaf('bullswarm workflow run <file-or-name> [--input k=v]... [--resume <shortId|runId>] [--json] [--quiet]') },
|
|
67
97
|
validate: { _text: leaf('bullswarm workflow validate <file-or-name>') },
|
|
68
98
|
list: { _text: leaf('bullswarm workflow list [--json]') },
|
|
69
99
|
capabilities: { _text: leaf('bullswarm workflow capabilities [--json]') },
|
|
70
100
|
inspect: { _text: leaf('bullswarm workflow inspect <file-or-name>') },
|
|
71
|
-
tui: { _text: leaf('bullswarm workflow tui [<runId>] [--json] [--all] [--show <runId>] [--cancel <runId>]'
|
|
101
|
+
tui: { _text: leaf('bullswarm workflow tui [<runId>] [--json] [--all] [--show <runId>] [--cancel <runId>]',
|
|
102
|
+
'Interactive mode opens a full-screen phase → agent → step/detail browser. Up/down selects, Enter drills in, Esc goes back, q detaches without stopping work, and c requests explicit cancellation confirmation.') },
|
|
72
103
|
watch: { _text: leaf('bullswarm workflow watch <runId> [--interval <seconds>] [--heartbeat <seconds>] [--jsonl] [--once]') },
|
|
73
104
|
events: { _text: leaf('bullswarm workflow events <runId> [--after <sequence>] [--json]') },
|
|
74
105
|
steer: { _text: leaf('bullswarm workflow steer <runId> --message <guidance> [--json]') },
|
|
@@ -107,9 +138,11 @@ export function helpForArgs(argv) {
|
|
|
107
138
|
function runsHelp() {
|
|
108
139
|
return {
|
|
109
140
|
_text: leaf('bullswarm workflow runs [list] [--all|--historical] [--name <workflow>] [--since <time>] [--until <time>] [--limit <n>] [--json]',
|
|
110
|
-
'Commands: show <id>, delete <id> --yes. Time filters use the workflow initiation timestamp.'),
|
|
141
|
+
'Commands: show <id>, result <id>, delete <id> --yes. Time filters use the workflow initiation timestamp.'),
|
|
111
142
|
list: { _text: leaf('bullswarm workflow runs list [--all|--historical] [--name <workflow>] [--since <time>] [--until <time>] [--limit <n>] [--json]') },
|
|
112
143
|
show: { _text: leaf('bullswarm workflow runs show <shortId|runId> [--json]') },
|
|
144
|
+
result: { _text: leaf('bullswarm workflow runs result <shortId|runId> [--json]',
|
|
145
|
+
'Return the stable caller-facing delivery, verification verdict, progress, and usage envelope.') },
|
|
113
146
|
delete: { _text: leaf('bullswarm workflow runs delete <shortId|runId> --yes [--force] [--json]') },
|
|
114
147
|
};
|
|
115
148
|
}
|
package/src/lib/agent-events.js
CHANGED
|
@@ -178,9 +178,19 @@ export function createAgentEventDecoder(eventStream, { onEvent, onProgress } = {
|
|
|
178
178
|
|
|
179
179
|
export function recordAgentAction(agent, event, maxActions = 3) {
|
|
180
180
|
agent.lastActions ??= [];
|
|
181
|
+
const priorVisibleCount = agent.lastActions.length;
|
|
182
|
+
if (!agent._actionHistory) {
|
|
183
|
+
Object.defineProperty(agent, '_actionHistory', {
|
|
184
|
+
value: new Map(agent.lastActions.map((action) => [action.id, action])),
|
|
185
|
+
writable: true,
|
|
186
|
+
enumerable: false,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
181
189
|
const existingIndex = agent.lastActions.findIndex((action) => action.id === event.id);
|
|
182
|
-
const
|
|
183
|
-
const
|
|
190
|
+
const visibleExisting = existingIndex >= 0 ? agent.lastActions.splice(existingIndex, 1)[0] : null;
|
|
191
|
+
const rememberedExisting = agent._actionHistory.get(event.id);
|
|
192
|
+
const existing = visibleExisting ?? rememberedExisting ?? {};
|
|
193
|
+
const isNew = !rememberedExisting;
|
|
184
194
|
const statusChanged = existing.status != null && existing.status !== event.status;
|
|
185
195
|
let summary = event.summary ?? existing.summary ?? null;
|
|
186
196
|
let summaryRaw = event.summary ?? existing._summaryRaw ?? existing.summary ?? null;
|
|
@@ -193,8 +203,15 @@ export function recordAgentAction(agent, event, maxActions = 3) {
|
|
|
193
203
|
const eventKind = event.kind === 'agent' && existing.kind ? existing.kind : event.kind;
|
|
194
204
|
const action = { ...existing, ...event, kind: eventKind, summary, summaryMode: undefined };
|
|
195
205
|
Object.defineProperty(action, '_summaryRaw', { value: summaryRaw, writable: true, enumerable: false });
|
|
206
|
+
agent._actionHistory.delete(event.id);
|
|
207
|
+
agent._actionHistory.set(event.id, action);
|
|
208
|
+
while (agent._actionHistory.size > 256) {
|
|
209
|
+
agent._actionHistory.delete(agent._actionHistory.keys().next().value);
|
|
210
|
+
}
|
|
196
211
|
agent.lastActions.push(action);
|
|
197
212
|
agent.lastActions = agent.lastActions.slice(-maxActions);
|
|
213
|
+
if (agent.actionCount == null) agent.actionCount = priorVisibleCount;
|
|
214
|
+
if (isNew) agent.actionCount += 1;
|
|
198
215
|
agent.lastActionAt = event.at;
|
|
199
216
|
agent.lastProgressAt = event.at;
|
|
200
217
|
return { action: agent.lastActions.at(-1), isNew, statusChanged };
|
package/src/lib/config.js
CHANGED
|
@@ -49,7 +49,9 @@ export function buildPools(bullswarmDir, now = Date.now(), readings = {}) {
|
|
|
49
49
|
costRank: conn.costRank ?? 5,
|
|
50
50
|
lanes: conn.lanes,
|
|
51
51
|
capabilities: conn.capabilities ?? [],
|
|
52
|
-
quarantine: ps.quarantine ?? null,
|
|
52
|
+
quarantine: isQuarantined({ quarantine: ps.quarantine ?? null }, now)
|
|
53
|
+
? ps.quarantine
|
|
54
|
+
: null,
|
|
53
55
|
incumbentLane: Object.entries(state.incumbents ?? {})
|
|
54
56
|
.filter(([, v]) => v === name)
|
|
55
57
|
.map(([k]) => k),
|
|
@@ -65,6 +67,7 @@ export function buildPools(bullswarmDir, now = Date.now(), readings = {}) {
|
|
|
65
67
|
...(state.strategy?.subscriptions?.[name] ?? {}),
|
|
66
68
|
},
|
|
67
69
|
strategyAssignments: state.strategy?.assignments ?? {},
|
|
70
|
+
strategyExcludedModels: state.strategy?.excludedModels ?? [],
|
|
68
71
|
};
|
|
69
72
|
pools.push(pool);
|
|
70
73
|
}
|
package/src/lib/route.js
CHANGED
|
@@ -117,6 +117,8 @@ export function pickPool(lane, pools, opts = {}) {
|
|
|
117
117
|
|
|
118
118
|
const candidates = scored.map((e) => ({
|
|
119
119
|
pool: e.pool.name,
|
|
120
|
+
model: e.pool.modelPolicy?.model ?? null,
|
|
121
|
+
modelPolicy: e.pool.modelPolicy?.source ?? null,
|
|
120
122
|
pace: Math.round(e.pace * 10) / 10,
|
|
121
123
|
costRank: e.pool.costRank ?? null,
|
|
122
124
|
}));
|