Bullswarm

Route bounded coding work across whichever installed agent CLI has quota to spare.

View the Project on GitHub cowcow02/bullswarm

Historical (2026-08-29): accurate when written; see CHANGELOG for what changed since.

Planner prompt and context audit — 2026-08-29

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.

1. What one planner turn receives

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 between turn 1 and turn 2 of the same run.

Where the 163 k of turn 2 went

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

2. What is said more than once

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.

3. Does it matter? Measured

4. Recommendation

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.

5. Outcome

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.

Deliverables:

6. Second audit — after the goal-4 rerun on 0.15.0 (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.