A single-binary agent orchestration platform that turns swarms of AI agents into reliable, professional software engineers.
SwarmForge coordinates several AI coding-agent CLIs (claude, codex, copilot, grok) working in parallel on the same project: each configured role gets its own git worktree and its own real tmux window, and roles hand off work to each other through a durable, file-based message queue.
This is a from-scratch Go rewrite of the original Babashka/shell implementation. There's no more ./swarm curl+tar bootstrap and no OS-specific terminal-emulator adapters — one swarmforge binary does everything, and each project gets its own isolated tmux session (its own socket, so it never collides with your own tmux) with one window per configured role.
gittmux>= 3.0- Go 1.24+ (only to build; the built binary has no further runtime dependency beyond
tmuxand the agent CLIs below) - At least one configured agent backend:
claude,codex,copilot, orgrok
go install github.com/TorratDev/swarm-forge-go/cmd/swarmforge@mainor build from a checkout:
go build -o swarmforge ./cmd/swarmforgeor download a prebuilt binary from the Releases page.
Scaffold a new project from one of the built-in packs (see Packs below):
swarmforge init --pack two-pack path/to/project
cd path/to/project
swarmforge upinit writes swarmforge.yaml plus swarmforge/roles/*.prompt and swarmforge/constitution/ into the target directory — plain files you can edit afterward, generated once and never overwritten. up then validates that config, creates a git worktree per role, pre-accepts Claude Code's workspace-trust dialog for claude roles, creates the project's tmux session with one window per role running that role's agent CLI directly, and attaches your terminal to it.
To stop a swarm from another terminal:
swarmforge down path/to/project # defaults to the current directoryThis is a real tmux session, so native tmux keys work as usual: prefix+w lists/switches windows, prefix+<n> jumps to window n, prefix+d detaches.
Detaching does not stop the swarm — the handoff daemon runs inside the swarmforge up process itself, so that process stays alive (and keeps delivering handoffs) for the swarm's whole lifetime, whether or not anyone is attached to look at it. After detaching:
swarmforge attach path/to/project # reattach; defaults to the current directoryOnly swarmforge down, a signal to the swarmforge up process, or the first role listed in swarmforge.yaml (the "cleanup role") exiting on its own tears down the entire swarm.
A pack is a declarative role topology, embedded in the swarmforge binary and materialized by init. Three ship today:
| Pack | Roles | Flow |
|---|---|---|
two-pack |
coder, cleaner |
coder → cleaner → coder — a quick implement/refine loop, no specification or QA overhead |
four-pack |
specifier, coder, refactorer, architect |
specifier → coder → refactorer → architect → specifier — Gherkin specification plus one architecture/hardening pass |
six-pack |
specifier, coder, cleaner, architect, hardender, QA |
specifier → coder → cleaner → architect → hardender → QA → completion — every quality gate as its own role |
List and lint the embedded packs:
swarmforge pack list
swarmforge pack lint # lints every pack
swarmforge pack lint six-pack # lints just oneinit generates this from a pack; up reads whatever is on disk, so hand edits stick. It's the data-driven replacement for the old window <role> <agent> <worktree> [mode] [args] config-file DSL:
name: two-pack
roles:
- name: coder
agent: claude
worktree: master
receive_mode: task
extra_args: ["--model", "haiku"]
- name: cleaner
agent: claude
worktree: cleaner
receive_mode: batch
extra_args: ["--model", "sonnet"]namemaps toswarmforge/roles/<name>.prompt; must be unique and must not contain_.agentis one ofclaude,codex,copilot,grok.worktreeismaster(ornone) to run in the main working directory, or any other unique name to get.worktrees/<name>on branchswarmforge-<name>.receive_modeistask(default) orbatch.batchroles consume every currently queued equal-priority handoff as one batch instead of one task at a time.extra_argsare passed straight through to the agent CLI's argv.- The first role in the list is the cleanup role (see Detaching and reattaching).
SwarmForge auto-injects a permission-mode flag so an unattended agent doesn't stall waiting for approval. By default it injects --permission-mode acceptEdits (auto-approves file edits, still stops on Bash/tool calls). To let a role auto-approve everything, add --yolo, --always-approve, or --permission-mode bypassPermissions to that role's extra_args:
- name: coder
agent: claude
worktree: coder
extra_args: ["--yolo"]bypassPermissions is opt-in per role — never the default.
Agents don't message each other directly. Each role's worktree gets a .swarmforge/handoffs/ directory (outbox, sent, failed, inbox/{new,in_process,completed}), and while a swarm is running, a delivery goroutine inside swarmforge up polls every role's outbox, copies validated handoffs into each recipient's inbox, and wakes the recipient with a tmux send-keys message into its window.
Agents interact with this queue through three commands (also installed on PATH under their original script names — swarm_handoff.sh, ready_for_next.sh, done_with_current.sh — so existing role prompts work unmodified):
swarm_handoff.sh <draft-file>validates a draft and queues it into the sender's outbox.ready_for_next.shaccepts the next task or batch, per the role's configured receive mode.done_with_current.shcompletes the current task or batch, then immediately reports the next one.
A draft is headers only; the command generates the delivered payload. Two message types:
type: git_handoff
to: <role>[,<role>...]
priority: NN
task: <short-stable-task-name>
commit: <10-character-commit-abbrev>
type: note
to: <role>[,<role>...]
priority: NN
message: <one line, max 80 chars>
commit must resolve to exactly one real commit object; swarm_handoff.sh canonicalizes it before queuing. priority is two digits, lower delivered first; handoff filenames (<priority>_<timestamp>_<sequence>_from_<sender>_to_<recipients>.handoff) are constructed to sort lexicographically in exactly that delivery order.
Recipients run ready_for_next.sh when notified or on restart. NO_TASK means stop waiting; TASK: <path> means treat the printed PAYLOAD as the task; BATCH: <path> means process each printed BATCH_ITEM in order. done_with_current.sh after finishing immediately reports the next task/batch the same way. Agents should never hand-edit, merge, stage, or commit anything under .swarmforge/.
swarmforge/
roles/
<role>.prompt
constitution.prompt
constitution/
articles/
engineering.prompt # shared base article
handoffs.prompt # shared base article
workflow.prompt # shared base article
project.prompt # pack-specific overlay
...
constitution.prompt tells every agent to read every file under constitution/articles/. engineering.prompt, handoffs.prompt, and workflow.prompt are shared base articles every pack includes by default (swarmforge pack lint fails a pack that drops handoffs without an explicit replacement). Packs add their own overlay files — project.prompt describes the pack's role topology; six-pack additionally ships local-engineering.prompt and local-workflow.prompt as small additive specializations that sit alongside the base articles rather than replacing them.
go build ./...
go test ./...
go test -race ./...internal/tmux and the tmux-backed internal/orchestrator tests skip themselves if tmux isn't on PATH.
Package layout:
cmd/swarmforge/ entry point; argv0 dispatch for the legacy script names
internal/
cli/ command handlers (init, up, down, attach, handoff, ready, done, pack)
config/ swarmforge.yaml schema + validation
state/ .swarmforge/state.json + project-root discovery
handoff/ header parse/serialize, filenames, draft validation, queue state machine
daemon/ outbox -> inbox delivery poll loop
gitutil/ git plumbing: worktrees, commit canonicalization
trust/ ~/.claude.json trust-dialog patcher
launch/ per-backend argv builders
tmux/ tmux(1) session/window lifecycle, send-keys, exit polling
orchestrator/ startup sequencing, live swarm (launch/daemon/teardown)
pack/ pack schema, embedded pack definitions, generator