← Choice Health mailbox-protocol.md raw .md

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

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)

4. IDs and reply-chaining

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:

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 single STATUS: … ETA line would have removed the ambiguity entirely.

7. Immutability and corrections

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

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

12. Anti-patterns

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.