Skip to content

Inbox / Outbox Messaging

OACP agents communicate through point-to-point async messaging. Messages are YAML files written to filesystem directories — no broker, no daemon, no network calls. Each agent has an inbox/ for incoming messages and an outbox/ for sent copies.

Every agent registered in a project gets its own directory pair:

$OACP_HOME/projects/<project>/agents/
claude/
inbox/ # messages TO claude
archive/ # processed messages (moved here at terminal processing)
outbox/ # copies of messages FROM claude
codex/
inbox/
archive/
outbox/
cursor/
inbox/
archive/
outbox/

Messages are plain YAML files. Any tool that can read and write files can participate in the protocol.

Every message is a single YAML file with the following schema:

FieldRequiredDescription
idyesUnique identifier (UUID recommended)
fromyesSender agent name
toyesRecipient agent name (or list for broadcast)
typeyesOne of the 12 message types below
priorityyesP0 through P3
created_at_utcyesISO 8601 timestamp
subjectyesShort summary (one line)
bodyyesMessage content (free-form text or structured YAML)
expires_atnoISO 8601 expiration timestamp
channelnoLogical channel or project context
related_packetnoReference to a related work packet
related_prnoPull request URL or number
conversation_idnoThread identifier for multi-message conversations
parent_message_idnoID of the message this replies to
context_keysnoList of context references for the recipient
id: "a1b2c3d4-5678-9abc-def0-1234567890ab"
from: claude
to: codex
type: review_request
priority: P1
created_at_utc: "2025-01-15T14:30:00Z"
subject: "Review auth middleware PR"
body: |
PR #42 adds JWT validation middleware.
Focus areas: token expiry handling, error responses.
related_pr: "https://github.com/kiloloop/oacp/pull/42"
conversation_id: "review-pr-42"

Since v0.4.0, a message may carry an optional Ed25519 signature as its final physical line — a detached-JWS auth trailer signed over the exact raw bytes of everything before it. Receivers verify against their own local key pins before parsing, with no network at verify time. Verification runs in the mode each receiver configures: warn annotates and never rejects; enforce (since v0.4.2) verifies at message intake and quarantines anything unverified to dead_letter/ before it is processed. The wire format, trust root, and key management live on the Message Signing page.

OACP defines 12 message types across three categories:

TypePurposeExpected response
task_requestAsk an agent to perform workCompletion notification or follow_up
questionAsk for information or clarificationReply with answer via follow_up
notificationOne-way status updateNone required
follow_upContinue an existing conversationDepends on context
handoffTransfer ownership of a taskhandoff_complete
handoff_completeConfirm receipt of handoffNone
TypePurposeExpected response
review_requestAsk for code reviewreview_feedback or review_lgtm
review_feedbackReturn review findingsreview_addressed
review_addressedRespond to feedbackreview_request (next round)
review_lgtmApprove the changeNone
TypePurposeExpected response
brainstorm_requestSolicit ideas or analysisbrainstorm_followup
brainstorm_followupRespond with ideasFurther brainstorm_followup or notification

See Review Loop for the complete code review workflow.

A message follows six steps from creation to completion:

  1. Create — Sender constructs the YAML message with a unique id.
  2. Deliver — Sender writes the file to the recipient’s inbox/ directory.
  3. Archive — Sender writes a copy to their own outbox/ directory.
  4. Read — Recipient reads the message from their inbox/.
  5. Archive — At terminal processing, the recipient atomically moves the exact processed file — without overwriting — to inbox/archive/ under its original filename, bytes preserved. If archival fails or the live bytes changed since verification, the message stays pending in inbox/. (Before v0.4.3, recipients deleted processed messages; the single cross-runtime archive destination replaces that.)
  6. Reply — Recipient creates a new message (linking via parent_message_id) and delivers it to the original sender’s inbox/.

Messages are immutable once written. Replies are always new messages — never edits to existing files.

History is bounded by oacp retention (since v0.4.3): outbox copies, dead-letter quarantine, and processed archives are pruned on age or count bounds. Quarantine evidence in dead_letter/ is excluded by its durable filename format, and an adjacent .retain marker protects a manually managed dead-letter fixture.

OACP is poll-based. There is no real-time push mechanism.

Agents should check their inbox:

  • At session start
  • After completing each major task
  • On a regular interval if running continuously

This keeps the protocol simple and compatible with any agent runtime, including those that run as batch jobs.

When multiple messages are waiting in an inbox, process them in this order:

  1. Priority firstP0 before P1 before P2 before P3.
  2. Type tiebreak — At the same priority, task_request and review_request take precedence over other types.
  3. Age tiebreak — Oldest messages first (by created_at_utc).

Multi-message exchanges use three fields to maintain context:

  • conversation_id — A shared identifier for all messages in a thread. Set by the initiator; subsequent messages reuse the same value.
  • parent_message_id — The id of the message being replied to. Creates a direct reply chain.
  • context_keys — A list of references (file paths, URLs, prior message IDs) that the recipient may need.

Set conversation_id on the first message. Omit parent_message_id (there is no parent yet).

Copy conversation_id from the message you are replying to. Set parent_message_id to that message’s id. Add any relevant context_keys.

conversation_id: "review-pr-42"
parent_message_id: "a1b2c3d4-5678-9abc-def0-1234567890ab"
context_keys:
- "pr:42"
- "file:src/middleware/auth.ts"

The to field accepts a list to send the same message to multiple agents:

to:
- codex
- gemini
- claude

When broadcasting:

  • One copy is written to each recipient’s inbox/.
  • One copy is written to the sender’s outbox/.
  • Maximum 10 recipients per broadcast.
  • handoff and handoff_complete are point-to-point only and cannot be broadcast.