Lab

Multi-agent swarm with no one in charge

Independent agent roles coordinate through mailboxes and a single governance rule. No agent or person decides who acts next. This page replays a real run and recomputes every decision from its audit log.

Jump to controls

No supervisor: the files decide who acts next

In the run replayed here, the roles built a six-milestone plan into another of my repos, with no supervisor assigning work. Each role is a fresh claude -p call that sees only the run objective, its own charter, the board and its own unread mail. Its answer must match a JSON schema. A small Python layer files the answer into folders and appends one line to the log. LangGraph then asks the router who can act, and starts all of them in parallel.

  • Accent box: the router, the only place that decides who acts. No model call.
  • Solid arrow: happens on every turn
  • Dashed arrow: optional or after the fact (a human objection, observability)

Inside the swarm

Routing is a pure function of the files. route() reads the proposal folders and the append-only log, promotes what has cleared its objection window, and returns one Send per role that has a reason to act. It makes no model call and takes no human input, so the same files always give the same answer.

One objection sends an integrated proposal back to open

A proposal integrates (is accepted and handed to engineer to build) once every other role has finished an ok turn since it was filed and no objection is on file. engineer, the one role with write access, is left out of that count. An objection against an integrated proposal moves it back to open. The page recomputes all of it at every step, from the log alone.

Excerpts from a real run, trimmed and lightly edited for length. The model turns are recorded. The governance is computed here.

–

Proposals, derived from the log

ProposalFiled byStateOwes a turnObjections

Eligible to act next

These are the two triggers in activatable() that come from proposals and the log. It also wakes any role with unread mail. Mail recipients aren't fully recoverable from this run's files, so mail is left out here.

Audit log

    A role sees its inbox and answers in a fixed schema

    Where the money and the waiting went

    Cost per role, USD

    RoleTurnsWith warningsErrorsCostPer turn

    Objection matrix

    Proposal lifecycle

    ProposalEnd stateTo integrateTo doneObjections

    How it works
    Pattern
    Holacracy-style roles, proposals and objections. No supervisor agent.
    Stack
    LangGraph StateGraph with Send fan-out, headless Claude Code CLI, JSON Schema, stdlib Python
    Data
    One real run, governance-pivot-build. The swarm built a six-milestone plan into another of my repos.
    Runs
    Plain JavaScript in this page. The governance rule is a port of the repo's Python.

    Method

    A run is a folder: mailboxes, tensions, proposals (open/, integrated/, done/, objections/) and an append-only log/events.ndjson. Roles come from a pack: a roster, one charter per role, and a config with tools, model and budget per role. A new use case needs a new pack and no framework code.

    The router imports its rules unchanged from scripts/next.py. sweep_proposals promotes an open proposal when it has no objection and every role other than its filer and the write role has an ok log entry with a later timestamp. activatable lists who has a reason to act: unread mail, an open proposal they haven't had a turn on yet, or (write role only) an integrated proposal to build. LangGraph sends every one of them a turn at once and loops until nobody is left.

    The comparison is strict string order on ISO timestamps, and only status: "ok" counts. A turn that finishes with warnings, or fails, does not clear anyone's window.

    Production setup

    • Each turn is claude -p with a per-role tool list, --max-budget-usd and --json-schema. Every role except research also runs under --restricted. The CLI returns the output already parsed, so nothing has to regex free text.
    • Permission scoping was tested against the CLI directly. Under --restricted, writes in headless mode were silently denied until --permission-mode acceptEdits was added. --restricted also blocks web tools even when named, so the research role runs with --allowedTools instead. --allowedTools alone did not restrict anything on that machine.
    • Every failed turn is logged, so the activation cap counts it. An earlier version only logged successes, and one run retried a rate-limited call thousands of times. A 429 now stops the run.
    • A human is just another objector. The dashboard's Reject button writes the same objection file a role would.

    Limits

    • Cost. Every turn is a full model call. Many turns end with "nothing to do", and they still cost money.
    • Latency. A proposal waits for every non-write role to finish a turn, including roles with nothing to say about it.
    • Races. Parallel turns of the same role don't see each other. In this run, parallel architect turns filed duplicate proposals for four of the six milestones, and architect then objected to its own duplicates. Several engineers built the same milestone at once.
    • Schema rigidity. The contract is fixed. A role can't express anything the schema has no field for, and an unknown id is dropped, not interpreted.

    About this page

    The log lines, costs and full turn are copied from the run folder. Run over the full log, the JavaScript port of sweep_proposals and activatable ends with every proposal in the same folder the real run left it in. Injected objections change what the rule computes, not what the agents said.