How the SwarmForge Handoff Daemon Delivers Messages Between Agents

The SwarmForge handoff daemon uses a lightweight file-based polling mechanism to move hand-off files from sender outboxes to recipient inboxes and wakes tmux sessions to notify agents of pending work.

The SwarmForge handoff daemon (handoffd.bb) implements an asynchronous, file-based message bus that enables agent communication without requiring network servers. This Clojure-based daemon continuously monitors outbox directories, parses hand-off files, routes them to specified recipients, and triggers agent activation through tmux notifications. Understanding how the handoff daemon deliver messages between agents reveals the core architecture of SwarmForge's distributed task coordination.

Role Configuration and Initialization

At startup, the daemon loads agent definitions from <project-root>/.swarmforge/roles.tsv using the load-roles function. Each line in this tab-separated file defines a role, its work-tree path, tmux session name, display configuration, agent type, and receive mode. The data is stored in a map keyed by role name for O(1) lookups during delivery.

;; Conceptual structure from handoffd.bb#L44-L56
(def roles (load-roles))  ;; Returns {"worker-1" {:worktree "..." :session "..."} ...}

This configuration enables the daemon to resolve physical file paths and tmux session identifiers dynamically without hardcoding agent locations.

Polling and Outbox Detection

Every second (controlled by poll-ms), the daemon executes poll-once!, which iterates over every registered role and gathers pending messages using outbox-files. These files are regular *.handoff files created when a sender agent executes swarm_handoff.sh.

;; Simplified polling loop from handoffd.bb#L46-L53
(doseq [role (vals roles)]
  (let [files (outbox-files role)]
    (doseq [path files]
      (deliver! roles socket role path))))

The daemon scans each agent's outbox directory (<worktree-path>/.swarmforge/handoffs/outbox/) for new hand-off files, creating a queue of pending deliveries to process.

Parsing and Routing Messages

Each outbox file is processed by parse-message, which splits the file into a header block and optional body. Header lines are converted into a key-value map, while the body content is preserved separately.

type: git_handoff
to: worker-1,worker-2
priority: 50
task: build-docker-image
commit: a1b2c3d4

Optional body content here...

The daemon examines the to header—a comma-separated list of recipient role names—and fetches corresponding role information from the roles map. If a specified recipient does not exist in the configuration, the daemon throws an exception and routes the message to the failure handler.

Adding Delivery Metadata

Before writing to recipient inboxes, the add-delivery-headers function injects provenance metadata:

  • recipient: The target role name
  • enqueued_at: Current ISO-8601 timestamp

This metadata, added at lines 85-89 of handoffd.bb, enables downstream agents to track message latency and routing history.

;; From handoffd.bb#L85-L89
(defn add-delivery-headers [headers recipient]
  (-> headers
      (assoc "recipient" recipient)
      (assoc "enqueued_at" (iso-8601-now))))

Writing to Recipient Inboxes

The target-path function constructs the absolute destination path:

<worktree-path>/.swarmforge/handoffs/inbox/new/<filename>.handoff

The render-message function serializes the enriched headers followed by a blank line and the original body. The daemon performs an idempotency check—if the file already exists in the target inbox, it skips writing to prevent duplicates.

Notifying Agents via Tmux

After successful file placement, the notify! function (lines 94-105) sends three tmux commands to the recipient's session:

tmux -S <socket> send-keys -t <session> -l "You have new handoff mail. If idle, run ready_for_next.sh."
tmux -S <socket> send-keys -t <session> C-m
tmux -S <socket> send-keys -t <session> C-j

This sequence displays a wake-up message in the agent's tmux pane and simulates Enter keypresses (carriage return and line feed), ensuring the notification appears even if the terminal is idle. Agents typically respond by executing ready_for_next.sh to process the new hand-off.

Archiving and Error Handling

Once all recipients receive the message, the original outbox file moves to the sender's sent directory using move-with-collision. If a filename collision occurs, the daemon appends a timestamp to ensure uniqueness.

Error handling follows a strict fail-safe protocol:

  • Missing to headers trigger immediate failure
  • Unknown recipients raise exceptions
  • Write failures are caught and logged

Any error moves the offending file to the failed directory with a companion .error file describing the cause, implemented in the fail! function.

Summary

  • The handoff daemon (swarmforge/scripts/handoffd.bb) provides asynchronous message delivery using filesystem operations rather than network sockets.
  • Configuration loads from .swarmforge/roles.tsv to map logical agent names to physical worktrees and tmux sessions.
  • Delivery flow moves files from outbox to inbox/new, adds metadata headers (recipient, enqueued_at), and triggers agents via tmux send-keys commands.
  • Reliability is ensured through collision-safe archiving, idempotent writes, and comprehensive error handling with automatic failure directory routing.

Frequently Asked Questions

How does the handoff daemon handle multiple recipients for a single message?

The daemon splits the to header on commas and iterates through each recipient. For every valid recipient, it creates an independent copy of the message in that agent's inbox with recipient-specific metadata. Only after all recipients receive their copies does the daemon move the original file to the sender's sent directory.

What happens if a recipient's tmux session is not running when a message arrives?

The tmux send-keys command will fail silently if the session does not exist, but the message remains safely in the recipient's inbox. When the agent starts and runs ready_for_next.sh, it will discover and process pending hand-off files regardless of whether the original notification succeeded.

Can agents communicate across different machines using this daemon?

No. The handoff daemon operates on local filesystem paths and unix sockets for tmux communication. It is designed for single-machine coordination where all agents share a filesystem namespace, though the repository structure could theoretically be extended for networked filesystems.

What distinguishes the "new" inbox directory from other inbox subdirectories?

The inbox/new directory holds messages that have not yet been acknowledged by the receiving agent. When ready_for_next.sh processes a hand-off, it typically moves the file from inbox/new to inbox/cur (current) or inbox/processing, creating a simple state machine for message lifecycle management without requiring database transactions.

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 →