bullswarm 0.32.0 → 0.33.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.
Files changed (149) hide show
  1. package/CHANGELOG.md +295 -0
  2. package/GOAL.md +1 -1
  3. package/README.md +66 -10
  4. package/data/openrouter-benchmarks.json +7979 -7867
  5. package/data/plan-prices.json +10 -0
  6. package/docs/design/dashboard-prototype.html +914 -0
  7. package/docs/design/owner-review-2026-09-18/budget.jpeg +0 -0
  8. package/docs/design/owner-review-2026-09-18/history-unknown-project.jpeg +0 -0
  9. package/docs/design/owner-review-2026-09-18/home-phone-landscape.jpeg +0 -0
  10. package/docs/design/owner-review-2026-09-18/home-phone-portrait.jpeg +0 -0
  11. package/docs/design/owner-review-2026-09-18/round2-budget-codex-row.png +0 -0
  12. package/docs/design/owner-review-2026-09-18/round2-budget-phone.jpeg +0 -0
  13. package/docs/design/owner-review-2026-09-18/round2-help-tab.png +0 -0
  14. package/docs/design/owner-review-2026-09-18/round2-models-lines.png +0 -0
  15. package/docs/design/owner-review-2026-09-18/round2-runs-duration-cut.png +0 -0
  16. package/docs/design/owner-review-2026-09-18/round2-trends-30d-horizontal-split.png +0 -0
  17. package/docs/design/owner-review-2026-09-18/round2-trends-desktop.png +0 -0
  18. package/docs/design/owner-review-2026-09-18/round2-trends-phone.png +0 -0
  19. package/docs/design/owner-review-2026-09-18/run-page-budget.jpeg +0 -0
  20. package/docs/design/owner-review-2026-09-18/runs-list.jpeg +0 -0
  21. package/docs/design/owner-review-2026-09-18/stats-pools.jpeg +0 -0
  22. package/docs/design/owner-review-2026-09-18/stats-trends-spent.jpeg +0 -0
  23. package/docs/design/prototype-frames/README.md +12 -0
  24. package/docs/design/prototype-frames/budget-120.tagged.txt +40 -0
  25. package/docs/design/prototype-frames/budget-120.txt +40 -0
  26. package/docs/design/prototype-frames/budget-55.tagged.txt +26 -0
  27. package/docs/design/prototype-frames/budget-55.txt +26 -0
  28. package/docs/design/prototype-frames/fleet-120.tagged.txt +40 -0
  29. package/docs/design/prototype-frames/fleet-120.txt +40 -0
  30. package/docs/design/prototype-frames/fleet-55.tagged.txt +26 -0
  31. package/docs/design/prototype-frames/fleet-55.txt +26 -0
  32. package/docs/design/prototype-frames/help-120.tagged.txt +40 -0
  33. package/docs/design/prototype-frames/help-120.txt +40 -0
  34. package/docs/design/prototype-frames/help-55.tagged.txt +26 -0
  35. package/docs/design/prototype-frames/help-55.txt +26 -0
  36. package/docs/design/prototype-frames/history-120.tagged.txt +40 -0
  37. package/docs/design/prototype-frames/history-120.txt +40 -0
  38. package/docs/design/prototype-frames/history-55.tagged.txt +26 -0
  39. package/docs/design/prototype-frames/history-55.txt +26 -0
  40. package/docs/design/prototype-frames/home-120.tagged.txt +40 -0
  41. package/docs/design/prototype-frames/home-120.txt +40 -0
  42. package/docs/design/prototype-frames/home-55.tagged.txt +26 -0
  43. package/docs/design/prototype-frames/home-55.txt +26 -0
  44. package/docs/design/prototype-frames/run-120.tagged.txt +40 -0
  45. package/docs/design/prototype-frames/run-120.txt +40 -0
  46. package/docs/design/prototype-frames/run-55.tagged.txt +26 -0
  47. package/docs/design/prototype-frames/run-55.txt +26 -0
  48. package/docs/design/prototype-frames/stats-models-120.tagged.txt +40 -0
  49. package/docs/design/prototype-frames/stats-models-120.txt +40 -0
  50. package/docs/design/prototype-frames/stats-models-55.tagged.txt +26 -0
  51. package/docs/design/prototype-frames/stats-models-55.txt +26 -0
  52. package/docs/design/prototype-frames/stats-overview-120.tagged.txt +40 -0
  53. package/docs/design/prototype-frames/stats-overview-120.txt +40 -0
  54. package/docs/design/prototype-frames/stats-overview-55.tagged.txt +26 -0
  55. package/docs/design/prototype-frames/stats-overview-55.txt +26 -0
  56. package/docs/design/prototype-frames/stats-pools-120.tagged.txt +40 -0
  57. package/docs/design/prototype-frames/stats-pools-120.txt +40 -0
  58. package/docs/design/prototype-frames/stats-pools-55.tagged.txt +26 -0
  59. package/docs/design/prototype-frames/stats-pools-55.txt +26 -0
  60. package/docs/design/prototype-frames/stats-projects-120.tagged.txt +40 -0
  61. package/docs/design/prototype-frames/stats-projects-120.txt +40 -0
  62. package/docs/design/prototype-frames/stats-projects-55.tagged.txt +26 -0
  63. package/docs/design/prototype-frames/stats-projects-55.txt +26 -0
  64. package/docs/design/prototype-frames/stats-trends-120.tagged.txt +40 -0
  65. package/docs/design/prototype-frames/stats-trends-120.txt +40 -0
  66. package/docs/design/prototype-frames/stats-trends-55.tagged.txt +26 -0
  67. package/docs/design/prototype-frames/stats-trends-55.txt +26 -0
  68. package/docs/design/prototype-frames/step-120.tagged.txt +40 -0
  69. package/docs/design/prototype-frames/step-120.txt +40 -0
  70. package/docs/design/prototype-frames/step-55.tagged.txt +26 -0
  71. package/docs/design/prototype-frames/step-55.txt +26 -0
  72. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.14.40/342/200/257AM.png +0 -0
  73. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.15.23/342/200/257AM.png +0 -0
  74. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.15.31/342/200/257AM.png +0 -0
  75. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.15.39/342/200/257AM.png +0 -0
  76. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.15.45/342/200/257AM.png +0 -0
  77. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.15.50/342/200/257AM.png +0 -0
  78. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.16.02/342/200/257AM.png +0 -0
  79. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.16.07/342/200/257AM.png +0 -0
  80. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.16.18/342/200/257AM.png +0 -0
  81. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.16.23/342/200/257AM.png +0 -0
  82. package/docs/design/prototype-shots/Screenshot 2026-09-17 at 9.16.27/342/200/257AM.png +0 -0
  83. package/docs/guide/observing.md +104 -59
  84. package/docs/guide/routing.md +81 -10
  85. package/docs/plans/dashboard-0.33-fidelity.goal.txt +13 -0
  86. package/docs/plans/dashboard-0.33-fidelity.md +283 -0
  87. package/docs/plans/dashboard-0.33-fidelity.program.json +220 -0
  88. package/docs/plans/dashboard-0.33-fidelity.rev2.json +338 -0
  89. package/docs/plans/dashboard-0.33-review.goal.txt +10 -0
  90. package/docs/plans/dashboard-0.33-review.program.json +117 -0
  91. package/docs/plans/dashboard-0.33.goal.txt +16 -0
  92. package/docs/plans/dashboard-0.33.md +315 -0
  93. package/docs/plans/dashboard-0.33.program.json +293 -0
  94. package/docs/plans/fidelity.goal.txt +1 -0
  95. package/docs/plans/fidelity.program.json +35 -0
  96. package/docs/reference/cli.md +67 -22
  97. package/docs/reference/configuration.md +1 -1
  98. package/docs/reference/providers.md +5 -5
  99. package/docs/workflow-design.md +1 -1
  100. package/mods/bullswarm/README.md +7 -6
  101. package/mods/bullswarm/hooks/pane.tsx +20 -2
  102. package/mods/bullswarm/hooks/pools.ts +2 -0
  103. package/mods/bullswarm/hooks/register.ts +14 -2
  104. package/mods/bullswarm/types/index.d.ts +4 -0
  105. package/package.json +1 -1
  106. package/providers/contrib/README.md +1 -1
  107. package/providers/contrib/command-code/provider.mjs +13 -5
  108. package/providers/contrib/{opencode2 → opencode}/connector.json +2 -1
  109. package/providers/contrib/{opencode2 → opencode}/provider.mjs +3 -3
  110. package/skill/references/providers.md +1 -1
  111. package/src/cli.js +56 -8
  112. package/src/help.js +49 -17
  113. package/src/lib/cli-flags.js +2 -1
  114. package/src/lib/config.js +73 -2
  115. package/src/lib/forecast.js +4 -2
  116. package/src/lib/prices.js +148 -0
  117. package/src/lib/project.js +81 -0
  118. package/src/lib/route.js +173 -46
  119. package/src/lib/spend.js +36 -1
  120. package/src/lib/state.js +417 -3
  121. package/src/lib/strategy.js +2 -2
  122. package/src/lib/usage.js +21 -3
  123. package/src/meters/framework.js +55 -1
  124. package/src/meters/registry.js +172 -14
  125. package/src/provider-kit.js +9 -3
  126. package/src/providers/claude-code/provider.mjs +28 -6
  127. package/src/providers/codex/provider.mjs +9 -4
  128. package/src/providers/grok/provider.mjs +9 -4
  129. package/src/strategy-cli.js +70 -5
  130. package/src/workflow/budget-model.js +567 -0
  131. package/src/workflow/budget-view.js +172 -0
  132. package/src/workflow/cli.js +55 -3
  133. package/src/workflow/dash-kit.js +1012 -0
  134. package/src/workflow/dashboard.js +2632 -423
  135. package/src/workflow/fleet-view.js +270 -0
  136. package/src/workflow/goal.js +54 -0
  137. package/src/workflow/history-view.js +548 -0
  138. package/src/workflow/history.js +295 -0
  139. package/src/workflow/rollup.js +462 -0
  140. package/src/workflow/runs-cli.js +109 -0
  141. package/src/workflow/stats-model.js +962 -0
  142. package/src/workflow/stats-view.js +1675 -0
  143. package/src/workflow/status.js +5 -0
  144. package/src/workflow/usage-view.js +107 -14
  145. package/src/workflow/v2-dispatch.js +183 -26
  146. package/src/workflow/v2-outcome.js +4 -1
  147. package/src/workflow/v2-runtime.js +55 -11
  148. package/src/workflow/v2-state.js +7 -1
  149. package/src/workflow/watch-cli.js +43 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,300 @@
