# What Is the Handoff Daemon (handoffd.bb) in SwarmForge?

> Discover the SwarmForge handoff daemon (handoffd.bb) automates task handoffs by scanning outbox directories, validating .handoff files, updating project boards, and sending tmux notifications.

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

---

**The handoff daemon (`handoffd.bb`) is a babashka-based background service that automates task handoffs in SwarmForge by scanning outbox directories, validating `.handoff` message files, updating the project board, and delivering notifications to recipient tmux sessions.**

The **handoff daemon** sits at the heart of SwarmForge's collaborative workflow. When team members working in different roles—such as "master," "specifier," or "implementer"—need to pass work to each other, they create `.handoff` files describing Git changes, task moves, or artifacts. The daemon, implemented in `swarmforge/scripts/handoffd.bb`, continuously monitors these handoff messages and orchestrates their delivery according to the SwarmForge protocol.

This article explains the daemon's architecture, key functions, and operational patterns drawn directly from the SwarmForge source code.

## Core Responsibilities of the Handoff Daemon

The handoff daemon performs **eight primary functions** that together enable automated, turn-based collaboration:

### Configuration and Path Setup

The `configure!` function establishes the daemon's operating environment. It parses command-line arguments including the optional `--once` flag, determines the `project-root`, and initializes paths for `state-dir`, `daemon-dir`, `socket-path`, and PID tracking.

```clojure
;; From swarmforge/scripts/handoffd.bb
(configure! project-root)
;; Sets globals: project-root, state-dir, daemon-dir, etc.

```

This configuration runs at startup and ensures all file operations stay scoped to the correct project context.

### Polling Loop and Execution Modes

The daemon operates in **two execution modes** controlled by the `-main` entry point:

| Mode | Trigger | Behavior |
|------|---------|----------|
| **Continuous daemon** | Default (no flags) | `run-daemon!` loops indefinitely, calling `poll-once!` and sleeping for `poll-ms` milliseconds between iterations |
| **Single-pass execution** | `--once` flag | Executes `poll-once!` exactly once, then exits—ideal for CI pipelines or debugging |

The `run-daemon!` function includes graceful shutdown handling through `should-stop?`, which checks for a stop flag file or internal termination signal.

### Outbox Discovery and Message Collection

During each poll cycle, `poll-once!` gathers `.handoff` files from every role's outbox directory, including the master project root. It builds a comprehensive `paths` collection that the daemon will process sequentially.

```bash

# Typical outbox locations scanned by the daemon

./.swarmforge/handoffs/outbox/          # role-specific outboxes

./.swarmforge/handoffs/master/outbox    # master role outbox

```

### Message Parsing and Validation

Each `.handoff` file undergoes parsing via `parse-message`, which splits the file into **headers** and **body**, returning a structured map with `:headers` and `:body` keys. The header section defines routing, task association, and approval status.

Example handoff file structure:

```text
id: 12345
type: git_handoff
from: specifier
to: master
task_id: TASK-42
approved: true
message: Implement feature X with tests

<optional detailed body follows blank line>

```

### Delivery Decision Logic

Before delivery, `should-hold?` evaluates whether a handoff requires manual approval. The daemon **holds messages** when:

- The sender is a **specifier** (creates packs)
- The sender holds the **master** role
- The message has **multiple recipients**
- The `approved` header is **missing or false**

Held handoffs remain in the outbox pending human review; approved messages proceed to `deliver!`.

### Message Delivery and File Operations

The `deliver!` function executes the complete handoff sequence:

1. **Renders** the final message with delivery timestamps
2. **Writes** the handoff to the recipient's inbox directory
3. **Moves** the source file to the sender's *sent* folder
4. **Updates** the project board via `update-board!`
5. **Archives** the sender's board entry via `archive-sender!`
6. **Notifies** the recipient's tmux session via `notify!`

This atomic sequence ensures board state and file system remain synchronized.

### Board State Management

SwarmForge maintains a visible task board that tracks work progress. The daemon integrates with board operations through two key functions:

