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 nameenqueued_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/:
- Recipients resolved:
["reviewer" "tester"] - Two inbox copies created with distinct
recipientmetadata - Tmux wake‑up sent — both agents detect activity and poll inboxes
- Board card moved to "Done" (forward‑type, non‑forwarding is false)
- 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
handoffdis a Babashka daemon atswarmforge/scripts/handoffd.bbthat 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:
--onceflag supports testing and CI workflows - Protocol compliance: Full implementation of
handoff-protocol.mdspecification
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →