Route bounded coding work across whichever installed agent CLI has quota to spare.
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
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.
| 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 |
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.
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:
workflow validate),
writable by any agent via its existing file tools, diffable in PRs.agent() calls (filter, dedupe, merge) is
either a step option here or belongs in a delegate’s prompt.If expressiveness becomes a wall later, odw-compatible .ts workflows can be
added as a second format without touching the runtime contract.
{
"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.
run — one offload. Verdict recorded.fanout — expand stepTemplate once per item from itemsFrom.
Items may be strings or objects (`` paths work). Concurrency
capped by min(step, settings).verify — independently review a prior artifact and require structured
{ok, concerns, summary} evidence before dependent work may trust it.decide — give the durable orchestrator current intent, outputs,
failures, budgets, and capabilities. Its versioned proposal is validated
before bounded run, fanout, or verify actions enter the plan.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.
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.
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
watchOnce verbatim — same verdict contract
(ok, keepOnClaude, why, contentUsableDespiteExit), same quarantine
side effects, same meter accounting as single runs.escalateOnFail): a failed step retries once on the next
pool by surplus (existing pickPool minus incumbent). Mirrors odw retry
but is verdict-driven, not timer-driven.~/.bullswarm/workflows/<runId>/state.json after every step —
crash-safe by construction.workflow run --resume <runId> skips steps whose saved verdict
is ok:true and whose declared output schema, if any, is satisfied;
everything else re-runs. Fanout items resume by content fingerprint, so
already verified items remain complete even when discovery order changes.state.json, report.json, every task/out file.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.
bullswarm workflow validate <file> — schema + template refs + lane/pool namesbullswarm workflow list — discovered workflows in ./workflows,
~/.bullswarm/workflowsbullswarm workflow run <file> [--input k=v]… [--resume <runId>] [--json]
[--quiet]bullswarm workflow run <file> works headless (no TTY prompts ever)onErrorworkflow validate catches: bad lane, unknown pool pin, missing template
ref, duplicate step id, bad onError value, non-unique phase names.--resume completes remaining items only.