For engineers

How to run a swarm.

A task, a profile, a budget, your tools. This page goes from install to a served record with people and agents holding seats, and ends with a worked setup for each of the five profiles.

Install

HypSwarm needs HypGraph's coordination layer and workflows, which ship with hypgraph 0.7.2:

python -m venv .venv && . .venv/bin/activate        # .venv\Scripts\activate on Windows
pip install --pre hypswarm                                      # pulls hypgraph>=0.7.2; extras: anthropic · bedrock · langchain · pydantic-ai · crewai · otel
hypswarm profiles                                               # the five rosters and their workflows

Any OpenAI-compatible endpoint is the model (OPENAI_BASE_URL, OPENAI_API_KEY); hypswarm[anthropic] adds the Messages API. Nothing in the box executes code or reaches a network on its own — those are tools you register (below).

Write the task

task.toml
title   = "Does the importer handle quoted commas"
mission = "The CSV importer in src/importer.py mis-parses quoted commas (issue #412). Fix it and prove it."
profile = "software"
files   = ["src/importer.py", "tests/test_importer.py", "fixtures/quoted.csv"]
success = "Every requirement verified by a test the instrument ran."

[[questions]]
code = "Q1"
text = "Does the importer parse fixtures/quoted.csv into 3 rows with the comma kept inside the quoted field?"

[[questions]]
code = "Q2"
text = "Do all existing importer tests still pass after the fix?"

Questions are what the run must settle; keep them checkable (a number, a yes/no, a choice). Files are registered as artifacts by hash and reach every worker's folder; the record's read_file only trusts bytes that match.

Pick a profile

The profile is the record's rule-set for the discipline, pinned on the investigation and versioned. It brings a roster, a paragraph of rules every seat reads, the discipline's workflow — the stages a question passes through and the gate out of each — and what "complete" means. A name (software) is its newest version, software@2; software@1 pins the pre-workflow one.

profilerosterthe stages a question passes throughthe record refuses
scientificanalyst, stats, critic, leadconjectured → preregistered → run → evidenced → claimed → reviewedunbound numbers; "never" without the count; "significant" without the test; a check on stale evidence
softwareimplementer, tester, reviewer, leadimplemented (a diff by hash) → verified (tests_pass_current: the instrument, on that head) → claimed → reviewed"passes" without an instrument run, or on a superseded head; the diff's author reviewing
tradingquant, risk, leadthesised → preregistered → backtested (backtested: out of sample, after costs, trials) → claimed → risk-reviewed → positioned → monitoredcostless backtests; in-sample "edge", or without trials; a position without a stop; any live order
knowledgeresearcher, fact_checker, leadsourced (the passage, retrieved, kind) → claimed (sourced) → checkedclaims from recall; a source without its passage
optimizationmodeller, solver, verifier, leadposed (the instance by hash) → modelled → configured → solved → verified → claimed → checkedobjectives without feasibility; a solve that uses no instance; "optimal" without a bound, or on a changed instance

The scheduler issues each question's stage order. It asks the record where every question stands (stages()) and issues the gates up to the next review as one order's done-when, to the seat the stage names; a review stage goes to a reviewer on the node the previous gate produced. The run ends settled when the record says every question is complete — a checked inconclusive answer, a declined thesis (no_position) or a verified failure count as complete too. Override the roster with your own seats.toml whenever you like; the profile's rules still apply.

seats.toml (optional)
[[seats]]
name = "implementer"
role = "worker"
model = "deepseek-ai/DeepSeek-V4.1-Flash"
escalate_to = "deepseek-ai/DeepSeek-V4-Pro"        # used from the first NOT MET on this seat's work
takes = ["implement", "fix", "importer", "parse"]

[[seats]]
name = "tester"
role = "worker"
takes = ["test", "verify", "regression"]

[[seats]]
name = "reviewer"
role = "reviewer"

[[seats]]
name = "lead"
role = "lead"
model = "deepseek-ai/DeepSeek-V4-Pro"

Register your tools

Tools are named bundles from a registry. hypswarm ships files; a profile asks for it by default. A sandbox that runs code is yours to provide — here is the shape the harmony study uses, as a subprocess in the working folder, as an unprivileged user, with a kill that really kills:

tools.py
import subprocess, sys
from hypswarm import toolkits
from hypswarm.messages import Tool

