How the Hive Coordination Layer Handles Inter-Agent Messaging and Routing in munder-difflin

The Hive coordination layer uses a poll-based file-system router that scans agent outboxes every ~1.5 seconds and atomically moves JSON messages to recipient inboxes using renameSync, guaranteeing reliable inter-agent delivery without native file watchers.

The inter-agent messaging and routing system in munder-difflin is powered by the Hive, an on-disk, multi-agent coordination subsystem managed entirely by the main Electron process. All communication between agents flows through this layer, which persists state under <harnessHome>/hive/ and enforces a single-committer Git model.

What Is the Hive Coordination Layer?

The Hive is the central nervous system for agent-to-agent communication in munder-difflin. Located under <harnessHome>/hive/, it is orchestrated by the HiveManager class in src/main/hive.ts and bootstrapped from src/main/index.ts. Rather than using network sockets or message brokers, the Hive relies on an on-disk directory structure and atomic file operations to route messages between agents.

Agent Workspace Layout

Every agent registered with the Hive receives its own dedicated folder under agents/<id>/. According to the HiveManager definition in src/main/hive.ts (lines 1–10), each workspace contains:

  • inbox/ – Directory for incoming messages.
  • outbox/ – Directory for outgoing messages.
  • identity.md – Agent identity document.
  • memory.md – Persistent agent memory.

This layout ensures that agents only need file-system access to participate in the messaging network.

HiveMessage Format

Messages exchanged between agents are plain JSON objects conforming to the HiveMessage interface declared in src/main/hive.ts (lines 48–63). A HiveMessage includes metadata fields such as from, to, act, subject, body, timestamps, and routing controls like hops and requires_reply.

The router treats any file ending in .json inside an outbox/ as a valid message payload.

Poll-Based Router and Routing Mechanism

The core routing logic is implemented as a poll-based router running on a setInterval timer (routerTimer). As implemented in src/main/hive.ts around lines 1311–1350, the router executes the following cycle approximately every 1.5 seconds:

  1. Enumerates every registered agent.
  2. Reads all .json files inside the agent's outbox/.
  3. Parses each file into a HiveMessage and inspects the msg.to field.
  4. Atomically moves the file to agents/<recipient>/inbox/ using fs.renameSync.
  5. Archives the original file under the sender's outbox/.sent using fs.renameSync.

The design deliberately avoids fs.watch to sidestep macOS quirks and to guarantee atomicity through simple file operations.

Delivery Guarantees and Atomicity

The Hive coordination layer provides three key reliability mechanisms:

  • Atomic move – renameSync ensures a message is never duplicated or left partially written during transit.
  • Sent archive – The outbox/.sent directory preserves a permanent record of dispatched messages, which the UI can render later.
  • Cursor tracking – A cursor.json file tracks inbox consumption, guaranteeing each message is processed exactly once (used by the Stop hook).

These mechanisms allow the system to recover cleanly from crashes without message loss.

Human-in-the-Loop Routing

Messages addressed to human are automatically intercepted by the router and redirected to the special "god" agent. The god agent's UI surfaces a permission prompt, allowing human operators to approve or reject actions inline. This routing decision happens inside the standard router loop without requiring a separate approval queue.

Hook Integration for Claude-Code Agents

Agents that expose a HIVE_SOCK via the sockPath() method receive lifecycle hook payloads such as Stop and PreToolUse. The router does not directly manage these hook calls; instead, it ensures that any message generated by a hook finishes its lifecycle and appears in the recipient's inbox. The hook shim writes JSON payloads to the Unix domain socket, and the Hive layer translates those into standard outbox files for the next router tick.

Step-by-Step Message Flow

A complete round-trip through the Hive inter-agent messaging pipeline looks like this:

  1. Agent writes a JSON file to its own outbox/ using any filename ending in .json.
  2. Router tick scans the outbox/ of every registered agent.
  3. For each message file:
    • The router reads the JSON and validates the HiveMessage structure.
    • It resolves the recipient from the to field.
    • It performs an atomic renameSync to move the file into agents/<recipient>/inbox/.
    • It archives the original under the sender's outbox/.sent/.
  4. Inbox consumer—whether a Claude-Code hook, the UI, or a custom skill—reads the file from the recipient's inbox, processes it, and optionally writes a response to its own outbox/.
  5. The router picks up the response on the next tick, completing the round-trip.

