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

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

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

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

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

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

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


# Run single delivery pass and exit — useful in CI

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

# 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:

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

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 →