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 identifier
  • from – Originating role
  • to – Destination role
  • type – Message category (e.g., git_handoff)
  • task – Associated task name
  • commit – Git SHA for version tracking
  • priority – Numeric priority for queue ordering
  • task – 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 pickup
  • in_process/ – Currently active handoff
  • completed/ – Finished work archive

Processing Functions

The daemon invokes board management functions after delivery confirmation:

  • pack-board! (lines 52-58) – Consolidates board state
  • archive-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 .handoff files exchanged through a centralized daemon
  • The three-phase pipeline creates messages in outbox/, delivers via handoffd.bb to role inbox/new/ folders, and consumes through board update functions
  • move-with-collision guarantees safe delivery without data loss
  • notify! 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →