# swarm-orchestrator [![gates](https://img.shields.io/github/actions/workflow/status/moonrunnerkc/swarm-orchestrator/gates.yml?branch=v13-main&style=flat-square&label=gates)](https://github.com/moonrunnerkc/swarm-orchestrator/actions/workflows/gates.yml) [![nightly proof](https://img.shields.io/github/actions/workflow/status/moonrunnerkc/swarm-orchestrator/nightly-proof.yml?branch=v13-main&style=flat-square&label=nightly%20proof)](https://github.com/moonrunnerkc/swarm-orchestrator/actions/workflows/nightly-proof.yml) [![weekly evidence](https://img.shields.io/github/actions/workflow/status/moonrunnerkc/swarm-orchestrator/weekly-evidence.yml?branch=v13-main&style=flat-square&label=weekly%20evidence)](https://github.com/moonrunnerkc/swarm-orchestrator/actions/workflows/weekly-evidence.yml) [![npm](https://img.shields.io/npm/v/swarm-orchestrator?style=flat-square&label=npm)](https://www.npmjs.com/package/swarm-orchestrator) [![node](https://img.shields.io/badge/node-%3E%3D24-blue?style=flat-square)](package.json) [![license](https://img.shields.io/github/license/moonrunnerkc/swarm-orchestrator?style=flat-square)](LICENSE) A coding agent whose claims about its own work resolve to machine-captured evidence. The model can say whatever it likes. It cannot make a gate pass, it cannot mark a claim verified, and it cannot change a record after the fact. Those are the harness's to decide, and the run exports a signed, hash-chained bundle that anybody can check without installing this tool. [What it does](#what-it-does) | [Install](#install) | [Use](#use) | [Several workers](#several-workers-at-once) | [Watching it work](#watching-it-work) | [What is claimed](#what-is-claimed) | [What is not claimed](#what-is-not-claimed) | [How it works](#how-it-works) | [Limits](#limits) | [Upgrading from v12](#upgrading-from-v12) ## What it does Give it a task and a git workspace. It plans, declares the files it intends to touch, edits through a chokepoint that records every tool call, runs your gates, and retries failures under a numeric ratchet that refuses a fix which trades away tests, assertions, or coverage. Then it exports the evidence. Here is a real run, committed: [`live-tasks.md`](docs/evidence/2026-08-18/live-tasks.md), with its bundle in [`live-frontier/`](docs/evidence/2026-08-18/live-frontier). And here is the packaged tool doing it, installed from a tarball into a directory holding nothing else, against a workspace it had never seen, recorded in a real terminal: [`installed-package-run.md`](docs/evidence/2026-08-23/installed-package-run.md). ## Install npm install -g swarm-orchestrator That is **13.1.9**, and it leaves `swarm` on your path. Installing from a tag works too, but only into a project rather than globally: npm install github:moonrunnerkc/swarm-orchestrator#v13.1.9 `dist/` is not committed, so a git ref builds itself on install and needs this package's devDependencies to do it. npm does not install those when a git ref is installed with `-g`: it carries the global context into its git-dependency preparation, places the clone as a root package, and runs the build without the compiler. The published package needs no build, which is why the line above it is the one to use. Anything below 13 is a different program. This package name carried a pull-request auditor through 12.x, and `npm install -g swarm-orchestrator@12` still installs it, so pin the major if you depend on one or the other: [Upgrading from v12](#upgrading-from-v12). If `swarm` turns out to be an older version than you installed, something else owns the command. The usual cause is a development checkout linked into the global prefix with `npm link`, which owns it until it is removed and which npm cannot install over: the install either fails renaming a symlinked directory aside, or succeeds behind a stale executable still pointing at the checkout. `swarm doctor` says which of those happened and `swarm doctor --fix` repairs it. Node 24 or newer. That is a runtime floor rather than a preference: the coverage cycle spawns the test runner with `--test-isolation=process`, which Node 22 rejects as a bad option, so on anything older that measurement does not happen. ## Use swarm "make slugify collapse whitespace and strip punctuation" swarm # a session: type tasks, one after another swarm doctor # what owns the swarm command, and --fix to repair it swarm gates # run the gates over a workspace, no model swarm select # probe this machine, recommend a local model swarm calibrate # measure candidate models on the golden set swarm routing # what the reward log adds up to swarm parallel --tasks # a worker per task, then a merge queue swarm parallel --goal # break the goal into tasks, then run them --redundancy # try each task n ways, land the best of them --concurrency # how many workers may hold a worktree at once swarm review # what a past run produced, and open it swarm replay # read a bundle back swarm --no-tui "..." # plain lines even on a terminal swarm --no-color "..." # no colour, whatever the terminal says swarm --open-evidence "..." # open the review page when it finishes swarm --model local: "..." # a specific model swarm --workspace "..." # a repository other than the current directory swarm --base "..." # what the diff and the ratchet measure against swarm --attempts "..." # how many times the ratchet may retry a gate swarm --max-steps "..." # how long the loop may run before it stops swarm --local-endpoint # an OpenAI-compatible server other than the default `swarm --help` prints all of it, including the calibration flags. Set `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` or `GOOGLE_GENERATIVE_AI_API_KEY` for a frontier model, or start Ollama or rapid-mlx and pass `--model local:`. With no model named, the router picks one from what the calibration measured on this machine, and `swarm routing` shows what it has learned. Settings live in one optional `swarm.toml`: providers and endpoints, gate definitions, budgets, model pins, and the `[interface]`, `[theme]` and `[keys]` tables. Flags win over the file. Two of those are worth knowing about before you need them. [providers] local_thinking = false # the model behind the local endpoint answers without reasoning first [interface] confirm_timeout_minutes = 30 # an unanswered confirmation refuses itself; 0 waits for ever `local_thinking` matters on a reasoning model served locally. Left unset, nothing is sent and the server's own default stands, which is the only safe default: the field is a vendor extension and a server that rejects what it does not recognise would fail every call rather than one. Set it, and a model that would otherwise spend its whole output budget thinking answers instead. Against rapid-mlx serving qwen3.8:27b, one request cost 37 completion tokens with reasoning on and 2 with it off. Ollama's OpenAI-compatible route ignores the field; that is the server's limit rather than this one's. `confirm_timeout_minutes` is why a run left alone no longer waits for ever. The chokepoint asks before it runs a command that is not on the allowlist, and a question nobody answers used to hold the run until somebody came back to it. Refusing is what a declined question records either way, so the deadline costs that one tool call and the run carries on. ## A session, or a single task Run `swarm` with no task and it opens a session: one process, one ledger, and tasks typed one after another, each continuing the conversation the last one left. ``` › create calculator.js exporting add and multiply, and calculator.test.js covering both 8 gate(s) passed, 8 step(s) › add a divide function that throws on division by zero, and cover both cases ``` Each turn is measured on its own. A turn ends by recording where it left the tree, so the next one's gates see that turn's changes and not the ones before it. Three turns of this, with the bundle verified from outside and the page it produced, are in [`session.md`](docs/evidence/2026-08-24/session.md). `swarm "task"` still runs one task and exits, exactly as before. ## Several workers at once `swarm parallel` gives each task a git worktree of its own and a merge queue that lands them one at a time under the same ratchet a single run answers to. Nothing is merged into the branch you are sitting on; the result waits on an integration branch and the report tells you how to take it. Three things sit on top of that, each optional and each off unless asked for. **Workers read each other's ledgers.** They coordinate through the record they were already writing, not through a bus or a daemon: which files a peer has declared, which gates have failed on it and how often, which approaches it has already spent its attempts on. Nothing a worker reads there can render green. Every signal comes from a ledger record rather than from model text, every line names the peer it is about, and no signal reports a success, so there is nothing in it to mistake for a gate result. **`--redundancy ` tries each task several ways and lands the best of them.** The winner is chosen by reading which attempt moved the measured numbers, never by asking a model which answer it likes. The whole ranking goes on the chain, losers and the reason each was left out included, so you re-read the choice instead of taking it. **`--goal ` breaks the goal into tasks itself.** A planner reads the workspace with read-only tools and declares a task graph, which is checked for unique ids, resolving dependencies, no cycle, and files that two unordered tasks do not share, and recorded before the first worker starts. Nodes land layer by layer. Two runs of this, committed with their bundles, are in [`swarm.md`](docs/evidence/2026-08-24/swarm.md). The second one is the more useful: every structural check passed on a decomposition that could not work, because the planner left out a dependency. Whether a set of tasks adds up to a goal is a judgement about meaning, and this tool makes none. ## Watching it work On a terminal, a run draws a single screen you can drive. Off one, it writes the same plain lines it always did, so pipes and CI are unchanged. ``` swarm make the parser trim before it splits local:qwen3-coder:30b-a3b /Users/brad/projects/scratch-repo 20s step 4 5812 tokens attempt 1/3 plan read the failing test, fix the parser, run the gates actions edit path=src/parse.ts find=text.split replace=text.trim().split shell command=npm test shell failed: 1 failing ratchet accepted: tests collected 12 to 12, assertions 34 to 35, skips 0 to 0 gates attempt 1/3 PASS tests: 12 collected, 0 failed PASS lint: no findings in 208 files N/A coverage: no lcov artifact was written to the path the harness named WARN diff-budget (advisory): 1 file changed, 1 line added, budget 12 files and 400 lines DONE stopped: completed (4 steps, 5812 tokens) j scroll enter expand tab pane / filter e evidence ? help q detach ctrl+c cancel run ``` While it works there is a line that says so: a spinner that turns, what is happening, how long it has been happening, and, while the model is talking, the tail of what it is saying. ``` ⠙ thinking, step 2 5s I don't see calculator.js in the root directory ⠹ shell npm test 12s ``` One line, deliberately. The whole response lands in the action stream when it arrives and in the ledger for ever, and repeating it as it streams would be the same text three times. A tool that finishes inside a frame never draws one. `?` lists every key. `enter` expands a row to its whole payload and the ledger record it came from. `q` leaves the view: the screen comes down and the run keeps going, reporting the plain lines it writes off a terminal. `ctrl+c` cancels the run. There is no progress bar, because an agent run has no denominator. When the run ends, the screen lists what it produced, says how many claims the harness verified and how many it refused, and offers to open the review page. It says the bundle verified only if the bundle's own verifier ran here and exited 0. `swarm review ` shows the same panel for any bundle already on disk. The keymap, the `swarm.toml` surface, the degradation matrix, and a recording of a session are in [`interface.md`](docs/evidence/2026-08-23/interface.md), with the frames in [`interface-frames.txt`](docs/evidence/2026-08-23/interface-frames.txt) and a playable asciinema capture in [`interface.cast`](docs/evidence/2026-08-23/interface.cast). ## What is claimed Every line here links to a committed artifact of the thing happening. The full table, including what backs each one, is [`docs/claims.md`](docs/claims.md). **A green verdict is computed by the harness, and the model cannot produce one.** In a real run, the model asserted a predicate the language does not parse. The harness rendered it `UNVERIFIED (predicate-unparseable)` and carried on, twice, until the model wrote one that could be evaluated: [shakedown results](docs/evidence/2026-08-18/shakedown/results.md). **One changed byte breaks verification.** The same bundle, verified and then tampered with in a single byte of one record, exit 0 and exit 1 side by side, with a script to reproduce it: [tamper demo](docs/evidence/2026-08-18/tamper-demo). **The bundle carries its own verifier, and it works on a machine that has never seen this repo.** `node verify.mjs ` checks the manifest, the chain, the signature, every blob against its content address, and recomputes every claim verdict. Run in a `node:24` container with no network and no mount of this repository: exit 0 on the committed bundle, exit 1 on the same bundle one byte later, with the image digest and both transcripts in [`clean-container-verification.md`](docs/evidence/2026-08-23/clean-container-verification.md). Transcripts from two earlier runs outside the repository: [`live-tasks.md`](docs/evidence/2026-08-18/live-tasks.md). Beside the verifier, every bundle now carries `rederive.mjs`, which recomputes every gate status from the recorded exit code and output under the rule the record names, every ratchet decision from its recorded measures, every bond verdict and every claim, and names what it cannot re-derive rather than agreeing with it: [`gates-bonded/`](docs/evidence/2026-09-02/gates-bonded) is a run with nine gates sealed, four bonds held, and all seventeen verdicts re-derived. **Bundles are signed from the OS keychain**, not from a key in the workspace. Both manifests in [`live-tasks.md`](docs/evidence/2026-08-18/live-tasks.md) say `"keySource": "keychain"` and both verifiers confirm it. Where the keychain holds no usable key the run signs with a per-run key and says which of the three keychain failures happened, rather than signing quietly with something else; the manifest then records `keySource: ephemeral`, which is what the bundles in [`installed-package-run.md`](docs/evidence/2026-08-23/installed-package-run.md) carry. **Local model choice is measured on your machine.** The probe output and recommendation from real hardware: [`hardware-select.md`](docs/evidence/2026-08-18/hardware-select.md). A hundred and eighty calibration runs across three models, distributions rather than averages, with the pick ranked against the other two: [`calibration-report.md`](docs/evidence/2026-08-23/calibration-report.md). **Eight untrusted boundaries are fuzzed**, and the harnesses are checked against a defect injected on purpose so a clean run cannot be a blind one: [`fuzz/`](fuzz/README.md) and [`security-coverage.md`](docs/security-coverage.md). **The screen renders from ledger projections, and a keystroke has no route to a verdict.** Interactive state is a separate type with a separate reducer, and a test asserts across every action that none of its fields can be mistaken for a gate result: [`interface.md`](docs/evidence/2026-08-23/interface.md). **A choice between competing attempts is made from measured numbers.** One task tried three ways: three green attempts, coverage measured rather than abstained on, the third ranked last on the dimension that saw it wrote one test fewer, and the top two identical on everything so the tie broke on the earliest, which the report says in those words rather than inventing a reason it did not have: [`swarm.md`](docs/evidence/2026-08-24/swarm.md). **Adversarial passes, with each closure locked as a regression test.** Six pass directories under [`redteam/`](redteam), 49 cases in `src/evidence/redteam-adversarial.test.ts`, and an accounting record mapping each pass to what it actually was, because the driver ledger records one completed lap and a directory is not a lap. ## What is not claimed Kept short and kept honest, because the point of the rest of this file is that claims cost something. - **Not "fully secure".** The secret detector does known-pattern scrubbing, not secret removal, with a four-character floor. Zero crashes at a fuzz budget is evidence, not proof. - **No benchmark numbers.** Nothing here is measured against another tool. The calibration results are self-run, on one machine, and labelled directional in the file itself. - **Six known gaps ship open**, and none of them is claimed closed. They are in [build guide 7.1](docs/build-guide.md), which says for each what now catches it and what still gets past. Four of them have detections built against them and have not yet been attacked, so what is claimed is a detection and not a closure: a check nobody has tried to get past is a check nobody has measured. The other two came with the scale-out work and are unchanged, since a decomposition that is well formed can still be unrunnable and a comparator built from gate measures reads discipline rather than completeness. - **A signature does not make the machine honest.** It proves the bundle was not altered after it left the machine that produced it. The review page says that on its face. ## How it works - **Claims are predicates, not prose.** A claim names a record, a record kind, and a machine-checkable predicate. The harness recomputes the record's kind, evaluates the predicate, and decides. Model narrative always renders as unverified prose. - **The ledger is append-only and hash-chained.** Each record carries the previous record's hash. A failed write aborts the run. Nothing is updated or deleted, ever. - **Every tool call goes through one chokepoint** that records it, tags its provenance, and enforces the sandbox. Credential paths are denied by default and each denial is itself recorded. - **Gates are data.** A gate declares a command, a parser, and whether it blocks. The engine never special-cases one. - **A pass is bonded, and the criteria are sealed first.** Before the model is asked for anything, the gates it will be measured by, with their severities, the rule that reads each, the budgets and the attempt cap, are one record on the chain, and the verifier holds every gate run to it. After the fixed point, every gate that passed is handed one file it has to refuse: a test that throws, a TODO, a credential-shaped token, a file outside the declared set, a change over the budget. A check that refuses it held. A check that passes over a bond it demonstrably saw is vacuous, and a vacuous blocking gate makes the run not green. A pass nothing shows can fail is not a pass. - **The ratchet is numeric.** Tests collected, assertions in touched test files, coverage of changed lines, skip markers. The two the runner reports, tests collected and coverage of changed lines, come from reports the runner wrote where the harness told it to, never from what a gate printed: a test file that prints its own counter line is the code under measurement authoring the measurement. Where the harness cannot vouch for the run that would write those reports it asks for none, and an unobtainable measure is reported as "not measured" rather than as a pass. Assertions and skip markers need no runner: the harness counts them out of the text of every test file the run touched. The full design, including what it refuses to build and why, is in [`docs/build-guide.md`](docs/build-guide.md). ## Limits Gates prove mechanical quality. They do not prove design quality, and nothing here pretends a passing run means the change is good. What the bundle buys you is that reviewing the change is fast and that its claims are checkable, not that review is unnecessary. ## Upgrading from v12 v12 was a PR auditor that ran as a GitHub Action. v13 is a coding agent. Same package name, different product, no migration path. If you are using v12, stay on it: it is tagged `v12-final`. Details in [`CHANGELOG.md`](CHANGELOG.md). ## License ISC. See [`LICENSE`](LICENSE).