Code Examples

Sending a Message from an Agent

Agents produce messages by writing JSON files directly to their outbox:

import { writeFileSync, join } from 'node:fs';
import { randomBytes } from 'node:crypto';

const msg = {
  id: randomBytes(6).toString('hex'),
  conversation: 'conv-123',
  in_reply_to: null,
  from: 'agentA',
  to: 'agentB',
  act: 'request',
  subject: 'Need data',
  body: 'Please fetch the latest sales numbers.',
  hops: 0,
  requires_reply: true,
  needs_human: false,
  created_at: new Date().toISOString()
};

const outboxDir = join(
  process.env.HIVE_ROOT!,
  'agents',
  'agentA',
  'outbox'
);

writeFileSync(
  join(outboxDir, `${msg.id}.json`),
  JSON.stringify(msg, null, 2)
);

When the next router tick runs, this file is moved to agents/agentB/inbox/ and archived under agents/agentA/outbox/.sent/.

Reading a Received Message

Recipients consume inbox messages by reading the JSON files from their inbox/ directory:

import { readdirSync, readFileSync, join } from 'node:fs';

const inboxDir = join(process.env.HIVE_ROOT!, 'agents', 'agentB', 'inbox');

for (const file of readdirSync(inboxDir)) {
  if (!file.endsWith('.json')) continue;
  const raw = readFileSync(join(inboxDir, file), 'utf8');
  const msg = JSON.parse(raw) as HiveMessage;
  console.log('Got message:', msg.subject, msg.body);
}

Hook-Driven Automatic Routing

For Claude-Code agents, the system generates a settings.json that points the hook shim to HIVE_SOCK. The shim writes JSON payloads to this socket, which the Hive layer surfaces as standard outbox files. The poll-based router then picks them up on its next tick without requiring any additional routing code.

Key Files in the Messaging Pipeline

  • src/main/hive.ts – Defines HiveMessage, HiveManager, the poll-based router loop, and archive helpers.
  • src/main/index.ts – Bootstraps HiveManager, starts the routerTimer, and integrates the Hive with the Electron main process.
  • src/shared/agentProvider.ts – Determines whether a provider can receive inbox messages (canReceiveInbox) and whether it is hive-aware.
  • src/main/pty.ts – Spawns agent PTYs with environment variables such as HIVE_ROOT and AGENT_DIR that the router relies on.
  • HIVE.md – Design document covering the on-disk layout, the single-committer Git model, and the rationale for avoiding file watchers.

Summary

  • The Hive coordination layer handles inter-agent messaging and routing in munder-difflin through an on-disk, poll-based file-system router.
  • All messages are plain JSON conforming to the HiveMessage interface, stored in per-agent inbox/ and outbox/ directories.
  • The router in src/main/hive.ts uses renameSync to atomically move messages from sender outboxes to recipient inboxes every ~1.5 seconds.
  • Delivery guarantees include atomic moves, a outbox/.sent archive, and cursor.json tracking for exactly-once processing.
  • Human-in-the-loop messages route to the "god" agent automatically, while Claude-Code hooks leverage HIVE_SOCK for lifecycle integration.

Frequently Asked Questions

How does the Hive router avoid message loss during crashes?

The router relies on fs.renameSync for both delivery and archiving. Because rename is atomic on POSIX systems, a message either remains in the sender's outbox or arrives completely in the recipient's inbox—there is no intermediate state. After delivery, the original is archived under outbox/.sent, creating a durable record even if the process restarts.

Why does the Hive use polling instead of file watchers?

According to the source comments in src/main/hive.ts, the poll-based router avoids fs.watch to eliminate macOS-specific quirks and to guarantee atomicity. A simple setInterval timer scanning every ~1.5 seconds is deterministic, portable, and avoids race conditions that can occur with native file-system event APIs.

Can agents communicate without writing files directly?

Yes. Agents using Claude-Code can write to the HIVE_SOCK Unix domain socket exposed via sockPath(). The Hive layer translates these socket payloads into outbox files, which the poll-based router then delivers using the standard inbox move. This allows hook-driven agents to participate without managing the file system directly.

What happens when a message is addressed to a human operator?

When msg.to equals human, the router redirects the message to the special "god" agent. The god agent's UI presents a permission prompt inline, enabling human approval without a separate queue or routing bypass. This is handled inside the standard router loop in src/main/hive.ts.

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 →