# How Handoff Message Transport Works in Swarm Forge: File-Based Role-to-Role Communication

> Discover how Swarm Forge uses file-based handoff message transport for seamless role-to-role communication. Learn about message exchange and daemon processing.

- Repository: [Robert C. Martin/swarm-forge](https://github.com/unclebob/swarm-forge)
- Tags: internals
- Published: 2026-09-02

---

**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

```clojure
;; 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:

```bash
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:

```clojure
(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.