def make_python(ctx):                       # ctx: record, inv, folder
    def python(code: str) -> str:
        """Run python 3 in your working folder (the data files are there). Print what you need; 90 s limit."""
        p = subprocess.run(["timeout", "-s", "KILL", "90", sys.executable, "-I", "-c", code],
                           cwd=ctx.folder, capture_output=True, text=True, timeout=100,
                           env={"PATH": "/usr/bin:/bin", "HOME": str(ctx.folder)})
        return (p.stdout + ("\n[stderr]\n" + p.stderr if p.stderr.strip() else ""))[:8000] or "(no output)"
    return [Tool.from_function(python)]

toolkits.register("python", make_python, "python in the run's working folder, 90 s, unprivileged")

A bundle a profile asks for that you haven't registered fails loudly at run() — the swarm never runs without a tool it was promised. Seats can be restricted to named tools (tools = ["files"]).

Run

shell
export OPENAI_BASE_URL=https://api.deepinfra.com/v1/openai OPENAI_API_KEY=…
hypswarm run --task task.toml --profile software --record issue-412.hypgraph --folder work/ \
             --model deepseek-ai/DeepSeek-V4.1-Flash --budget-calls 150 --budget-usd 1.00 --pricing 0.2,0.006,0.6
python
import asyncio, tools                                  # tools.py registers "python"
from hypswarm import Task, Budget, run
from hypswarm.providers import OpenAICompatible

task = Task(title="Quoted commas", mission=open("mission.md").read(), profile="software",
            questions=[("Q1", "Does the importer parse fixtures/quoted.csv into 3 rows …?"),
                       ("Q2", "Do all existing importer tests still pass?")],
            files={p: open(p, "rb").read() for p in ("src/importer.py", "tests/test_importer.py", "fixtures/quoted.csv")})
model = OpenAICompatible("deepseek-ai/DeepSeek-V4.1-Flash", base_url="https://api.deepinfra.com/v1/openai")
res = asyncio.run(run(task, None, model, record="issue-412.hypgraph", folder="work/",
                      toolkits=["files", "python"], reproducer=tools.reproducer,
                      budget=Budget(max_calls=150, max_usd=1.0, per_order_calls=40)))
print(res.stopped, res.usage, res.answers())
print(res.report)

The budget is a rule: calls and dollars per run and per order, counted including calls in flight, shown to every seat in every reply. The run ends settled (every question settled and the profile's completion criteria met), budget, or stuck (nothing left to issue; the report says what's open).

The instrument: reproduce, don't recompute

Pass a reproducer(source) -> stdout — usually the same sandbox your python tool uses. Every result an author registers with its code is re-run once, before any review, and the record files a kind=lab run that replicates the author's run: values confirmed, not printed, or contradicted. The review order says which, and tells the reviewer to recompute only a contradiction. In the software profile this is also what tests_pass means: the instrument's run, not the tester's word.

def reproducer(source: str) -> str:            # same sandbox, same folder, same user, same limits
    return make_python(ctx)[0].fn(source)

A served record

Workers don't have to share a process. Serve the record, bind writes to tokens, and point runs at it. The swarm's token covers the harness and its seats (one client writes as each); an outside seat's token covers its own name only. The harness name is reserved — no token's wildcard includes it.

hypgraph --db issue-412.hypgraph serve --writable --no-browser \
  --token "$SWARM_TOKEN=harness,implementer,tester,reviewer,lead" \
  --token "$NIC_TOKEN=nicolas"
hypswarm run --task task.toml --profile software --record-url http://127.0.0.1:8765      # HYPSWARM_TOKEN or HttpRecord(token=)

Keep the server on localhost or behind your own TLS/reverse proxy; reads are open (the HypGraph UI needs them), writes are 401 without a known token and 403 for an author the token isn't bound to.

People and outside agents as seats

Add a seat with external = true. The scheduler issues it orders like any other and waits (up to the lease); whoever holds the name works the order over MCP and returns it. If the lease runs out the record returns the order on their behalf and the scheduler decides, exactly as for a worker that stopped.

[[seats]]
name = "nicolas"
role = "reviewer"
external = true
# nicolas, elsewhere:  hypgraph --db issue-412.hypgraph --as nicolas mcp  (or --record-url … --token "$NIC_TOKEN")

Send them to the agents page: it is the whole job from their side.

Resume, escalation, notices

  • Resume. A run that died left open orders and leases in the record. run(..., resume=inv) adopts them: open orders are re-dispatched to their seats, rework chains are read back from the record, an order whose seat is gone is cancelled with the reason, and a new run node records that it resumed.
  • Escalation. escalate_to on a seat names a deeper model, used from the first time the record sends that seat's work back NOT MET. The cheap model gets the first attempt; the record's verdict — not the model's confidence — moves it up.
  • Notices. The world can interrupt: scheduler.notify("site_b.csv was exported in µg/L", stale=["site_b.csv"], add_files={"site_b_fixed.csv": data}) withdraws the artifact with the reason, registers the fix, reaches every order issued from then on, and cancels exactly the open orders that rested on it. Workers' read_file refuses the stale bytes.

