Route bounded coding work across whichever installed agent CLI has quota to spare.
Historical (2026-08-29): accurate when written; see CHANGELOG for what changed since.
Question from the user: after the 0.11 → 0.14 iterations, does the instruction set given to the orchestrator/planner still make sense, or should it be simplified or refactored?
Method: measure what the planner actually receives, not what the source files
look like. The two planner task files of dogfood run wf-mtdq1l9v-ed22fe
(75t4n2, bullswarm building outputSchema in its own repo, runtime 0.13.2)
are the specimens; sizes are characters of the task text
(task-orchestrator-*.md), tokens ≈ chars / 4.
| section | source | turn 1 | turn 2 |
|---|---|---|---|
| orchestrator prompt + worktree line | goal.js AUTONOMOUS_ORCHESTRATOR_PROMPT |
5 000 | 5 000 |
| PLANNING DOCTRINE (11 bullets) | runtime.js runDecision |
3 605 | 3 605 |
| Action skeletons (6 shapes + verify semantics) | runtime.js |
1 342 | 1 342 |
| Program skeleton (discovery → fan-out → verify → suite) | runtime.js |
1 217 | 1 217 |
| Graph skeleton (two chains + suite) + fanout paragraph + runtime-owned line | runtime.js |
4 054 | 4 054 |
| durable context (JSON) | runtime.js plannerContext |
17 474 | 162 946 |
| total | 32 730 (~8 k tokens) | 178 452 (~45 k tokens) |
The fixed prefix is 15.2 k chars on every turn. The durable context grew 9× between turn 1 and turn 2 of the same run.
| key | chars | what it is |
|---|---|---|
completedActions (19 entries) |
66 700 | every finished action with its full attempt records: routing candidates and pace numbers, usage, pricing table, child pid, timings — ~3.5 k per action |
outputs (19 entries) |
41 560 | outputExcerpt of ~3 k chars for every finished action, including ones that finished ok and were already verified in the previous program |
intent |
6 657 | the goal text (6.5 k) + cwd + policy — needed, once |
failures |
1 073 | the two blocked actions — duplicates of completedActions entries |
availablePools, budget, executionConstraints, closedPhases, … |
~1 800 | fine |
Reading the prefix as the planner does, the same rules appear two or three times in different words:
| rule | orchestrator prompt | doctrine | skeletons |
|---|---|---|---|
| propose the whole program, one round trip costs minutes | item 2 | bullets 1, 2 | “all in ONE decision” ×2 |
| N items → N run + N verify + one suite verify | item 5 | “Per-item chains” | Graph skeleton |
unknown items → discovery + itemsFrom fan-out |
item 5 | “Unknown item count” | Program skeleton + fanout paragraph |
every verify gets a repair policy |
item 5 | “Verification failures” | verify skeleton |
completion: all-actions-ok on a clean program |
item 5 | “Self-completing programs” | — |
| self-contained worker prompts, file ownership | item 3 | “File ownership”, “Self-contained prompts” | — |
| don’t propose pool/addDir/taskFile | closing line | — | final line |
Item 5 of the orchestrator prompt alone is 1 050 chars and restates four
doctrine bullets. The two program skeletons both end in the same
verify-items → verify-suite tail.
ok:false concerns that matter.completion). No observed decision needed a rule
to be stated twice.Yes — refactor, in two independent pieces, both measurable:
A. Compact the durable context (the 9× growth). Planner-facing ledger rows
instead of raw ledger entries: { id, type, phase, status, pool, durationSec,
attempts, why } (~150 chars; 19 actions → ~3 k instead of 66.7 k). Keep a
full outputExcerpt only for actions finished since the last decision and
for every ok:false verify; older ok actions get a one-line summary (id, ok,
first 200 chars). Replace failures with the ids of failing actions (their
full entry already sits in the ledger). Expected turn-2 context: ~25 k chars
instead of 163 k. Pure runtime change; no planner behaviour change intended.
B. One contract instead of three overlapping texts. Merge
AUTONOMOUS_ORCHESTRATOR_PROMPT and the doctrine bullets into a single ordered
list of ~10 rules (target ≤ 4 k chars, from 8.1 k), each stated once with its
reason; keep exactly two JSON examples — the action shapes list and one
complete program (discovery → data-driven fan-out → per-item verify with
repair → suite verify, with completion) — and delete the second program
skeleton (target ≤ 3 k, from 6.6 k). Total prefix ≤ 7 k chars, from 15.2 k.
Acceptance for both: unit tests on the context builder (row shape, excerpt
policy by decision sequence) and on the prompt (each rule appears once; the
skeleton assertions in tests/workflow-adaptive.test.js updated); then one
re-run of goal 3 on the same fixture (baseline 0.13.1: 28 min 42 s, 1 planner
turn, 294 s) to confirm the decision shape is unchanged and record the new
per-turn size.
Not recommended: cutting the goal text or the scout excerpt from the context — both were used verbatim by every first-turn program observed.
Measurements below were taken after the refactor from the current source and
from the committed source saved into /tmp/goal-before.mjs and
/tmp/runtime-before.js, using the same temporary measurement script. The
canonical prefix is the complete emitted planner task text counted from its
first character up to (not including) the durable-context marker, with the
worktree-isolation suffix included. The committed baseline predates the named
section exports, so its emitted prefix was reconstructed from the committed
runtime.js task-text assembly and the committed AUTONOMOUS_ORCHESTRATOR_PROMPT.
16,316 characters before
and 5,208 characters after. Command: node /tmp/measure-planner.mjs.16,209 characters before and 5,101 characters after. The
after value is 107 characters shorter because it excludes the unchanged
worktree-isolation suffix; this is a secondary source-level measurement, not
the canonical emitted-prefix headline. Command: node /tmp/measure-planner.mjs.PLANNER_RULES_SECTION: OBSERVED, 2,202 characters after. Command:
node /tmp/measure-planner.mjs.PLANNER_EXAMPLES_SECTION: OBSERVED, 1,867 characters after. Command:
node /tmp/measure-planner.mjs.AUTONOMOUS_ORCHESTRATOR_PROMPT: OBSERVED, 4,670 characters after;
the committed before source had no separately exported rules or examples
sections, so separate before-section sizes are NOT AVAILABLE, not
inferred. Command: node /tmp/measure-planner.mjs.163,000 characters in the
audit’s rounded turn-2 durable-context total (the detailed table records
162,946; the full turn-2 task was 178,452), and COMPUTED 23,547
characters after. The computed sample applies 19
compact ledger rows at 150 characters each, keeps a 3,000-character scout
excerpt and two 3,000-character failing-verify excerpts, truncates the
other 16 action excerpts to 200 characters, represents two failures as
20-character IDs, and retains the audit’s 6,657-character intent and
1,800-character other-context components. Command: node /tmp/compute-context.mjs.Deliverables:
src/workflow/goal.js.src/workflow/runtime.js imports
the shared contract instead of carrying a duplicate doctrine and graph
skeleton.skill/SKILL.md was left unchanged: it documents the general durable
context and the separate run-state/TUI attempt view, but does not document a
renamed/dropped planner-context field shape such as the old attempt records
or an old failures representation.ydpjts)Method: a Claude Code dynamic workflow (12 agents, 16 min) reviewed the COMPLETE turn-1 task text of run ydpjts
(19,577 chars; 6,199-char prefix) and the program the planner wrote, from three lenses — width/critical path,
worker-prompt authoring, clarity vs Claude’s own workflow-authoring reference — and adversarially verified every new
finding (refute-framed, source-checked). 3 known + 6 new findings survived; 2 were refuted (one because lane was
dead text — see the runtime defect below — one for deleting a must-keep rule).
What the planner received said nothing wrong; it said too little in three places, and the example showed a linear program:
| finding | evidence on ydpjts |
contract change |
|---|---|---|
| a verify’s verdict was treated as a dependency | tests dependsOn verify-src; 330 s idle |
rule 3: “a worker depends on the run that wrote its input files, never on that run’s verify (a verdict is not data)” |
| width framed only as “known N items” | one tests worker, 951 s, where three file-disjoint writers were allowed |
rule 4: “each file-disjoint unit … one worker for N independent files is N chains in series” |
| outputSchema invited on a prose report | report schema → stray key, then fenced tail → retry + planner turn (≈ 685 s) |
rule 6: schema only where a later action reads the object; prose gets none |
| repair cannot rewrite an answer under review (new) | verify-report repair prompt asked the worker to “restate the report” — unreadable by design (repairAndReverify re-reads the original outFile) |
rule 7: “the repair edits files and cannot rewrite the answer under review, so reject only what a file edit can fix” |
workers demanded npm test as acceptance (new) |
tests prompt: “No other worker is editing the tree while you run” |
shared-tree line: unit’s focused command, never the full suite |
no guidance on effort/lane (new) |
every action ran at build/medium | rule 10: planner sets lane and effort; they pick the model tier |
| example program was linear and not valid JSON (new) | — | example rewritten: {"actions":[…],"completion":…}, tests depend on fix and run beside verify-fix, lane/effort shown |
runtime: planner lane silently overwritten (new) |
runner.js merged {...action, ...actionDefaults} so lane: build won |
fixed: action overrides gate defaults; addDir stays runtime-owned; lane validated |
Sizes after: rules 3,999 (cap 4,000), examples 2,938 (cap 3,000). Every rule still stated once with its reason.
Acceptance: goal-4 rerun on the new contract with workers pinned to opencode2/relay/gpt-5.6-luna (strategy
assignments high/medium/low, cleared after) — structural pass conditions: tests depend on impl-src not
verify-src; more than one file-disjoint writer; parallelism ≥ 1.5; 0 repairs / corrections / schema retries;
auto-completed; ≥ 299 tests, existing tests extended only; wall reported with the pool mix.
Outcome: r2vu9i (runtime d14c1fa) met every structural condition except “0 repairs” (4 rounds, 2 of them verifier
moving-goalposts → re-verify scoping 9af8fdf); bizp4s (runtime 9af8fdf) then ran in 25 min 13 s with one planner
turn (247 s), parallelism 1.77, 3 repair rounds each fixing a real defect, 0 schema retries / corrections, 319/319.
Details in docs/experiments/2026-08-29-dogfood-bullswarm-builds-bullswarm.md.