# How the `handoffd` Daemon Works in Swarm‑Forge: Architecture and Operation

> Discover how the handoff daemon handoffd operates in Swarm Forge. Learn its architecture, message parsing, delivery, and agent notification for asynchronous work exchange.

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

---

**The `handoffd` daemon continuously monitors a project's outbox directory, parses hand‑off messages, delivers copies to recipient inboxes, and wakes up agents via tmux notifications to enable asynchronous work exchange between Swarm‑Forge agents.**

This deep dive explains the internal mechanics of `handoffd`, the central message broker in the [Swarm‑Forge](https://github.com/unclebob/swarm-forge) project. Written in Babashka and located at `swarmforge/scripts/handoffd.bb`, this daemon implements the hand‑off protocol that decouples agent communication from direct coordination.

## Core Responsibilities of `handoffd`

The daemon operates through six primary responsibilities, each implemented as discrete functions in the main script.

| Responsibility | Implementation | Source Location |
|---------------|----------------|-----------------|
| **Directory watching** | Infinite loop monitoring `.swarmforge/outbox/` for new files | `handoffd.bb` main loop |
| **Message parsing** | `parse-message`, `recipient-list`, `non-forwarding?`, `phantom-sender?` extract headers | `handoffd.bb` helper functions |
| **Inbox delivery** | `deliver!` writes copies with `recipient` and `enqueued_at` metadata | `handoffd.bb` core logic |
| **Agent wake‑up** | `tmux-send!` broadcasts generic tmux `display-message` notifications | `handoffd.bb` notification layer |
| **Board synchronization** | `update-board!` moves Kanban cards for forward‑type hand‑offs | `handoffd.bb` board integration |
| **Process management** | PID file and log file creation for lifecycle control | `handoffd.bb` startup routines |

## How `handoffd` Processes Hand‑Off Files

### Step 1: Watch the Outbox

The daemon runs an infinite watch loop using Babashka's `fs/watch` capabilities. When a file appears in `.swarmforge/outbox/`, the loop triggers processing.

```clojure
;; Simplified conceptual flow from handoffd.bb
;; The actual implementation uses fs/watch for filesystem events
(loop []
  (when-let [handoff (next-outbox-file project-root)]
    (process-handoff! handoff))
  (recur))

```

### Step 2: Parse Headers and Body

The parsing functions extract structured data from the hand‑off file format:

```clojure
;; Example handoffd.bb parsing flow
(defn parse-message [file-content]
  ;; Extracts: from, to, type, non-forwarding, phantom-sender headers
  ;; Returns map with :headers and :body
  )

(defn recipient-list [headers]
  ;; Parses comma-separated "to" field into vector of agent names
  ;; e.g., "to: reviewer, tester" → ["reviewer" "tester"]
  )

(defn non-forwarding? [headers]
  ;; Checks for "non-forwarding: true" header
  ;; Determines if board state should advance
  )

(defn phantom-sender? [headers]
  ;; Checks for "phantom-sender" header
  ;; Used for system-generated hand‑offs without human origin
  )

```

### Step 3: Deliver to Recipient Inboxes

The `deliver!` function creates individualized copies for each recipient:

```clojure
;; From handoffd.bb — delivers to each recipient's inbox
(defn deliver! [handoff recipients project-root]
  (doseq [recipient recipients]
    (let [inbox-path (str project-root "/.swarmforge/inbox/" recipient "/")
          enriched (assoc handoff 
                     :recipient recipient
                     :enqueued_at (iso-timestamp))]
      (spit (str inbox-path (uuid) ".handoft") 
            (serialize enriched)))))

```

Each delivered file receives:
- **`recipient`**: The specific agent name
- **`enqueued_at`**: ISO‑8601 timestamp of delivery

### Step 4: Wake Up Receiving Agents

After delivery, `tmux-send!` broadcasts a minimal wake‑up:

```clojure
;; handoffd.bb notification mechanism
(defn tmux-send! [message]
  ;; Executes: tmux display-message 'handoff'
  ;; Agents poll their inboxes when any tmux activity fires
  (shell/sh "tmux" "display-message" "handoff"))

```

Agents listen for **any** tmux activity on their board session, then check their `.swarmforge/inbox/` for pending work.

## Board Integration and State Management

### Conditional Board Updates

The `update-board!` function only operates when a Kanban board is configured:

```clojure
;; handoffd.bb board synchronization
(defn update-board! [handoff project-root]
  (when (board-exists? project-root)
    (when (and (forward-type? handoff)
               (not (non-forwarding? (:headers handoff))))
      ;; Moves source card to "Done" lane
      (move-card! handoff project-root))))

```

Forward‑type hand‑offs (like `git_handoff`) advance workflow state automatically. Non‑forwarding hand‑offs preserve card position.

## Process Lifecycle and Management

### Daemon Startup

`handoffd` writes two control files on startup:

| File | Path | Purpose |
|------|------|---------|
| PID file | `.swarmforge/daemon/handoffd.pid` | Enables graceful shutdown via `stop_handoff_daemon.bb` |
| Log file | `.swarmforge/daemon/handoffd.log` | Operational logging for debugging |

```clojure
;; handoffd.bb startup sequence
(let [daemon-dir (str project-root "/.swarmforge/daemon/")]
  (io/make-parents daemon-dir)
  (spit (str daemon-dir "handoffd.pid") (pid))
  ;; Fork to background, redirect stdout/stderr to log file
  )

```

### One‑Shot Mode for Testing

The `--once` flag bypasses the infinite loop for deterministic execution:

```bash

# Run single delivery pass and exit — useful in CI

bb swarmforge/scripts/handoffd.bb --once /path/to/project

```

```bash

# Normal daemon operation — forks to background

bb swarmforge/scripts/handoffd.bb /path/to/project

```

## Hand‑Off File Format Example

Files processed by `handoffd` follow the protocol defined in [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md):

```markdown
from: coder
to: reviewer, tester
type: git_handoff
non-forwarding: false

# Commit message and instructions

git commit -m "Implement feature X"

```

When this file appears in `.swarmforge/outbox/`:

1. **Recipients resolved**: `["reviewer" "tester"]`
2. **Two inbox copies created** with distinct `recipient` metadata
3. **Tmux wake‑up sent** — both agents detect activity and poll inboxes
4. **Board card moved** to "Done" (forward‑type, non‑forwarding is false)
5. **Original file** remains in outbox until manually cleaned

## Integration with Swarm‑Forge Ecosystem

| Component | Role in `handoffd` Lifecycle |
|-----------|------------------------------|
| `swarmforge.bb` | Launches `handoffd` automatically on project open |
| `stop_handoff_daemon.bb` | Reads PID file, sends termination signal |
| [`handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/handoff-protocol.md) | Formal specification of message format |
| `handoff_test.clj` | Unit tests for parsing and delivery logic |
| `coverage_in_process_test.clj` | Integration tests including board updates |

## Summary

- **`handoffd`** is a Babashka daemon at `swarmforge/scripts/handoffd.bb` that implements asynchronous agent messaging
- **Core loop**: watch → parse → deliver → notify → optionally update board
- **Key functions**: `parse-message`, `recipient-list`, `deliver!`, `tmux-send!`, `update-board!`
- **Persistence**: PID and log files in `.swarmforge/daemon/` enable external lifecycle management
- **One‑shot mode**: `--once` flag supports testing and CI workflows
- **Protocol compliance**: Full implementation of [`handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/handoff-protocol.md) specification

## Frequently Asked Questions

### What triggers `handoffd` to process a new hand‑off?

Filesystem events in `.swarmforge/outbox/` trigger the watch loop. The daemon uses Babashka's `fs/watch` to detect new files without polling, then immediately parses and delivers them according to the protocol specification.

### How do agents know when to check their inboxes?

`handoffd` sends a generic tmux `display-message` notification via `tmux-send!`. Agents are configured to treat any tmux activity as a wake‑up signal, then proactively poll their `.swarmforge/inbox/` directories for new files.

### Can `handoffd` run without a Kanban board?

Yes. The `update-board!` function checks for board presence before executing. Hand‑offs deliver normally regardless of board configuration; board synchronization is purely optional.

### How do I stop a running `handoffd` instance?

Use the `stop_handoff_daemon.bb` helper script, which reads `.swarmforge/daemon/handoffd.pid` and sends the appropriate termination signal. The launcher also stops the daemon automatically when the project closes.