Read the result

  • res.report — the facts generated from the record, the lead's interpretation under them, the answers block; res.answers() parses it; res.scheduler.events is the timeline (dispatch, returned, reproduced, settled, rework, escalate, notice, report_missing with the refusal texts).
  • hypgraph --db issue-412.hypgraph serve — the HypGraph UI: the graph, claims with their standing, provenance, the work orders, overclaims refused, and the run replayed live in Live. hypgraph brief compiles the orchestrator's view; hypgraph orders lists orders; hypgraph check report.md lints a write-up against the record.
  • GET /api/graph/stages?inv=… — each question's stage and every gate's status; GET /api/graph/completion?inv=… — the profile's criteria and whether each is met; the record verifies by hash (hypgraph verify). res.report carries the workflow's section: the stage per question and the evidence that has gone stale.

Worked setups, one per profile

Software — CI as the instrument

profile software  tools files, python (pytest in a checkout)

Mission: fix an issue and prove it. Questions are requirements. The first stage order asks the implementer for the change as an artifact kind=diff (base and head sha, files); the tester registers pytest commands as code with uses=[the diff]; the reproducer runs them in a clean checkout and the lab run carries the same uses; tests_pass_current is met only by that run, on that head. A later commit supersedes the diff and the gate reopens. Completion: every requirement's claim rests on a passing instrument run on the current head — or is a verified failure the record holds. Hook the reproducer to your CI runner and the record holds the same evidence your pipeline does — by hash.

Trading — a paper desk

profile trading  tools files, python (pandas), a read-only market-data tool you register

Mission: does a thesis have an edge after costs? The stage orders ask the quant for the thesis as a hypothesis, the backtest preregistered as an experiment (windows, costs, configurations), then the result with out-of-sample return, costs and trials; the record refuses a costless result and an in-sample "edge"; the risk seat reviews against the code; then a decision based_on the claim — kind=position with size, stop, review date, paper, or kind=no_position. Completion: questions settled, a position taken or declined, nothing past its review date. There is no order-placement tool to register, and the profile's notes say so to every seat; keep it that way.

Knowledge — a sourced brief

profile knowledge  tools files, a fetch tool you register (allowlisted hosts, read-only)

Mission: answer questions from sources. Researchers add source nodes part_of the question (citation, URL/DOI, the passage, retrieved, kind) and claims that cite them; the fact-checker opens the sources; the report is linted so no sentence treats an unreviewed claim as settled. A question no source answers ends as a checked inconclusive claim derived from the search's own result. Completion: every claim cites a source and is checked. Point the fetch tool at the sources you trust; the record keeps what was actually read.

Optimization — a solve with a bound

profile optimization  tools files, python (OR-Tools / PuLP / scipy)

Mission: minimise cost under constraints. The instance is registered as an artifact kind=instance; the modeller writes the model as a hypothesis and the solve configuration as an experiment; the solver's run uses the instance and its values carry objective, feasible, gap and runtime; the verifier reviews the result, recomputing feasibility, before the claim; "optimal" is accepted only with a bound that meets the objective, and never on an instance that has since changed. Completion: a feasible incumbent with a bound, verified.

Scientific — an eval report that holds up

profile scientific  tools files, python

Mission: does model A beat model B on an item set? The stage order asks for a hypothesis, an experiment with its expected outcome before the run, then the run; analysts register counts; the record runs the test on bound counts before it lets "significantly" through; the critic settles each question; the lead writes under the facts. This is the setup the harmony study uses, and the one every scheduler path is tested on.

Operating notes

  • Per-order budget. per_order_calls (default 40) stops one order eating the run; the lead's exception turns are their own unit.
  • Lease. lease_minutes (default 240) is how long a seat holds a target before the record returns the order on its behalf.
  • Providers that stall. Keep per-call timeouts short on the provider and let the scheduler's returns do the rest; a worker that stops without returning is returned by the record.
  • Prompt prefixes. Task(header="[run ]") puts one line first in every system prompt, so no two runs share a cached prefix with a provider.
  • Capture. OpenAICompatible(on_request=…) sees every request body before it is sent — for replaying a refusal or a hang exactly.
  • Tests. pytest runs every scheduler path on scripted models in seconds; bench/stub_bench.py reports calls, rounds and wall time per run so a change is measured before it is described.