# SwarmForge Handoff Directory Structure: A Complete Guide to the Inbox/Outbox Protocol

> Understand the SwarmForge handoff directory structure in unclebob/swarm-forge. Learn about the inbox/outbox protocol for reliable AI agent work exchanges using .swarmforge/handoffs/.

- Repository: [Robert C. Martin/swarm-forge](https://github.com/unclebob/swarm-forge)
- Tags: architecture
- Published: 2026-08-28

---

**SwarmForge uses a dedicated `.swarmforge/handoffs/` directory with fixed subdirectories (`outbox/`, `inbox/`, `sent/`, `failed/`) to enable reliable, audit-trailed work exchanges between AI agents without direct socket communication.**

The **SwarmForge handoff directory structure** implements a durable, filesystem-based message queue that replaces traditional inter-agent networking. Each agent's worktree contains a single hidden directory where all task exchanges are staged, delivered, and archived. This design ensures restart-safe processing and deterministic order guarantees, as specified in [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md)【/cache/repos/github.com/unclebob/swarm-forge/main/swarmforge/handoff-protocol.md#L24-L38】.

## Root Handoff Directory Location

Every agent operates within a **worktree root** containing:

```text
.swarmforge/
└── handoffs/
    ├── outbox/
    ├── sent/
    ├── failed/
    └── inbox/

```

The `.swarmforge/handoffs/` path is hardcoded and enforced by the handoff daemon (`handoffd.bb`) and all helper scripts. No configuration overrides this location.

## Outbox Subdirectories

The `outbox/` branch handles **outbound handoffs** created by the local agent.

### outbox/

Holds validated `.handoff` files ready for delivery. Files here follow strict naming conventions:

```text
00_20260615T140531Z_000042_from_architect_to_coder.handoff
└┬┘ └─────────┬────────┘ └─┬─┘ └──┬──┘ └──┬───┘
 │            │            │      │       └─ recipient agent
 │            │            │      └─ sender agent
 │            │            └─ sequence number
 │            └─ UTC timestamp
 └─ priority (00-99, lower = higher priority)

```

### outbox/tmp/

A **private scratch area** for atomic file creation. The handoff daemon ignores this directory entirely. Scripts like [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) write handoff contents here first, then perform an atomic move to `outbox/` once fully formed. This prevents the daemon from processing incomplete files.

## Delivery Archive Directories

After processing, outbound handoffs land in one of two terminal states.

### sent/

Contains **successfully delivered handoffs**. The daemon (`handoffd.bb`) moves the original file here after:
- Validating the handoff structure
- Copying it to every recipient's `inbox/new/`
- Adding audit headers (`recipient`, `enqueued_at`)

### failed/

Stores **undeliverable handoffs** with diagnostic context. Common failure modes include:
- Validation errors (malformed headers)
- Missing or invalid recipient agents
- Permission or disk space issues

Each failed handoff retains its original content plus error annotations.

## Inbox Subdirectories

The `inbox/` branch manages **incoming work** from other agents.

### inbox/new/

The **task queue** for unclaimed handoffs. Agent helper scripts scan this directory to find the next available work item. The [`ready_for_next.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next.sh) entry point dispatches to [`ready_for_next_task.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next_task.sh) or [`ready_for_next_batch.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next_batch.sh) based on configuration.

Selection uses **priority-then-FIFO** ordering: lower numeric prefixes process first; ties resolve by arrival time.

### inbox/in_process/

Holds the **currently active handoff** (or batch directory in batch mode). When an agent claims work:

```bash
ready_for_next.sh

# Output:

# TASK: .swarmforge/handoffs/inbox/in_process/00_20260615T140531Z_000042_from_architect_to_coder.handoff

# FROM: architect

# TYPE: git_handoff

# PRIORITY: 00

# TASK_NAME: task-1-cave-setup

```

The atomic move from `inbox/new/` to `inbox/in_process/` prevents double-claiming.

### inbox/completed/

Archive of **finished handoffs**. The [`done_with_current.sh`](https://github.com/unclebob/swarm-forge/blob/main/done_with_current.sh) script (dispatching to [`done_with_current_task.sh`](https://github.com/unclebob/swarm-forge/blob/main/done_with_current_task.sh)) finalizes the active work:
- Adds `completed_at` timestamp
- Records processing outcome
- Moves file to `inbox/completed/`

## Complete Directory Tree Example

```text
.swarmforge/
└── handoffs/
    ├── outbox/
    │   ├── tmp/
    │   ├── 00_20260615T140531Z_000042_from_architect_to_coder.handoff
    │   └── 01_20260615T140545Z_000043_from_architect_to_tester.handoff
    ├── sent/
    │   └── 00_20260615T140500Z_000041_from_pm_to_architect.handoff
    ├── failed/
    │   └── 02_20260615T140600Z_000044_from_architect_to_invalid_agent.handoff.err
    └── inbox/
        ├── new/
        │   ├── 00_20260615T140548Z_000045_from_tester_to_architect.handoff
        │   └── 01_20260615T140555Z_000046_from_pm_to_architect.handoff
        ├── in_process/
        │   └── 00_20260615T140531Z_000042_from_architect_to_coder.handoff
        └── completed/
            ├── 00_20260615T140400Z_000040_from_coder_to_tester.handoff
            └── 01_20260615T140420Z_000039_from_tester_to_pm.handoff

```

## Handoff File Format

Files stored throughout this structure follow a **YAML frontmatter + body** format:

```text
id: 20260615T140531Z_000042_from_architect
from: architect
to: coder
recipient: coder
priority: 00
type: git_handoff
role: architect
task: task-1-cave-setup
commit: a1b2c3d9e8
created_at: 2026-06-15T14:05:31Z
enqueued_at: 2026-06-15T14:05:32Z

Re-read your role and constitution.

merge_and_process.sh architect a1b2c3d9e8

```

Headers are space-separated key-value pairs. The blank line separates metadata from payload. The `enqueued_at` field is added by the daemon during delivery—not present on creation.

## Key Scripts Enforcing Structure

| Script | Path | Function |
|--------|------|----------|
| [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) | [`swarmforge/scripts/swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/swarm_handoff.sh) | Creates handoffs in `outbox/tmp/`, validates, atomically moves to `outbox/` |
| `handoffd.bb` | `swarmforge/scripts/handoffd.bb` | Daemon watching `outbox/`, delivering to `inbox/new/`, archiving to `sent/` or `failed/` |
| [`ready_for_next.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next.sh) | [`swarmforge/scripts/ready_for_next.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/ready_for_next.sh) | Entry point for claiming next task; dispatches to task/batch variants |
| [`ready_for_next_task.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next_task.sh) | [`swarmforge/scripts/ready_for_next_task.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/ready_for_next_task.sh) | Selects from `inbox/new/`, atomically moves to `inbox/in_process/` |
| [`done_with_current.sh`](https://github.com/unclebob/swarm-forge/blob/main/done_with_current.sh) | [`swarmforge/scripts/done_with_current.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/done_with_current.sh) | Finalizes active work, moves to `inbox/completed/` |

These scripts embed the directory structure as constants—no runtime configuration exists to alter paths.

## Summary

- **Root location**: `.swarmforge/handoffs/` per worktree, enforced by all scripts
- **Outbound flow**: `outbox/tmp/` → `outbox/` → `sent/` or `failed/` (daemon-managed)
- **Inbound flow**: `inbox/new/` → `inbox/in_process/` → `inbox/completed/` (agent-managed)
- **Atomicity**: `outbox/tmp/` and atomic moves prevent partial file processing
- **Audit trail**: Headers added at each stage (`enqueued_at`, `completed_at`) enable full traceability

## Frequently Asked Questions

### Where does SwarmForge store handoff files between agents?

Handoff files reside in `.swarmforge/handoffs/` within each agent's worktree. This hidden directory contains subdirectories for outbound drafts (`outbox/`), delivery archives (`sent/`, `failed/`), and inbound task queues (`inbox/`). The structure is fixed across all agent types and enforced by the handoff daemon and helper scripts.

### What prevents the daemon from processing incomplete handoff files?

The `outbox/tmp/` subdirectory serves as a private staging area. Scripts write handoff content here first and only move the completed file to `outbox/` once fully formed. The daemon watches only `outbox/`, never `outbox/tmp/`, ensuring atomic handoff creation without partial file hazards.

### How does an agent claim the next available task?

Agents invoke [`ready_for_next.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next.sh), which dispatches to [`ready_for_next_task.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next_task.sh) or [`ready_for_next_batch.sh`](https://github.com/unclebob/swarm-forge/blob/main/ready_for_next_batch.sh). These scripts scan `inbox/new/`, select by priority-then-FIFO ordering, and perform an atomic move to `inbox/in_process/`. This prevents race conditions when multiple agent instances attempt to claim the same work item.

### What happens to handoffs that fail delivery?

Failed handoffs move to `failed/` with diagnostic extensions (`.err` files). Common causes include validation errors, missing recipients, or system resource issues. Unlike `sent/`, the `failed/` directory preserves error context to enable debugging and potential reprocessing without original data loss.