How Handoff Message Transport Works in Swarm Forge: File-Based Role-to-Role Communication
Swarm Forge moves work between roles by exchanging handoff files—tiny text-based messages stored under the project's .swarmforge/handoffs directory and processed by a dedicated daemon.
The handoff message transport is the backbone of Swarm Forge's collaborative workflow, enabling language-agnostic communication between AI agents playing different roles. This article examines the transport pipeline from message creation through final delivery, based on the implementation in unclebob/swarm-forge.
Handoff File Format and Structure
Handoff messages follow a simple header → body format defined in the test suite and used by production scripts.
Header Fields
A handoff header contains structured metadata that routes and identifies each message:
id– Unique message identifierfrom– Originating roleto– Destination roletype– Message category (e.g.,git_handoff)task– Associated task namecommit– Git SHA for version trackingpriority– Numeric priority for queue orderingtask– Associated task identifier- Timestamps for auditing
The hand-off function in test/swarmforge/handoff_test.clj (lines 81-99) demonstrates this format structure.
Body Content
The body holds an optional free-form payload—typically task instructions, code snippets, or completion notes. This design keeps the transport layer agnostic about payload contents.
Creating and Queueing Handoffs
Role-specific scripts generate handoffs using helper functions from handoff_lib.bb.
Building the Handoff File
;; Build a handoff map
(def attrs {:id "123"
:from "coder"
:to "cleaner"
:priority "50"
:type "git_handoff"
:task "my-app"
:commit (head-sha root)})
;; Write it to the recipient's inbox
(put-handoff! root "new" "50_from_coder_to_cleaner.handoff" attrs)
The queue-handoff! function (lines 124-132 of handoff_test.clj) creates filenames encoding priority, sender, and recipient in the pattern {priority}_from_{sender}_to_{receiver}.handoff.
Outbox Directory Structure
Queued handoffs land in .swarmforge/handoffs/outbox where the handoff daemon polls for new work.
The Handoff Daemon: Delivery Pipeline
The handoffd.bb script implements core transport logic in three phases: scanning, routing, and delivery.
Scanning and Parsing
The daemon runs continuously (or once with --once) to process outbox files:
bb handoffd.bb --once /path/to/project
The parse-message function (lines 77-86) reads .handoff files and extracts header fields into a structured map.
Determining Recipients
The recipient-list function calculates target roles based on message routing rules. For each recipient, target-path (lines 9-12) constructs the destination inbox path.
Collision-Safe Delivery
The move-with-collision function (lines 26-34) prevents data loss:
- Checks if filename already exists in target inbox
- Appends timestamp suffix when collision detected
- Guaranteed atomic move operation
This ensures in-flight handoffs are never overwritten, even during high-volume exchanges.
Tmux Session Notification
After successful delivery, notify! (lines 13-18) alerts the receiving role:
(let [socket (fs/path state-dir "tmux-socket")
session (str "coder")
msg "You have new handoff mail. If idle, run ready_for_next.sh."]
(sh "tmux" "-S" socket "send-keys" "-t" session "-l" msg)
(sh "tmux" "-S" socket "send-keys" "-t" session "C-m")
(sh "tmux" "-S" socket "send-keys" "-t" session "C-j"))
This wake-up mechanism eliminates polling overhead—agents receive immediate notification when work arrives.
Handoff Consumption and Board Updates
Receiving agents process delivered handoffs through inbox state transitions.
Inbox State Folders
Each role maintains isolated subdirectories:
new/– Unprocessed handoffs awaiting pickupin_process/– Currently active handoffcompleted/– Finished work archive
Processing Functions
The daemon invokes board management functions after delivery confirmation:
pack-board!(lines 52-58) – Consolidates board statearchive-sender!(lines 59-64) – Records sender information for audit trail
The agent removes processed files from new/ after handling, maintaining clean state separation.
Error Handling and Observability
The transport includes comprehensive failure management:
| Mechanism | Purpose | Implementation |
|---|---|---|
handoffd.log |
Delivery audit trail | Written after each operation |
failed/ directory |
Quarantine for undeliverable messages | fail! function routing |
| Collision avoidance | Prevent overwrites | move-with-collision timestamp suffixes |
Key Design Decisions in Swarm Forge Handoff Transport
File-based language agnosticism – Any process can participate by writing plain text; no shared library dependencies required.
Directory-per-role isolation – State is explicit in filesystem layout, making debugging and inspection straightforward.
Tmux integration for interactive agents – The notification system bridges file transport with terminal-based workflows without requiring custom protocols.
Immutable handoff files – Once written, handoffs are never modified; state changes through directory moves only.
Summary
- Handoff message transport in Swarm Forge uses text-based
.handofffiles exchanged through a centralized daemon - The three-phase pipeline creates messages in
outbox/, delivers viahandoffd.bbto roleinbox/new/folders, and consumes through board update functions move-with-collisionguarantees safe delivery without data lossnotify!triggers tmux-based agent wake-up, eliminating polling- State isolation through per-role subdirectories (
new/,in_process/,completed/) enables clear reasoning about message lifecycle
Frequently Asked Questions
What file format does Swarm Forge use for handoff messages?
Handoffs use plain text with a structured header containing routing metadata (id, from, to, type, priority, etc.) followed by an optional free-form body. The format is defined in test/swarmforge/handoff_test.clj and processed by parse-message in handoffd.bb.
How does the handoff daemon prevent message loss?
The move-with-collision function (lines 26-34 of handoffd.bb) detects filename collisions and appends timestamps before delivery. This atomic move operation ensures existing handoffs are never overwritten, even when multiple messages target the same role simultaneously.
Can I run the handoff daemon without continuous polling?
Yes. Execute bb handoffd.bb --once /path/to/project to process the outbox once and exit—useful for CI pipelines or manual debugging sessions where persistent daemon processes aren't appropriate.
How do agents know when new handoffs arrive?
The daemon's notify! function sends a literal message to the target role's tmux session using the tmux socket at .swarmforge/tmux-socket. This triggers immediate agent attention without requiring filesystem polling or network requests.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →