Bullswarm

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

View the Project on GitHub cowcow02/bullswarm

bullswarm Dynamic Workflows — Design

Status: historical. The authored-graph engine this document describes — workflow run, workflow validate, workflow draft, the bullswarm.workflow.v1 document, its decide step and the check-output-schema preflight — was removed in 0.27.0. The program-based V2 goal engine is the only workflow engine; see README.md. Kept for design rationale only · Created: 2026-08-21

Problem

bullswarm routes ONE task to ONE pool with ONE verdict. Real work comes in shapes: audit 47 route handlers, review every changed file in a PR, sweep a repo for a bug class, research across sources and cross-check. Claude Code solved this with dynamic workflows: the orchestration plan moves from the model’s context window into an executable artifact. This design brings that capability to bullswarm’s multi-subscription world — where the execution fabric is metered, verified, and provider-diverse.

Prior art (studied)

System Core idea What we take What we reject
Claude Code dynamic workflows Plan-in-JS executed by runtime; 16 concurrent / 1000 cap; resumable; adversarial verification enforced structurally Phases + fan-out + skeptic patterns; background run; script-as-artifact JS-sandbox authoring (Claude writes code; our users/agents shouldn’t have to)
open-dynamic-workflow (odw) Open reimplementation pointed at external CLIs; vm sandbox; deterministic resume via call fingerprints; artifacts per run Deterministic-replay resume; artifact tree; item-streaming pipelines; event stream → renderer separation; declarative safety limits TS workflow authoring; static per-call providers; no meters; no content verify
MS Conductor YAML workflows, deterministic routing, zero tokens on orchestration Declarative format; human gates; dry-run validation Full DAG generality
pilotfish Role policy; verdicts are evidence not authority; bounded escalation Verdict vocabulary; escalation caps Prompt-only enforcement

Thesis

Claude’s workflows answer “how do I run 500 agent calls without drowning my context?” — but every call burns the SAME subscription. bullswarm answers a question nobody else asks: “what if each of those 500 calls could land on whichever subscription has headroom, and get content-verified before it counts?” The workflow feature makes bullswarm the metered execution fabric for plan-driven work.

Format decision: declarative JSON, not executable JS

Claude/odw execute user-written JS in sandboxes. That buys expressiveness at the cost of a security boundary, nondeterminism guards, and requiring the author to be a programmer. Bullswarm’s workflows are:

If expressiveness becomes a wall later, odw-compatible .ts workflows can be added as a second format without touching the runtime contract.

Schema

{
  "name": "route-review",                  // required, kebab-case
  "description": "...",                     // required
  "version": "1.0.0",
  "inputs": {                               // optional, overridable per run
    "targetDir": { "default": "." },
    "files":     { "required": false }
  },
  "phases": [                               // ordered; UX grouping + gates
    {
      "name": "review",
      "steps": [
        {
          "id": "fanout-review",            // required, unique
          "type": "run",                    // run | fanout | verify | decide
          "taskFile": "/tmp/wf//task-.md",
          "lane": "analyze",
          "addDir": "",
          "timeoutSec": 600,
          "onError": "continue"             // continue | fail | skip-phase
        },
        {
          "id": "per-file-review",
          "type": "fanout",
          "itemsFrom": "discover.files",   // JSON path into state.outputs
          "concurrency": 4,
          "stepTemplate": {                 // expanded once per item
            "lane": "chore",
            "addDir": "",
            "prompt": "Review  for auth gaps."
          },
          "onError": "continue"
        }
      ]
    }
  ],
  "settings": {
    "concurrency": 8,                       // global cap across fanouts
    "stopOnPhaseFailure": false,
    "escalateOnFail": true                  // retry failed steps on next pool
  }
}

Step fields (all pass through to the existing run pipeline): lane, addDir, taskFile, prompt, timeoutSec, pool (pin a pool; default = paced routing), onError.

Step types

run and fan-out templates may declare outputSchema when later actions need structured data. The schema is an object-typed JSON-Schema subset. A successful run records outputs.<id>.data and schemaOk: true; a fan-out records the same fields on each outputs.<fanoutId>.items[] entry. A mismatch gets one bounded schema retry and records schemaOk: false plus schemaErrors if the retry also fails. Ordinary prose should leave outputSchema unset; verify has its own fixed verdict schema.

Before replying, a schema-bound worker receives the exact schema file and a deterministic check-output-schema command for a temporary candidate object. It must correct the candidate until that preflight exits zero and then emit the validated object. The runtime validates the captured response again; worker preflight reduces avoidable retries but never replaces the authoritative gate.

Templating

resolves against a single scope, precedence: loop item > inputs > prior outputs (`outputs.<stepId>`), whole-run metadata (`runId`, `wfDir`). Missing reference → validation error *unless* inside `fanout.stepTemplate` where/`` resolve per-expansion.

Runtime

load → validate → resolve inputs → for each phase:
  for each step:
    run | expand fanout → dispatch tasks through router+verify (existing
    watchOnce pipeline) with concurrency limiter
    record outputs → next
artifacts + report + exit code

Terminal UX (the deliverable’s face)

Claude-style live view, plain ANSI (no deps):

bullswarm workflow · route-review · run wf-a1b2c3

▐ phase 1/2 · discover                                    ⏳ running
  ✓ discover.files              codex        12.3s   ok · 47 items

▐ phase 2/2 · review                                      ⏳ running
  ⟡ per-file-review[0/47]       grok         31.0s   … verifying
  ✓ per-file-review[1/47]       command-code 28.9s   ok
  ✗ per-file-review[2/47]       opencode2    40.1s   fail · announcement without substance
    ↳ escalate → codex
  ⋈ per-file-review[3/47]       —            —       quarantined pool, waiting

── summary ──────────────────────────────────────────────
✓ 45 · ✗ 2 · ⋈ 0                        elapsed 6m 12s
report: ~/.bullswarm/workflows/wf-a1b2c3/report.json

Marks: ok · fail · running · blocked · skipped · phase. Non-TTY: same events as indented JSONL lines. --json: machine report only.

CLI

Non-goals (prototype)

Success proofs

  1. workflow validate catches: bad lane, unknown pool pin, missing template ref, duplicate step id, bad onError value, non-unique phase names.
  2. Example workflow runs end-to-end on echo connector in CI-safe mode.
  3. A fanout of N items produces N verdicts + N output files, concurrency never exceeds the cap (assert via timing/ordering in test).
  4. Kill mid-fanout → --resume completes remaining items only.
  5. Escalation: forced-fail first pool → step retries on second pool → ok.
  6. Dogfood: real workflow over bullswarm’s own connectors directory; outputs QA’d by me.
  7. Terminal UX shows phases/steps/pool/status exactly as specced above (screenshot-in-text captured in QA notes).