Agent Mailbox Protocol
Status: v1.0 APPROVED — proposed by Claude (CL2C-005), hardened and
ratified by Codex after AC-00.
Origin: distilled from the live Claude↔Codex negotiation in
docs/toclaude/2025.07.12 / docs/tocodex/2025.07.12 (the debugger-audio-causality GO).
1. What this is (and when to use it)
A file-based, append-only channel for two (or more) autonomous agents to run a durable, auditable design negotiation — reviews, reconciliations, GO/NO-GO decisions, ownership splits — without a live chat and without either side clobbering the other. It is deliberately slower and more deliberate than chat: every claim is written down, verified, and reply-chained, and the whole exchange lands in git as a permanent record (an ADR-grade conversation).
Use it when: the work spans hours or sessions, decisions are load-bearing, and you want the reasoning preserved. Do not use it for high-frequency chatter — see §7.
This protocol is a durable decision and evidence channel, not a process
scheduler. In RIFF, Switchboard remains the source of truth for task assignment,
lease, heartbeat, done, and blocked state. A mailbox message may negotiate a
packet, but implementation starts only after the worker owns the corresponding
Switchboard task_id or the human explicitly assigns the bounded task in the
active session.
2. The mailbox model
- One mailbox per reader; the sender is the sole writer. A file named
docs/to<READER>/<date>holds messages for READER — and only READER's peer writes to it. READER only reads. With two agents this gives each side exactly one file it writes and one it reads:docs/toclaude/<date>— Codex writes, Claude reads.docs/tocodex/<date>— Claude writes, Codex reads.
- Append-only. New messages are appended to the end. Earlier bytes are never rewritten.
- Ownership is structural, not advisory. "Never edit the peer's mailbox" is the one rule that makes concurrent writing safe. Verified in the live run: one side made ~6 appends while the other's file stayed byte-identical.
3. Message format
Each message is a Markdown section:
## [<SENDER>, <HH:MM TZ>] <ID>: <one-line subject>
**in-reply-to:** <ID(s) this answers, or "—">
**acks:** [<IDs whose receipt this confirms>]
**task_id:** <Switchboard task ID, local packet ID, or "design-only">
**status:** <one line: e.g. "GO", "blocked on X", "working AC-00 ETA ~30m">
<body — use the sections in §3.1 as they apply>
**NEXT:** <who acts next> — <exactly what they do next>
3.1 Body sections (include those that apply)
- Verdict / decision — GO / GO-WITH-CHANGES / NO-GO / ACK.
- Verified facts — load-bearing claims checked against source, with
file:line. (§5) - Blocking items — numbered, each with the exact change required to clear it.
- Evidence — commit hashes, test results,
file:line, run IDs. Claims without evidence are hypotheses and must be labelled as such. - Rollback — feature flag, revert boundary, or why the packet is documentation-only.
- Questions — only those that materially block the plan, numbered for reply.
4. IDs and reply-chaining
- Stable, monotonic, directional IDs:
<SRC>2<DST>-<NNN>— e.g.C2C-003(Codex→Claude #3),CL2C-004(Claude→Codex #4). Never reuse or renumber. - Every message names what it answers (
in-reply-to) and what it confirms receipt of (acks). Citation is the read-receipt — you know the peer readCL2C-002when a message replies to it.
5. Verify before you accept
Load-bearing claims are checked against the primary source before acceptance, not taken on the
peer's word. In the live run this caught real errors on both sides: a "12 send sites" count that was
actually 10 (+1 inbound to exclude), and a record_audio_out-proves-playback assumption that was
false (it fires before ws.send). Cite file:line. When source contradicts a summary, trust the
source and say so.
6. Liveness — silence must never be ambiguous
The single biggest gap in an unadorned mailbox: a quiet peer is indistinguishable from a dead one. Three conventions fix it:
NEXT:closing line on every message — names who acts next and on what. Removes whose-turn ambiguity.STATUS:heartbeat when heads-down. If a side will be working silently for more than ~20 min, it appends a normal, uniquely identified message (status: authoring AC-00, ETA ~30m) so silence ≠ stall. Status messages obey the same append-only and reply-chain rules.- ~20-minute poll or milestone cadence, and no empty heartbeat chatter — a poll that finds nothing new produces no message; only real progress or a status/ETA gets written.
Out-of-band notification (a human relay, or a file-watch on mtime) is expected to sit on top of the poll — the protocol assumes the reader is pinged, but degrades gracefully to polling.
An interactive model cannot promise unattended continuity after its process or session stops. “Poll every 20 minutes” applies only while the worker is active; all-night continuity requires an external supervisor that restarts the worker, injects the newest unacknowledged message, and verifies that the worker actually executes it. A file watcher or heartbeat alone is not proof of work.
Live lesson: one side went silent ~1.5 h mid-negotiation. With no
NEXT:/STATUS:convention the peer could only poll blindly and eventually escalate to the human. A singleSTATUS: … ETAline would have removed the ambiguity entirely.
7. Immutability and corrections
- A delivered message is immutable. Do not edit a message after appending it.
- Correct by superseding: append a new message with
**supersedes:** <ID>. The record then shows both the original and the correction — history is never rewritten under the reader. - Detect drift by message count, not file length. In the live run a peer made an in-place tweak that changed byte count but not line count; a reader caching "same line count = unchanged" would have missed it. Track the last-seen message ID; compare mtime and size, never lines alone.
8. The index file (fast resume + rollover)
Optionally generate a short docs/MAILBOX.md (or leading INDEX block) from the
two mailbox tails:
last-message: CL2C-004
turn: Codex (owes AC-00 commit + fixture/schema evidence)
active-files: docs/toclaude/2025.07.12, docs/tocodex/2025.07.12
A resuming agent (or a monitor) reads one line instead of tailing a 350-line file. The index is a cache, never the ownership or approval source of truth. It has one named generator/coordinator; peers do not concurrently hand-edit a shared status file. If stale or missing, read the two owned mailboxes directly.
9. File naming and rollover
- Name mailbox files by real ISO date (
YYYY-MM-DD). Known debt: the live files are named2025.07.12while the actual date is 2026-07-12 — a latent bug. Do not rename active files mid-negotiation (both agents are pointed at them); fix at the next day/file boundary. - On rollover, the last message in the old file carries a
continued-in: <new-file>line and the new file opens withcontinues: <old-file>so a multi-day thread never silently fragments.
10. Authority boundary (who may approve what)
| Class | Approver |
|---|---|
| Local docs, local tests, fixtures, replay / dry-run, design + schema decisions, ownership splits | Peer agent (via this channel) |
| Narrow implementation packets with commit + test evidence | Peer agent, after the reconciled plan is agreed |
| OTA / live-call deployment, caller-facing changes | Human only — explicit GO |
| Secrets handling, destructive git (force-push, history rewrite, hard reset) | Human only |
This standing contract rides in every message ("no implementation until reconciled; no OTA without human GO") and is the reason a multi-hour autonomous exchange never touched production.
Peer approval does not override repository ownership boundaries, required fix
journal entries, focused tests, or the Switchboard lifecycle. A GO approves
the described packet, not unrelated files or a broader deployment.
11. Sensitive-data boundary
- Never place secrets, raw credentials, authentication codes, or
.envvalues in either mailbox. - Use synthetic/redacted evidence for caller text, slots, audio excerpts, and fixtures unless the repository's authorized evidence policy explicitly permits the retained artifact.
- Reference retained call IDs and artifact paths; do not copy full transcripts or audio into the negotiation merely for convenience.
- A mailbox being committed to a private repository is not a substitute for the repository's data-retention and access policy.
12. Anti-patterns
- ❌ Editing the peer's mailbox (breaks the one structural guarantee).
- ❌ Editing a delivered message in place (use
supersedes:). - ❌ Empty heartbeat messages ("still polling, nothing new").
- ❌ Accepting a load-bearing claim without checking source.
- ❌ Any human-gated side effect (§10) without an explicit human GO.
- ❌ Caching "no change" on line count alone.
- ❌ Treating a watcher, heartbeat, or mailbox append as proof that an AI read the task or executed work.
- ❌ Using peer
GOto bypass Switchboard ownership or a human-only gate. - ❌ Letting two agents stage or commit the same mailbox file.
13. Worked example (abbreviated, from the live run)
C2C-001 Codex → Claude review request (design as contract) NEXT: Claude — review
CL2C-001 Claude → Codex GO WITH REQUIRED CHANGES + 5 blockers NEXT: Codex — reconcile
C2C-002 Codex → Claude reconciled graph + 2 amendments; asks 5 NEXT: Claude — close schema
CL2C-002 Claude → Codex GO; 5 answers verified vs source NEXT: Codex — AC-00
CL2C-003 Claude → Codex readiness nudge (peer silent ~1h) NEXT: Codex — status/ETA
C2C-003 Codex → Claude accepts GO; 2 clarifications; hold AC-01 NEXT: Claude — ack, hold
CL2C-004 Claude → Codex ack; holding AC-01 NEXT: Codex — AC-00 commit
Outcome: Claude found blockers → Codex reconciled → Claude issued GO → both sides agree exactly who starts AC-00 and AC-01, with no production side effects and a full audit trail in git.