Understanding the Handoff Daemon (handoffd.bb) in SwarmForge

The handoff daemon (handoffd.bb) is the core background service in SwarmForge that continuously monitors role-specific outboxes, routes handoff messages between distributed work-trees, manages approval workflows for git handoffs, and synchronizes the project task board.

The SwarmForge project (unclebob/swarm-forge) relies on handoffd.bb to act as the central message router that keeps multiple role-based repositories synchronized. Written in Babashka, this daemon operates as a persistent process (or one-shot scanner) that bridges communication gaps between isolated work-trees without requiring manual file copying.

Configuration and Daemon Initialization

When handoffd.bb starts, the configure! function parses command-line arguments to determine operational mode. The daemon accepts either a path for continuous operation or the --once flag for single-scan execution. During initialization, it establishes critical filesystem paths for the project state, role definitions, tmux socket location, and logging directories.

According to the source code in swarmforge/scripts/handoffd.bb, the configuration phase sets up:

  • The daemon folder for PID and log files (.swarmforge/daemon/)
  • Path resolution for the tmux socket used for notifications
  • Role table loading via load‑roles from .swarmforge/roles.tsv

The Polling Loop and Message Detection

In continuous mode, the run-daemon! function implements the main event loop. The daemon repeatedly invokes poll-once! followed by a configurable sleep interval (poll-ms), creating a lightweight polling mechanism that minimizes resource usage while maintaining responsiveness.

The poll-once! function (lines 99-107) performs three critical actions:

  1. Loads the role table to understand the project topology
  2. Reads the current tmux socket configuration
  3. Recursively scans all .handoff files in each role's outbox directory and the project-wide outbox

This scanning approach ensures that any role—from master to specialized development roles—can deposit handoff files that the daemon will detect and route.

Message Processing and Delivery Pipeline

For each detected handoff file, the daemon calls process-outbox-file!, which initiates a sophisticated delivery workflow defined in deliver! (lines 50-71).

The Delivery Workflow

The deliver! function executes several sequential operations:

  • Parsing: Extracts headers using parse-message to determine routing (from, to, type)
  • Board Updates: For git_handoff types, triggers update-board! which calls pack_board.sh to move tasks between lanes on the physical board
  • Routing: Copies the message to each recipient's inbox (target-path) and appends delivery timestamps via add‑delivery‑headers
  • Notification: Sends tmux alerts to recipient sessions using notify!
  • Archival: Moves the original to the sender's sent folder and archives the sender's board state if needed

Approval and Hold Logic

Not all handoffs proceed immediately. The should-hold? function (lines 92-98) implements a gatekeeping mechanism specifically for git_handoff messages originating from the master role. When such a handoff targets a single recipient who hasn't yet approved the change, the daemon moves the file to the pending_approval directory via hold!, preventing premature delivery until explicit approval is granted.

Task Board Integration

The handoff daemon maintains synchronization between code changes and project management through update-board!. This function invokes pack_board.sh to manipulate the task board data stored in .swarmforge/board/tasks.tsv, automatically moving tasks between columns (such as from "In Progress" to "Done") when associated handoffs are processed.

Running the Handoff Daemon

The daemon supports two operational modes depending on your workflow requirements.

Continuous mode (default behavior):


# From the root of a SwarmForge project

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

One-shot execution (ideal for CI/CD pipelines):

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

Manual handoff creation for testing:

cat > .swarmforge/handoffs/outbox/example.handoff <<EOF
id: 12345
from: master
to: dev
type: git_handoff
task_id: task-42
message: Deploy latest changes
EOF

Monitoring daemon activity:

tail -f .swarmforge/daemon/handoffd.log

Graceful Shutdown and Process Management

The daemon implements clean shutdown semantics through shutdown! and the should-stop? predicate. When a stop file is detected or an internal flag is set, the daemon:

  1. Completes the current polling cycle
  2. Removes its PID file
  3. Writes final entries to .swarmforge/daemon/handoffd.log

This ensures that no handoff files are left in a partially processed state during restart or deployment operations.

Summary

  • The handoff daemon (handoffd.bb) serves as the central message bus for SwarmForge, routing .handoff files between role-specific work-trees.
  • Continuous polling via poll-once! and run-daemon! ensures real-time message delivery with configurable intervals.
  • Approval workflows prevent master-to-single-recipient git handoffs from auto-delivering until explicitly approved.
  • Board synchronization occurs automatically through update-board! and the pack_board.sh helper script.
  • Tmux integration provides real-time notifications to developers when handoffs arrive in their inboxes.

Frequently Asked Questions

What triggers the handoff daemon to process a message?

The daemon processes messages based on filesystem polling. When poll-once! detects .handoff files in any role's outbox directory (.swarmforge/handoffs/outbox/), it immediately queues them for processing through process-outbox-file!. Unlike event-driven systems, SwarmForge uses intentional polling to avoid filesystem watcher limitations across different operating systems.

How does the daemon handle messages that require approval?

When should-hold? determines a git handoff from the master role requires approval (typically when sent to a single recipient who hasn't approved), the daemon moves the file to pending_approval/ using hold! instead of calling deliver!. The message remains there until manual approval moves it back to the outbox or the recipient status changes.

Can I run the handoff daemon without continuous polling?

Yes. Pass the --once flag when invoking the script. This executes a single scan of all outboxes, processes any pending handoffs, and immediately exits. This mode is particularly useful in automated CI pipelines where you want to trigger handoff processing as a discrete step rather than maintaining a background process.

Where does the daemon store its logs and PID information?

The daemon writes operational logs to .swarmforge/daemon/handoffd.log and maintains its process ID in .swarmforge/daemon/handoffd.pid. These paths are established during the configure! phase and are used by shutdown! to manage graceful termination and debug logging.

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 →