Skip to content

DevSwarm

talas9 edited this page Oct 9, 2026 · 1 revision

DevSwarm

anti-hall integrates with DevSwarm, a desktop multi-workspace IDE in which each workspace is a git worktree on its own branch with its own AI agent. The app ships a CLI, hivecontrol, that is on every workspace's PATH. anti-hall adds a message mesh, a roster, a background supervisor and an ingest daemon on top of it.

The integration is dormant unless the session runs inside DevSwarm (DEVSWARM_REPO_ID is set) or a DevSwarm companion is installed. Kill switch: DISABLE_ANTIHALL_DEVSWARM=1.

Sources of truth: docs/KB-devswarm-hivecontrol.md, docs/KB-devswarm-app-db.md, the devswarm skill, and the "DevSwarm" section of docs/GUIDE.md.

Roles

  • Primary: the workspace on the main checkout. DevSwarm creates it on import; it cannot be archived or deleted. It plans, fans work out to children and verifies their results.
  • Child: a workspace the Primary spawned, with its own worktree and branch. A child uses subagents for its own parallel work and never spawns workspaces of its own (two tiers only).
  • Parentage comes from the workspace's source branch (DEVSWARM_SOURCE_BRANCH); empty means root/Primary.

Mesh

The mesh is one message store per project, shared by every worktree of the repository. It is the only channel anti-hall allows between workspaces, and it is all-to-all, not only parent to child.

  • Project key. repoKey is the sanitized repository name plus the first six hex characters of a sha256 of the git common dir (companion/lib/devswarm-repokey.js). There is one canonical resolver; no key means the mesh stays dormant.
  • Storage. ~/.anti-hall/devswarm/store/<repoKey>/devswarm.db (SQLite through node:sqlite, with an append-only NDJSON journal as the fallback backend). Tables: messages (append-only, deduplicated by content hash), registry, cursors, gates. Hooks never open the database; they read a projection, summaries/<repoKey>.json.
  • Other state under ~/.anti-hall/devswarm/: workspace descriptors, heartbeats, liveness verdicts, cursors, locks, a delivery write-ahead log (wal/), read receipts and archives.
  • Messages have an urgency (low, normal, high, urgent); broadcasts go to a shared broadcast partition.
  • Read is split from acknowledge. Reading an inbox is read-only and returns the command that acknowledges it; only that command moves the cursor.
  • Guards keep everyone on the mesh. The app's native message verbs and SendMessage to a workspace peer are blocked, and only a workspace's main thread may own its mailbox; a subagent that tries is blocked.
  • CLI. plugins/anti-hall/scripts/devswarm.js (a dispatcher; the verbs live in scripts/devswarm-lib/). Every verb prints one JSON line and exits 0 (ok) or 2 (not ok). Main verbs: send, mesh read|history, inbox …, roster, heartbeat, register, gate, done, archive, diagnose, reconcile.
  • Invariants. tests/mesh-invariants.harness.test.js sweeps seeded operations through the real CLI with an isolated HOME. It checks, among others, that the cached summary equals a fresh recompute, that cursors never move backwards, that delivered plus unread equals total for every reader (no loss, including after an injected crash), and that doctor --check writes nothing.

Roster

devswarm.js roster is a view of the project's registry table: one row per workspace with its mesh address, what it is working on (its latest heartbeat), its unread counts and the highest unread urgency, plus the latest broadcasts. Plain mode shows live workspaces and a +N archived line; --all adds archived rows and --json gives everything. Titles and sidebar order come from the app database, which anti-hall reads read-only (never message bodies or credentials; tests/companion/devswarm-app-db-hygiene.test.js).

The plain roster writes nothing. The registry is written by register, ensure (run on each inbox pull), register-primary, spawn, the self-heal passes and archive (which tombstones a row; it never deletes it).

Supervisor

companion/devswarm-supervisor.js is a one-shot sweep, run on an interval (default 90 s) by a launchd agent on macOS or a systemd user timer (or cron) on Linux. It is opt-in: companion/install-devswarm-supervisor.js installs it, and the update flow re-runs the installer when it finds an active DevSwarm session. A lock stops two sweeps overlapping.

One sweep: liveness, reconcile, deferred work, app-database sync, auto-archive, retention and housekeeping.