- **`update-board!`** – For Git handoffs, moves tasks between lanes (e.g., "In Progress" → "Review") or marks them complete
- **`archive-sender!`** – Archives the originating role's board entry after successful delivery

Both functions invoke `pack_board.bb` (**`swarmforge/scripts/pack_board.bb`**) to apply board transformations.

### Tmux Session Notification

The `notify!` function wakes recipient tmux panes by sending keystrokes that display a configurable `wake-message`. This ensures team members receive immediate, in-terminal alerts when work arrives.

```clojure
;; Notification implementation from swarmforge/scripts/handoffd.bb
(notify! session message)
;; Sends tmux keystrokes to display wake-message

```

## Operational Commands and Workflows

### Starting and Stopping the Daemon

```bash

# Start continuous daemon for a project

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

# Single execution for testing or CI

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

```

### Graceful Shutdown

Create the stop flag file to trigger clean termination:

```bash
touch /path/to/project/.swarmforge/daemon/stop

```

The daemon detects this file via `should-stop?`, removes its PID file, logs termination, and exits on the next poll cycle.

### Log Inspection

Human-readable operational logs write to:

```bash
cat /path/to/project/.swarmforge/daemon/handoffd.log

```

### Creating and Sending Handoffs

Roles initiate handoffs by writing `.handoff` files to their outbox:

```bash

# Example: specifier creates a handoff for master

cat > .swarmforge/handoffs/outbox/feature-x.handoff << 'EOF'
id: 2024-001
type: git_handoff
from: specifier
to: master
task_id: TASK-42
message: Specification complete; ready for implementation

Branch: spec/TASK-42-feature-x
Commits: 3 files changed, 47 insertions
EOF

```

The daemon automatically processes this file, delivers it to `master`'s inbox, updates the board, and notifies the master tmux pane.

## Related Source Files

| File | Purpose |
|------|---------|
| `swarmforge/scripts/handoffd.bb` | **Daemon implementation** (primary source file) |
| `swarmforge/scripts/handoff_lib.bb` | Shared library for handoff parsing and utilities |
| [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md) | Formal message format specification |
| `swarmforge/scripts/pack_board.bb` | Board manipulation commands |
| [`swarmforge/scripts/ready_for_next.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/ready_for_next.sh) | Post-notification task preparation helper |

## Summary

- The **handoff daemon** (`handoffd.bb`) is a **babashka script** that runs continuously to automate SwarmForge's message-driven workflow
- It **polls outbox directories**, **parses `.handoff` files**, and **delivers messages** to recipient inboxes while updating the project board
- **Approval routing** via `should-hold?` ensures sensitive handoffs from specifiers and masters receive human review
- **Tmux notifications** provide immediate, in-terminal alerts to receiving team members
- The daemon supports **continuous daemon mode** for production use and **`--once` execution** for CI/testing scenarios
- All board updates and archiving operations integrate with `pack_board.bb` to maintain consistent project state

## Frequently Asked Questions

### What triggers a handoff to be held for approval?

A handoff is held when `should-hold?` determines the sender is a specifier pack creator, holds the master role, addresses multiple recipients, or omits the `approved: true` header. These criteria prevent premature delivery of impactful changes without explicit authorization.

### Can the daemon run without staying resident?

Yes. Pass the `--once` flag to execute a **single poll cycle** and exit immediately. This mode supports CI pipelines, pre-commit hooks, and diagnostic debugging without the overhead of a persistent process.

### How does the daemon know which tmux session to notify?

The `notify!` function targets the **recipient role's tmux session** as specified in the handoff's `to` header. It sends keystrokes to that session's window, displaying the configured `wake-message` defined in the SwarmForge configuration.

### Where are handoff files stored during processing?

Handoffs follow a **three-stage lifecycle**: they originate in the sender's **outbox**, move to the recipient's **inbox** upon delivery, and the source copy archives to the sender's **sent** folder. Failed or held handoffs remain in outbox pending resolution.