Atomic File Mailboxes: How Agents Message Each Other

ankitg121 pts0 comments

Atomic File Mailboxes: How Agents Message Each Other — Munder Difflin Blog

Skip to content

Atomic File Mailboxes: How Agents Message Each Other

Can Claude Code agents talk to each other? Yes — the outbox-router-inbox design that lets agents message safely using plain files and atomic renames.

CG<br>Chaitanya Giri<br>May 31, 2026<br>5 min read<br>Internals

TL;DRYes,<br>Claude Code agents can talk to each other — and the safest way is plain files. Each<br>agent writes to its own outbox/; a router drains it and delivers each<br>message into the recipient's inbox/ by atomic rename . One JSON file per<br>message, never a shared log. The result is durable, auditable, crash-safe messaging with no broker and<br>no write conflicts.

“Can Claude Code agents talk to each other?” is one of the first questions people ask when they move<br>past a single session. The answer is yes — but how they talk is where most designs go wrong. Let a<br>shared chat file be appended by every agent and you’ve built a multi-writer conflict. The robust<br>approach is a mailbox: outbox, router, inbox, with atomic file operations underneath. Here’s the whole<br>design.

The shape: outbox → router → inbox #

Every agent gets a private workspace, and two of the folders in it are for messaging:

agents//<br>inbox/ # messages delivered TO me — one JSON file each<br>inbox/.done/ # messages I've handled (kept for audit, not deleted)<br>outbox/ # messages I want to SEND — the router drains these<br>outbox/.sent/ # archived after delivery, so they're never reprocessed

The flow is one direction at a time:

To send, an agent writes a single JSON file into its own outbox/.

A router — one coordinating process — scans every outbox, reads each message, and delivers it<br>into the recipient’s inbox/.

After delivery, the router moves the original from outbox/ into outbox/.sent/ so it’s never<br>sent twice.

The recipient reads its inbox/ at the start of its next turn, acts on each message, and moves the<br>handled ones to inbox/.done/.

The critical rule sits underneath all of it: an agent only ever writes into its own directory. It<br>never reaches into another agent’s inbox. Delivery is the router’s job. That single-writer discipline<br>is what makes the whole thing safe under concurrency — no two processes touch the same file. (It’s the<br>same principle behind the single-committer git pattern.)

Why atomic renames matter #

Here’s the subtle part. If the router wrote a message into an inbox with a normal “open, write bytes,<br>close,” a reader scanning the inbox at the wrong moment could pick up a half-written file and choke on<br>invalid JSON.

The fix is an atomic rename : write the message to a temporary filename first, then rename it<br>into its final place. On every mainstream filesystem, rename within a directory is atomic — the file<br>either isn’t there or is there complete. A reader never sees a partial message. There’s no lock, no<br>coordination, no window of inconsistency.

write inbox/.json.tmp-9f3a ← may be partial mid-write<br>rename inbox/.json.tmp-9f3a → inbox/.json ← appears atomically, whole

This one trick is why files beat a naive shared log. A log every agent appends to is a multi-writer<br>race; a directory of atomically-renamed files is conflict-free by construction.

The message itself: speech acts, not free text #

A message isn’t just a blob of text — it carries intent. The design borrows the one genuinely useful<br>idea from decades of agent-communication research, the speech act , and drops the rest:

"id": "2026-05-30T14-03-11-123Z-a1b2", // unique, time-sortable<br>"conversation": "conv-7f3", // groups a thread<br>"in_reply_to": " | null",<br>"from": "agent.researcher",<br>"to": "agent.coder | god | broadcast",<br>"act": "request | inform | propose | query | agree | refuse | done",<br>"subject": "short human-readable summary",<br>"body": "the details",<br>"hops": 3, // increments per reply; capped to kill loops<br>"requires_reply": true,<br>"needs_human": false,<br>"created_at": "ISO-8601"<br>An agent writing a message only has to supply the meaningful fields — to, act, subject, body,<br>and optionally a conversation to thread it. The harness fills in the id, the authoritative from<br>(taken from the owning outbox directory, so an agent can’t spoof another’s identity), the hops, and<br>the timestamps.

The act field is what makes coordination tractable. It tells the recipient — and the router —<br>whether a reply is even expected.

Anti-livelock: how the conversation ends #

Two agents told to “coordinate” will, without guardrails, message each other forever. Three rules stop<br>that:

Only some acts obligate a reply. request, query, and propose expect an answer. inform<br>and done are terminal — replying to them is exactly how a loop starts, so the protocol forbids<br>it.

Hops are capped. Every reply increments the hops counter. Past a fixed cap, the message is<br>flagged for a human instead of being delivered again — a livelock fuse.

Delivery is idempotent. Each agent keeps a cursor of the last message id it processed, so<br>re-seeing a message is a no-op....

message inbox agent file outbox router

Related Articles