SwarmForge — pi-optimized, universal agent support
Fork of unclebob/swarm-forge (based on the four-pack
workflow) that runs the swarm with pi as the agent harness — while keeping
universal support for other agent CLIs, any model per role, and bash instead of zsh.
The four-pack pipeline is unchanged: specifier → coder → refactorer → architect with
human approval at the specifier gate.
Quickstart
# copy this fork's main branch into a project directory
curl -L "https://github.com/pablo-io/swarm-forge/archive/refs/heads/main.tar.gz" | tar -xz --strip-components=1
# edit swarmforge/swarmforge.conf (pick your models, see below), then start
SWARMFORGE_TERMINAL=none ./swarm
# observe/steer agents (headless Linux: attach to sessions manually)
tmux -S "$(cat .swarmforge/tmux-socket)" attach -t swarmforge-coder
# stop the swarm
./close-swarm
Using any model (per role)
Models are configuration, not code: the extra-args field of swarmforge.conf is passed
straight to the agent CLI. To pick a different model, change --model (pi) or the equivalent
flag of your backend:
# pi + models per role (one provider per model family)
window specifier pi master --model opencode-go/deepseek-v4-flash
window coder pi coder --model opencode-go/deepseek-v4-flash
window refactorer pi refactorer batch --model qwen-token-plan/qwen3.7-max
window architect pi architect batch --model qwen-token-plan/glm-5.2
# or a different backend entirely
window coder codex coder --yolo
window architect claude architect batch --dangerously-skip-permissions
- List what your harness offers:
pi --list-models/pi --list-models <provider>(runpi update --modelsafter catalog changes). - pi supports DeepSeek, Qwen (incl. GLM) and many providers natively — no custom provider extensions needed for those.
- Anything after the receive mode (
task/batch) is passed to the agent CLI:--model,--thinking,--yolo, etc.
Monitoring tools
The tools/ folder ships two small bash utilities (no dependencies) for operating a swarm:
tools/sf-queue [project-dir]— live handoff queue state for every role: pending (new), in-process (incl. batches), completed, outbox, sent, plus task names. Reads.swarmforge/roles.tsvand works with any agent backend.tools/sf-tokens [project-dir]— token consumption per role (input / output / cache / total) from pi's session files (~/.pi/agent/sessions, organized per worktree). Cumulative across restarts. pi-specific (only meaningful when pi is the backend).
bash tools/sf-queue /path/to/project # queue state per role
bash tools/sf-tokens /path/to/project # tokens per role (pi sessions)
Adding another agent backend
The launcher validates backends in parse-config and builds the launch command in
launch-command (swarmforge/scripts/swarmforge.bb). To add a CLI:
- Add the name to the allowed set in
parse-config:(when-not (#{"claude" "codex" "copilot" "grok" "pi" "your-agent"} agent) - Add a
casearm inlaunch-commandthat starts the CLI interactively with the initial prompt, e.g. for pi:"pi" (str "pi -a --name " (sq (str "SwarmForge " display)) " " (extra-args-prefix row) "\"$(cat " (sq (str prompt-file)) ")\"") - The binary must be on
PATH(the launcher checks it). The agent must be an interactive TUI/REPL that accepts typed input — wake-ups arrive as a typed message + Enter.
Tip: for many backends, a per-harness launch script (like terminal-adapters/) is cleaner than
adding case arms — the fork is happy to host one per agent.
Changes & improvements vs upstream
| # | Change | Why |
|---|---|---|
| 1 | pi as agent backend | parse-config accepts pi; launch arm pi -a --name 'SwarmForge <Role>' [extra] "$(cat prompt)". pi starts interactive, sends the initial prompt, stays in the TUI. |
| 2 | Bash-native, zero zsh dependency | All shell scripts migrated: shebangs zsh → bash, ${1:l} → tr, <-> glob → regex, &! → & disown (tmux pane shell is bash), zsh -c → bash -c in swarmforge.bb/swarm-window-watchdog.bb. Bash is the default on Linux and exists on every platform. |
| 3 | Universal wake-up (pi + others) | Upstream typed the wake-up text then a separate Enter; pi-tui drops a standalone CR, so messages sat in the editor. The fork embeds the CR in the same write (text\r) and keeps the trailing LF for other TUIs — validated in pi, compatible with claude/codex/grok. |
| 4 | Self-contained branch | swarmforge/scripts/ vendored (upstream ignores it and downloads from main); .gitignore adjusted; empty scripts/shared-articles/ stops the wrapper download. No surprise overwrites from upstream. |
| 5 | Shared constitution articles included | engineering.prompt, handoffs.prompt, workflow.prompt copied into constitution/articles/ (upstream four-pack only carries project.prompt, so agents had no handoff/workflow rules). |
| 6 | Git identity and worktree failure detection | Upstream fails silently when git commit cannot run (no user.name/user.email) → unborn HEAD → empty worktrees. Now: clear error with instructions before git init, and fail! if git worktree add fails. |
| 7 | Written review reports | The architect commits docs/reviews/<task>-summary.md at the end of every task/batch (what was reviewed, fixes, verification results, suite status, handoffs sent) — the review record is durable and versioned instead of living only in the agent's session. |
Where review reports are documented, and when
The architect writes the summary to docs/reviews/<task>-summary.md (one per task, created
under docs/reviews/ in the repo) at the end of each task or batch, committed with its byline
in the same commit as the review changes. Because it is a git commit, the report:
- travels with the branch and arrives on
masterwhen the specifier merges the architect's work, - is versioned — you can diff
docs/reviews/across runs to see every review checkpoint (the pipeline "cuts"), - replaces the informal closing message the architect used to leave only in its own TUI.
Example layout after a run:
docs/reviews/
user-login-summary.md
login-form-summary.md
Scope (verified)
Verified on Linux (Arch) with the real pi CLI and real provider credentials:
bb test— 24 tests, 91 assertions, 0 failures (no zsh required)- Launch command generation:
pi -a --name 'SwarmForge <Role>' --model <provider/model> "$(cat prompt)" - Swarm startup headless (
SWARMFORGE_TERMINAL=none): tmux sessions, real git worktrees withswarmforge-<role>branches, agents live with the correct model per role - Handoff protocol end-to-end: commit → draft → validation → outbox → daemon → inbox
(generated headers +
merge_and_processpayload) → tmux wake-up →ready_for_next.sh(task and batch semantics) - Autonomous chain: coder → refactorer → architect with automatic task pickup and forwarding
- Clean shutdown via
close-swarm - Git identity guard: clear failure with instructions when identity is missing
- Universal wake-up: auto-submit in pi with embedded CR; trailing LF retained for other TUIs
Not tested: macOS/Windows terminal adapters (bash-ported from upstream, not exercised on Linux), window watchdog with a trackable terminal backend (Linux headless mode has no windows by design).
Requirements
- bash (default shell; tmux panes run it). No zsh required.
- Linux: any modern distro (bash ≥ 4.4). Windows/WSL: fine.
- macOS: system bash is 3.2 —
close-swarmuses array append (sessions+=(...), bash ≥ 4); use a modern bash (Homebrew) or zsh on macOS.
gitwithuser.nameanduser.emailconfigured — enforced with a clear error at startup.tmux≥ 3.5 (recommended for pi extended keys).- Babashka (
bb) — single static binary. - One or more agent CLIs on
PATH(pi, codex, claude, …), authenticated for the providers you use.
Notes
- This branch is self-contained: it does not auto-follow upstream
mainscript updates (scripts are vendored). Rebase/merge manually if you want upstream changes. - Handoff semantics are unchanged from upstream: only
git_handoff/note, priorities 00–99, chain forwarding always, terminal broadcast merge-only. Seeswarmforge/handoff-protocol.md. - Upstream is zsh-based; if you pull upstream scripts, re-apply the bash migration
(2 syntax sites + shebangs +
zsh -c+&!).