1
1
  # bullswarm changelog
2
2
 
3
+ ## 0.33.0 — the dashboard release
4
+
5
+ - dashboard: the phone `Home` no longer hides data behind its width. The
6
+ `budget · this week` block draws every pool the desktop draws — four today —
7
+ or ends with `+N more` when the rows genuinely do not fit, and its names are
8
+ wide enough that `claude-code:wati` and `claude-code` read as two pools
9
+ rather than one truncated one. The by-pool, by-model and by-project lists
10
+ keep at least their top three rows (`+N more` for the rest) and always paint
11
+ the whole percentage, never `24…`. The summary figures sit one per line, so
12
+ `Busiest project` and `Favourite model` are whole at 55 columns, and the
13
+ spent tile's empty state fits the phone row.
14
+ - dashboard, **keys corrected**: `h` opens **Home** and `?` opens **Help**.
15
+ In 0.32.0 both opened Help, and an earlier 0.33.0 entry wrongly listed them
16
+ as unchanged. `r` `b` `s` `y` `f` keep the pages they opened. The key table,
17
+ the nav tail (`[Top] [End] [?.Help]`), the Help page, the README and the
18
+ observing guide all say the same thing, and tests pin both bindings.
19
+ - dashboard: a finished run the history index has not covered yet is named by
20
+ the project its `goal.json` working directory belongs to — the same
21
+ derivation `bullswarm workflow reindex` uses — instead of reading
22
+ `unknown project`. `unknown project` now means only what it says: no working
23
+ directory was recorded for that run.
24
+ - dashboard: the stacked column charts on `Home` (spent per day) and `Stats`
25
+ `Trends` are drawn to eighth-block precision. The top cell of a bar is one of
26
+ `▁▂▃▄▅▆▇█`, so a `≈$2.93` bar sits below its own `≈$3.00` tick instead of
27
+ overshooting it; the axis top and the row count are chosen together so every
28
+ tick label lands on a row with even spacing; and every non-zero pool slice
29
+ gets at least one eighth-block, smallest on top, so `codex` and
30
+ `command-code` appear on any day they spent anything.
31
+ - dashboard: `Stats` `Pools` drops the shared unlabelled “licence used per day,
32
+ by pool” line chart. Each pool now carries its own seven-day sparkline on its
33
+ own row — one glyph per day of end-of-day usage, `▏` on a day the quota
34
+ window rolled over — under one caption saying that a drop after `▏` is the
35
+ window resetting. A reset is read from the recorded `resets_at` moving, never
36
+ guessed from a falling reading. The row tails (`≈ $3.06 API`, `✓ 100%`) are
37
+ measured before the meter is sized, so nothing truncates at 170 or 200
38
+ columns.
39
+ - dashboard: `Budget` reads in plain words. Each pool is a header
40
+ (`name · weekly plan · resets <day time> (in Nd Nh)`) over four labelled
41
+ rows: `used` (the meter, a whole percent, and `N% of the window gone → on
42
+ track`), `by bullswarm` (this tool's share of the whole plan, with the
43
+ measured minutes and `other tools N%`), `room` (`about N more medium runs
44
+ before the reset`) and `so far` (≈ money and the two biggest workflows).
45
+ Figures are whole numbers. The subscription line appears only where a price
46
+ is declared — the `Subscription rate: —` rows are gone — and
47
+ `workflows 131.24% · rest 0% · workflows exceeds the reported meter` is
48
+ replaced by one footnote in words when the measured share exceeds the meter.
49
+ The page-foot notes are one short block.
50
+ - dashboard: **Runs and History are one page.** `Runs` opens on an `active`
51
+ block — every running or waiting run with its plan strip and per-step bar,
52
+ drawn exactly as `Home`'s running section draws it — and History's
53
+ day-grouped table below it: a day header with its count and ≈ total, then one
54
+ row per run with status glyph, short id, project, goal, duration, ≈ cost and
55
+ time, under the selection cursor, so `Enter` and the digits open a run and
56
+ older days load as the reader scrolls. The `agents` and `run it` blocks moved
57
+ to `Help`. The `History` tab is gone from the tab row — the tabs are Home,
58
+ Runs, Budget, Stats, Fleet — and **`y` jumps to the first day header** of
59
+ Runs. The one-second refresh keeps the table cursor where the reader put it
60
+ (it used to snap back to the in-flight run, so `Enter` opened the wrong
61
+ workflow).
62
+ - dashboard: the second owner-review round, from phone screenshots of the
63
+ 0.33.0 preview. `Stats` `Models` draws stacked columns per day and model
64
+ instead of a three-row line chart that flattened every model under four
65
+ hours onto the zero line (Opus ran 29, 9, 897, 75, 278 and 686 minutes on
66
+ six consecutive days and showed on one). Axis labels mark the bottom of
67
+ their row, so a bar of 15 rises half a row above `14` instead of reading as
68
+ 16. Phone columns are equal width and the charts use the rows the screen
69
+ has. A column never lays two colours side by side in one row: slices too
70
+ small for their own eighth merge into one grey `other` slice, counted in the
71
+ legend. Each pool and model keeps one colour on every page and period. Under
72
+ a column, totals keep one blank cell apart and a long duration falls back to
73
+ whole hours. `Budget`: a pool whose meter never moved during its measured
74
+ runs says `share unknown · meter did not move during 32 measured runs`
75
+ instead of `≈ 0%`; credits join the `used` row (`0 of 70 credits`); a pool
76
+ with nothing left says `no room left before the reset`. The Runs day table
77
+ never cuts a duration (`3h21m`, not `3h2…`). The tab row stays five tabs on
78
+ the Help page. Charts draw from fourteen hues and never give two series in
79
+ one chart the same colour (the 30-day Trends had three purples and the
80
+ Models chart seven models in two colours); slices stack smallest at the
81
+ bottom, biggest on top; the legend wraps to name every series instead of
82
+ hiding the rest behind `+N more`. The mouse now lights the clickable row
83
+ under the pointer in reverse video, the way the Mod pane does (a whole row
84
+ when it is the row's only target, just the button when several share a
85
+ row); the TUI asks the terminal for motion reports and releases them on the
86
+ way out. Only list rows light up (a run in the table, a step in a plan);
87
+ chart columns, tiles, meters and tabs do not, and inside a lit row only the
88
+ words reverse — a step's bar and the plan strip's `──○──` connectors keep
89
+ their colours. Home's breakdown percents sit right-aligned in one column, so `2%`
90
+ lines up under `19%`.
91
+ - dashboard, **no longer laggy**: a CPU profile of the idle TUI showed it busy
92
+ 61% of the time. Every one-second tick re-discovered Claude accounts through
93
+ the macOS keychain (`security`, 100–300 ms, blocking), re-ran the pool-rename
94
+ migration check over the whole state.json, and on Runs re-read all 300 run
95
+ directories. Usage now reloads every 10 s, the keychain read is cached for a
96
+ minute, the migration check is remembered per file fingerprint, the run
97
+ catalogue rebuilds only when an active run changes or 15 s pass, and the
98
+ unknown-project fallback asks `git` once per working directory. Idle busy
99
+ share after: 14%. The painter now rewrites only the rows that changed
100
+ since the last frame (a spinner tick is a few dozen bytes instead of the
101
+ whole 120×40 screen), which is what a phone or a remote terminal feels.
102
+ - dashboard: no page draws an empty track where it has no data. The `Run`
103
+ page's budget block is titled `licence this run used`, shows whole percents
104
+ (`≈ 0.4% of the weekly plan`, as a one-cell `▏` sliver rather than an empty
105
+ bar), writes `free model · no licence meter` for an unmetered pool instead of
106
+ a dotted track and a `—`, and collapses to a single sentence when no pool is
107
+ metered. The plan strip's pool row prints the pool name alone when the cell
108
+ cannot hold pool and model. A paused run's live block says
109
+ `paused · bullswarm workflow resume <id> continues it` instead of `waiting for
110
+ the next dispatch`, and the ETA footnote is
111
+ `ETA —: <step> has no recorded duration yet` rather than
112
+ `N of N remaining steps recorded no expected duration`. The same rule removed
113
+ the dotted `meter unavailable` tracks from `Stats` `Pools`.
114
+
115
+ - dashboard: the visual-fidelity pass now composes `Home` with a four-column
116
+ 7-day breakdown and a budget block, `Run` with a plan strip, per-step bars
117
+ and a `budget` / `live` / `so far` band, `Budget` with seven rows per pool
118
+ plus one consolidated footer, coloured stacked columns on `Stats` `Trends`,
119
+ and fixed columns on `History`, reducing its phone form from two rows per run
120
+ to one at 55 columns.
121
+ - dashboard: the shared palette keeps its four existing meter colours and adds
122
+ seven named roles, including the four-shade heat ramp; `Help` groups the `r`,
123
+ `b`, `s`, `y`, and `f` keys into 35 body rows at 55 columns (from about 51)
124
+ and drops the nonexistent `Budget` sub-tabs.
125
+ - dashboard: the `Run` plan strip closes a fan the way the prototype does —
126
+ each lower branch hangs under the `┬` that opened it, is ruled across to the
127
+ joint, and the last one ends in `┘` under the closing `┬`; it used to draw a
128
+ `┴` with nothing beneath it when the lower branch was the longer name. The
129
+ `Stats` `Models` footnote says `no per-model money` in words instead of
130
+ naming the `apiEquivalentUsd` field.
131
+ - dashboard: the phone layouts now put one item on one row where the page is a
132
+ list — `Stats` `Models`, `Stats` `Projects` (with its sparkline beside the
133
+ name), `Stats` `Pools`, `Fleet` and `History`, plus `Home`'s today tiles and
134
+ per-step bars. `Budget`'s seven-row pool block, `Home`'s `budget · this week`
135
+ pool and the `Runs` list keep their extra lines on purpose.
136
+ - dashboard: `Stats` `Models` draws a worker-minutes-per-day chart whose series
137
+ are coloured to match the model list beneath it, and `Stats` `Projects` draws
138
+ a sparkline beside every project name. At a 200-column frame a project keeps
139
+ name, sparkline and figures on one row, with the sparkline capped at four
140
+ cells per measured day so seven days read as a shape rather than a
141
+ frame-wide run of repeated glyphs.
142
+ - dashboard: `Stats` rows built from the shared `compactRow` now lay their
143
+ fields out on the frame minus their one-cell indent, so the last field no
144
+ longer lost its final character to the frame's own truncation (`p50 5…`
145
+ where `p50 54m` fits).
146
+ - dashboard: every page now composes to a 200-column frame instead of stopping
147
+ at the 120-column composition — measured on the real binary, the widest
148
+ painted row is 200 cells on every page except `Fleet`, which reaches 188.
149
+ - dashboard: `Run` draws a bar for the running step inside the plan strip, using
150
+ an empty indeterminate bar when the step recorded no expected duration, and
151
+ `Step` keeps its task summary short enough that the `output` and `artifacts`
152
+ sections both start inside a 26-row phone frame rather than below it.
153
+ - data: the pass keeps real figures and the `≈`/basis rule. An undeclared
154
+ subscription price, an expected duration, and a per-run licence draw
155
+ deliberately remain blank with their reasons. The months-wide heatmap does
156
+ not move over: `Stats` uses only history that exists and, when there are no
157
+ measured days, renders a blank with `No workflow history yet — the heatmap
158
+ has no measured days.` rather than inventing the prototype's number.
159
+ - docs: `README.md` and the observing guide now describe the shipped
160
+ composition and the same data-honesty rule, and name the three places whose
161
+ phone form deliberately keeps more than one row per item rather than claiming
162
+ a universal one-row layout.
163
+
164
+ - routing: measured pacing rates now charge timed in-flight work at the actual
165
+ `rate × remaining minutes` with no 3-point floor, so an expiring-soon pool
166
+ with a known burn rate is not demoted by a tie-breaker meant for unmeasured
167
+ pools. `config.inflightPenaltyPct` remains the per-agent fallback for pools
168
+ without a measured rate (and for in-flight records with unknown duration).
169
+ - workflow: every attempt record now carries `routeWhy` (the router's reason)
170
+ and `routeCandidates` (each pool's effective surplus, urgency state and
171
+ pacing forecast at pick time); `workflow action show --json` and
172
+ `runs result --json` print them, and older state files still load.
173
+ - dashboard: the full-screen surface now has eight pages — Home, Runs, Run,
174
+ Step, Budget, Stats, Fleet, and Help (History is the day table inside Runs,
175
+ see above). Home answers what happened today and what is active; Runs is the
176
+ active block over the day-grouped table; the integration actions sit on Help; Run
177
+ and Step explain a workflow and one action; Budget shows quota, measured
178
+ worker-time share, labelled money, fit, and biggest workflows; Stats covers
179
+ Overview, Trends, Pools, Models, and Projects; History is the dated timeline;
180
+ Fleet shows lane/provider rungs; Help names the controls. Home, Stats, and
181
+ History read the rollup index instead of parsing every run directory on each
182
+ refresh.
183
+ - workflow: every finished V2 run now records a per-run `rollup.json` and
184
+ appends an idempotent line to `~/.bullswarm/history/runs.jsonl`. On the real
185
+ corpus used for the release, `294` run directories yielded `181` indexed
186
+ finished V2 records; `105` legacy and `8` unfinished directories were left
187
+ out. History spans `15` recorded days, and `94` of the `188` V2 results are
188
+ verified.
189
+ - dashboard, **keys rebound** — three keys changed meaning, one is gone, and
190
+ seven are new. Rebound: `r` opened nothing and refreshed the view in 0.32.0
191
+ and now opens **Runs** (the 1 s timer refreshes, so no key needs to);
192
+ `b` was **move out** and now opens **Budget** (`Esc` and `←` are move out);
193
+ `Tab` was **next workflow** and now cycles the current page's **sub-tabs**
194
+ (`Shift+Tab` cycles workflows, as it did). Removed: the Usage page is
195
+ replaced by **Budget** (`b`) and **Fleet** (`f`), so **`u` is gone**, and
196
+ Usage's `l`/`p` lane/provider grouping keys are now **`Tab`** on Fleet.
197
+ New: `s` Stats, `y` History, `f` Fleet, `p` cycles the period, `Home` top,
198
+ `End` bottom, and `ctrl+s` copies the screen. Moved, not rebound: `i`, `/`
199
+ and `a` are **Runs-page** keys now that Home is the prototype's Home.
200
+ Unchanged: `1`–`9`, `Enter`/`→`/`l`, the arrows, `PgUp`, `PgDn` and `q`.
201
+ (Corrected: an earlier draft of this entry said `h`/`?` were unchanged. They
202
+ are not — `h` opened Help in 0.32.0 and now opens **Home**, and `?` alone
203
+ opens **Help**. `y` no longer opens a History page; it opens **Runs** at the
204
+ first day header of its history table. See the two entries below.)
205
+ - accounting: money and licence figures are either measured or carry `≈` with
206
+ their basis. API-equivalent estimates use the recorded per-attempt estimate;
207
+ a licence draw uses the measured pool rate multiplied by measured worker-
208
+ minutes and stays blank when no rate is available; an undeclared subscription
209
+ price stays null. On the release corpus, `113` of `188` V2 runs carried a
210
+ cost figure and the other `75` did not, so no zero was manufactured for them.
211
+ - workflow: `bullswarm workflow reindex [--json] [--force]` backfills finished
212
+ runs from their durable directories, skips legacy and unfinished runs,
213
+ repairs missing index entries, and can rebuild existing rollups with
214
+ `--force`. The JSON form reports `ok`, `indexPath`, `scanned`, `written`,
215
+ `skipped`, `legacy`, `unfinished`, `present`, `failed`, and `failures[]`.
216
+ - docs: the observing guide, README, CLI reference, command help, and Claude
217
+ Mod README now describe the eight pages, the full key map, the measured/
218
+ labelled money rule, and the Mod's smaller Run/Step/Usage-Pools surface.
219
+
220
+ ## 0.32.1 — free models first, with graceful failover
221
+
222
+ - meters: rate-limited or failed live reads now persist a per-pool negative-cache hold (honouring `Retry-After` or the five-minute freshness window), and `pools`/the Claude Mod identify the stale snapshot's error and retry time.
223
+ - rename: the OpenCode pool is now `opencode`; the first run migrates saved
224
+ state, routing, provider, and meter names automatically, while historical
225
+ workflow run records keep their recorded `opencode2` name.
226
+ - routing: a pool whose model for the effort tier costs nothing is ranked ahead
227
+ of every metered pool while it is healthy. Free-ness is per (pool, effort
228
+ tier) and comes from the connector — `modelProfiles[].free`, or a model name
229
+ carrying a standalone `free` segment. The free tier sits below the forecast
230
+ gate and the 5-hour headroom tier and above expiring-soon urgency, so an
231
+ expiring metered window can now go unspent while free work runs; the ranking
232
+ among metered pools is unchanged. The reason reads `free pool first: <pool>
233
+ (free model <model>, …) · metered pools ranked below free: <pool> <surplus>`.
234
+ - routing: a pool that stalls, returns a server error, or (on a free pool) hands
235
+ back literally empty output takes a strike. The first is recorded; the second
236
+ consecutive one soft-benches the pool for a 10-minute cooldown, after which it
237
+ returns automatically. The strike count survives the cooldown and is cleared
238
+ only by a success. Auth is untouched: an upstream auth failure still
239
+ quarantines and still spreads across a credential group. An answer the
240
+ verifier judged thin but not empty stays semantic and is not retried
241
+ elsewhere.
242
+ - workflow: a free pool's silence clock is the median wall time of its own
243
+ recorded runs at that (pool, effort) rung — the `p50`, once 3 runs exist there
244
+ — with a 5-minute floor, instead of the one-hour default a metered pool keeps.
245
+ The multiplier on that median is 1 (`FREE_STALL_P50_FACTOR`): a worker silent
246
+ for as long as the whole rung usually takes has stopped working. Stalled
247
+ attempts are excluded from the median so a pool cannot tighten its own
248
+ threshold by stalling.
249
+ - workflow: a stalled attempt ends, keeps its partial output on disk (the retry
250
+ writes a new `-attempt-N` file beside it, never over it), releases its
251
+ in-flight ledger entry, and re-dispatches the same action on the next eligible
252
+ pool in the same run. The attempt record and `attempt.finished` carry
253
+ `stalled`, `partialOutput`, `silentSec` and `willRetry`; a new `pool.benched` event names
254
+ the pool, reason, strike count and deadline; the retry's reason is prefixed
255
+ `fallback from <pool> after stall <n>s`. A free-pool stall (and the
256
+ free-only literally-empty-output provider reclassification) does not spend
257
+ `maxMechanicalRetries`; the tried-set is the bound and each pool is tried at
258
+ most once for the action. A metered-pool stall keeps the mechanical retry
259
+ accounting.
260
+ - workflow: evidence and acceptance steps are exempt from the free tier and
261
+ route on pace as before, with one preference — a pool that wrote the work
262
+ being judged is chosen last, and only when no other pool is eligible, which
263
+ the reason then says (`evidence step: only the writer pool <pool> is
264
+ eligible`). This restores, in prefer-not rather than forbid form, the steering
265
+ removed in `eb83b79`; the reason line now names the exception, which its
266
+ absence was half the reason for that removal.
267
+ - pools: `bullswarm pools` prints `free=<model>` for a pool whose model costs
268
+ nothing (`free=<tier>:<model>` when it differs per effort tier, since `pools`
269
+ names no lane), `BENCHED until <time> (<reason>, <n> strikes)` for a benched
270
+ pool, and `strikes=<n>(<reason>)` for one carrying an uncounted-out strike. It
271
+ sweeps expired benches the same way it sweeps expired quarantines.
272
+ - routing: measured pacing rates now charge timed in-flight work at the actual
273
+ `rate × remaining minutes` with no 3-point floor, so an expiring-soon pool
274
+ with a known burn rate is not demoted by a tie-breaker meant for unmeasured
275
+ pools. `config.inflightPenaltyPct` remains the per-agent fallback for pools
276
+ without a measured rate (and for in-flight records with unknown duration).
277
+ - workflow: every attempt record now carries `routeWhy` (the router's reason)
278
+ and `routeCandidates` (each pool's effective surplus, urgency state and
279
+ pacing forecast at pick time); `workflow action show --json` and
280
+ `runs result --json` print them, and older state files still load.
281
+
282
+ - routing: a pool whose pacing window is about to reset is `draining` only
283
+ when its forecast is at or above 95% *and* ahead of the window's own clock,
284
+ the same clock-relative shape the 5h near-limit line already has. A pool on
285
+ or behind pace keeps spending right up to its reset instead of being ranked
286
+ last by a fixed line (observed 2026-09-16: command-code at 94.9% used with
287
+ 98% of the month gone was passed over while +3 points of quota expired).
288
+ The skip reason now reads `expiring but draining (forecast >= 95% and past
289
+ its clock): <pool> <forecast>% (<elapsed>% elapsed)`.
290
+ - workflow goal: refuses to launch a duplicate of a run that is already going.
291
+ Before anything is validated or launched, an ongoing run with the same goal
292
+ text and cwd exits 2, naming that run's shortId, age, and watch command
293
+ (JSON: `{"error":"duplicate-goal",...}`); `--again` starts the copy anyway
294
+ (observed 2026-09-16: a caller whose JSON parser failed on the first
295
+ launch's output retried five seconds later and two identical workflows ran
296
+ side by side in the same directory).
297
+
3
298
  ## 0.32.0 — the dashboard is the main screen
4
299
 
5
300
  - wording: a program run's dependency levels are called phases everywhere the
package/GOAL.md CHANGED
@@ -35,7 +35,7 @@ A working prototype installed at `~/.bullswarm/` with:
35
35
  2. **Connector registry** (`~/.bullswarm/connectors/*.json`): declarative per-CLI
36
36
  spawn command, auth-failure signatures, output extraction, verify contract,
37
37
  quirk fields (e.g. PWD resolution). Seeded with codex, grok,
38
- command-code, opencode2 — extracted from the proven `/offload` skill.
38
+ command-code, opencode — extracted from the proven `/offload` skill.
39
39
  3. **Meter layer**: per-pool window definition (5h / weekly / none) with
40
40
  programmatic readers where providers expose usage, manual declaration
41
41
  (`--meter pool=window,pct`) where they don't. Unmetered pools pace as
package/README.md CHANGED
@@ -55,9 +55,10 @@ as a Claude Mod (a plugin of TypeScript function hooks, behind
55
55
  prompt, the pools named in the model's context, Claude's general-purpose
56
56
  subagents routed to whichever pool has surplus and answered with the
57
57
  verified output, and the verdict appended to every `bullswarm run` the model
58
- runs. The Mod is the dashboard's read-only counterpart: the same Run, Step and
59
- Usage pages in the same meter colours, with no edit or install action. See
60
- [mods/bullswarm/README.md](mods/bullswarm/README.md).
58
+ runs. The Mod is the dashboard's read-only counterpart: its pane shows Run,
59
+ Step, and the Usage/Pools view in the same meter colours. It does not expose
60
+ the dashboard's Home, Runs, Budget, Stats, Fleet, or Help pages, and
61
+ it has no edit or install action. See [mods/bullswarm/README.md](mods/bullswarm/README.md).
61
62
 
62
63
  Three ways to load it:
63
64
 
@@ -83,12 +84,67 @@ over the skills-dir link, and Claude says so at startup.
83
84
  Once setup is complete, bare `bullswarm` opens the dashboard on its Home page.
84
85
  Use `bullswarm --setup` or `bullswarm setup` to open setup again, and use
85
86
  `bullswarm workflow tui` when you want the explicit dashboard command. The
86
- pages are Home, Run, Step, Usage, and Help. A sticky header and bottom nav
87
- (`[ 1.<run> ] … [ usage ] [ help ] [ quit ]`, each button's key
88
- underlined inside its label) carry `q` (quit),
89
- `h` or `?` (help), `u` (usage), `e` (edit), `i` (install), `l`/`p` (Usage tabs), arrows or
90
- PgUp/PgDn (scroll), and Enter; click any button, tab, run or step, or use the
91
- wheel to scroll.
87
+ eight pages answer different questions:
88
+
89
+ | Page | What it answers |
90
+ |---|---|
91
+ | Home | What happened today, what is verified, what measured or labelled money/licence data exists, and what is active or recent; includes pool/model/project breakdowns. |
92
+ | Runs | Which workflows are active or historical — one `active` block and the History day table below it — plus the product commands; the integration line and `/` filter live here, and `?` carries the agents and `run it` blocks. |
93
+ | Run | Where one workflow is in its plan, which workers are live or next, and its ETA and budget shares. |
94
+ | Step | What one action is doing: route, attempt, activity, verdict/failure, prompt, usage, events, output, and artifacts. |
95
+ | Budget | Pool quota windows, measured worker-minutes versus rest, measured or `≈` money, fit, and biggest workflows. |
96
+ | Stats | Runs, spend, worker-minutes, and verification over 7d/30d/all, by pool, model, and project. |
97
+ | Fleet | Lane/provider model and reasoning rungs, records, meter state, and the setup edit hand-off. |
98
+ | Help | Every key, click, layout rule, and dashboard command. |
99
+
100
+ The visual-fidelity pass keeps the same real numbers while composing and
101
+ colouring these pages like the approved prototype. At 55 columns every Home
102
+ today tile and per-step bar, every Stats model, project and its sparkline, every
103
+ Stats and Fleet pool row, and every run row in the Runs history table keeps one
104
+ row per item on the phone; Budget's per-pool block (a header over `used`,
105
+ `by bullswarm`, `room` and `so far`), Home's `budget · this week` pool (meter
106
+ plus a reset line, as the prototype draws it) and the Runs `active` entry are
107
+ the deliberate exceptions. At 200 columns every page composes to the frame
108
+ rather than capping at the 120-column composition. Estimated figures still
109
+ carry `≈` and their basis; a figure that cannot be measured is a line of words
110
+ saying so — no page draws an empty or dotted track for missing data.
111
+
112
+ Every page has a sticky header, a page tab row, and a sticky bottom nav. The
113
+ tab row is five tabs — Home, Runs, Budget, Stats, Fleet — with Run and Step
114
+ marking Runs, and Help shown only while it is open. The shared key table is:
115
+
116
+ | Key | Does |
117
+ |---|---|
118
+ | `r` | open Runs; the view refreshes itself, so `r` is no longer refresh |
119
+ | `b` | open Budget; it is no longer move-out |
120
+ | `s` | open Stats |
121
+ | `y` | open Runs at its History table (the first day header), except that it confirms a pending stop |
122
+ | `f` | open Fleet |
123
+ | `h` | open Home |
124
+ | `?` | open Help |
125
+ | `1`–`9` | open that run from the nav |
126
+ | `Tab` | cycle the current page's sub-tabs |
127
+ | `Shift+Tab` | cycle workflows |
128
+ | `p` | cycle the period on Home and Stats |
129
+ | `Esc` / `←` | move out one page, then Home |
130
+ | `↑`/`k`, `↓`/`j` | move one line |
131
+ | `Enter` / `→` / `l` | open the selected run, step, tab, or action |
132
+ | `PgUp` / `PgDn` | scroll one screen |
133
+ | `Home` / `End` | jump to the top or bottom |
134
+ | `ctrl+s` | copy the screen through OSC 52, falling back to `pbcopy`, `wl-copy`, or `xclip` |
135
+ | `q` | quit the dashboard; workflows keep running |
136
+
137
+ The 0.33.0 rebinding is deliberate: `r` no longer refreshes, `b` no longer
138
+ moves out, and `Tab` no longer cycles workflows. Esc/left moves out, the view
139
+ refreshes itself, and Shift+Tab still cycles workflows. Runs also provides `/`
140
+ (filter), `a` (active/all), and `i` (install); Run provides `o`, `v`, and `t`,
141
+ and Fleet provides `e` for setup. Click tabs, tiles, bars, runs, steps, dates,
142
+ or controls, or use the wheel to scroll.
143
+
144
+ Home, Runs, Budget, and Stats read a per-run rollup that every finishing
145
+ run appends to `~/.bullswarm/history/runs.jsonl`. After upgrading, backfill
146
+ the runs that finished before 0.33.0 once with `bullswarm workflow reindex`;
147
+ legacy runs get a minimal record and only runs still in flight are skipped.
92
148
 
93
149
  One bounded outcome — a task with a clear finish line:
94
150
 
@@ -198,7 +254,7 @@ page maps its pages, keys, and mouse controls.
198
254
  | [Concepts](https://bulls-work.github.io/bullswarm/guide/concepts) | Pools, lanes, surplus, the two windows, verdicts, quarantine, and the run directory |
199
255
  | [Run one task](https://bulls-work.github.io/bullswarm/guide/run) | Every `bullswarm run` option, and what each verdict asks you to do |
200
256
  | [Workflows](https://bulls-work.github.io/bullswarm/guide/workflows) | Authoring the program `workflow goal` executes: territories, dependencies, integration, acceptance |
201
- | [Observing runs](https://bulls-work.github.io/bullswarm/guide/observing) | `workflow watch`, the Home/Run/Step/Usage/Help dashboard pages, keys, mouse, and terminal glyphs |
257
+ | [Observing runs](https://bulls-work.github.io/bullswarm/guide/observing) | `workflow watch`, the Home/Runs/Run/Step/Budget/Stats/Fleet/Help dashboard pages, keys, mouse, and terminal glyphs |
202
258
  | [Routing](https://bulls-work.github.io/bullswarm/guide/routing) | How a pool is picked: pace, 5-hour headroom, urgency, load, quarantine |
203
259
  | [CLI reference](https://bulls-work.github.io/bullswarm/reference/cli) | Every verb and nested subcommand, with its flags and defaults |
204
260
  | [Workflow program](https://bulls-work.github.io/bullswarm/reference/program) | The `bullswarm.workflow.program.v2` document: action fields, kinds, validation rules |