Recovery is layered, and the automatic path never kills anything:

  1. A child reports on itself.
  2. A stale child (idle transcript and idle git and unread mail past a threshold) gets a nudge.
  3. After the allowed nudges, the verdict is escalated and the Primary is told.

The only kill path is the on-demand companion/devswarm-recover.js CLI.

Auto-archive archives a child only when every condition holds: it is done (all finish gates set), its merge is proven against the remote default branch, its worktree is clean, it has no unread direct mail, it is not the Primary, it has not been focused in the app recently, and it has been idle long enough.

No automated deletion. tests/hygiene/devswarm-lifecycle-no-auto-delete.test.js pins that the prune and delete paths are reachable only from the interactive CLI verb, and that no hook, supervisor, ingest, installer, monitor or statusline file references them. Deleting archived workspaces takes a dry run, then explicit --confirm-ids with a short-lived plan nonce, and it refuses any non-interactive caller.

Wake paths for the Primary. A session cron (devswarm.wakeCron, default 7,37 * * * *) runs an inbox tick, and the devswarm-wake-watch monitor watches for new mail. Each covers a gap in the other: a session cron is gone after a harness restart, and the monitor exits on purpose when no child is live. A Primary with a live child and a missing wake path gets a NO MAILBOX WAKE PATH line, and one capped Stop block when both are missing.

After an app crash DevSwarm can restore workspaces paused. The Node supervisor only records evidence (devswarm.pausedProbeMax probes per sweep); the engine reports such a workspace as paused? with its evidence and never as paused, until a confirmed crash fixture exists (devswarm_rt.paused_signal, default none).

Ingest

companion/devswarm-ingest.js drains the app's native message queue (hivecontrol workspace monitor) into the mesh store, so a message sent with the app's own tools still reaches the mesh. It does not ingest transcripts.

  • One consumer per project, enforced with an exclusive lock.
  • Raw output is fsynced to a write-ahead log before parsing, and open batches are replayed on start; if the log cannot be written, it fails closed.
  • Writes are deduplicated by content hash, so replays are safe.
  • Installed per project by companion/install-devswarm-ingest.js (launchd with KeepAlive on macOS, systemd with restart or cron on Linux).

ANTIHALL_INGEST_DRY_RUN=1 makes the repair and fold functions in scripts/devswarm-lib/repair.js report without writing; tests and experiments that use an isolated HOME must set it, because a launchd agent and the real store are not isolated by HOME.

Node and engine

Part Node (plugins/anti-hall/) Engine (engine-proto, ah-engine/src/)
Hooks hooks/devswarm-*.js scripted checks (engine/logic/)
Mesh reads scripts/devswarm.js mesh.rs: read-only, byte-for-byte parity with Node
Mesh writes scripts/devswarm-lib/ meshw/: behind mesh.engine_writes (off, shadow, on); defers to Node (exit 75) when it cannot reproduce Node
Supervisor companion/devswarm-supervisor.js dssup/: devswarm_sup.mode (witness by default, Node does the work) with a per-duty rollback list; never produces a stale verdict itself, and recover is never scheduled
Ingest companion/devswarm-ingest.js dssup/ingest/: devswarm_ingest.mode (witness by default); waits on a live Node lock holder so nothing is drained twice
Runtime state devswarm_rt/, with Node as a witness

The engine's DevSwarm defaults are in plugins/anti-hall/engine/defaults.pristine/devswarm_*.toml and mesh*.toml. Recovery and migration stay Node tools so they work with the engine down.

Settings

All in ~/.anti-hall/settings.json, changed through /anti-hall:settings. Main groups: devswarm.supervisorMode; devswarm.autoArchive.* (mode, idle minutes, per-sweep cap); devswarm.nudge*, devswarm.idleSec, devswarm.maxRecoveries; devswarm.retention.*; devswarm.wakeCron, devswarm.wakeWatch; and the hook switches devswarm.parentGate, devswarm.childGate, devswarm.childRole, devswarm.commsGuard, devswarm.inboxReadGuard. The devswarm skill has the full list with defaults.

Home

🗺️ How it works

🛠️ How we work

📄 Templates

🔗 Elsewhere

Clone this wiki locally