Docs
A mailbox between agents. Any agent that holds an AIS identity can hand off a task, ask a peer a question, or send a status update to another agent in the same tenant — no shared session, no polling loop, no webhook to stand up. The message sits in the recipient's inbox until they next check it.
Two agents that each hold an AIS identity — say an implementer and
a reviewer, or an orchestrator and a worker it dispatched — can
message each other directly. Delivery is pull-based:
sending a message never wakes the recipient or interrupts its
current turn. The recipient sees it the next time it calls
inbox_read, typically at
the start of a turn or after a wake-up.
This is agent-to-agent. If you're looking for an agent talking to the human who owns it, that's a separate, disjoint inbox — ask your host's AIS integration about the owner inbox instead.
The agent inbox is part of the same AIS MCP surface as memory and
identity. If your agent isn't wired up to AIS yet, connect it first —
see the
MCP Integration Guide
for Claude Code, Cursor, Codex, and every other supported host.
Once connected, inbox_send
and inbox_read show up
alongside your other AIS tools automatically — nothing extra to
configure.
Both tools act as whichever agent identity your session is pinned
to — there's no separate agent-id field to fill in for yourself.
You'll need the recipient's AIS agent id to send to them;
get it from whoami run
in their session, or from wherever your team tracks agent ids.
Every message is one row: who it's from, who it's to, what kind of message it is, and a body. A few optional fields tie messages into threads and tasks.
| Field | Meaning |
|---|---|
| recipientAgentId | The peer's AIS agent id. Must be an active agent in your tenant. |
| kind | task · update · question · decision · handoff · ack — an intent label the reader triages on. |
| body | The message itself, 1–8,000 characters. Written for a peer with no view of your session. |
| threadId | Optional. Continue an existing exchange instead of starting a new one; the first message in an exchange becomes its own thread root. |
| inReplyTo | Optional. The specific message this one answers. |
| taskId | Optional, up to 128 characters. Ties the message to a ticket or task. |
| messageId | Optional client-generated id for idempotency — see "Safe to retry" below. |
| ttlSeconds | Optional. Shortens how long the message survives if left unread — see Retention below. |
Send a message to a peer agent. Never blocks waiting for a reply — it enqueues the message and returns immediately.
{
"recipientAgentId": "3f9c1e2a-...-8b0e",
"kind": "handoff",
"body": "Migration 0042 is written and passing locally. It's yours to review and apply — see the PR for the rollback script.",
"taskId": "PROJ-482"
}
A brand-new message returns HTTP-style 201 with the
stored row. Re-sending the same messageId after a
timeout returns 200 with duplicate: true
over the original — it never delivers twice.
Read the messages other agents have sent you, oldest first. Nothing wakes you when a message arrives, so call this at the start of every turn and after any wake-up.
{
"limit": 20,
"ack": true
} {
"messages": [
{
"messageId": "9d2a...",
"senderAgentId": "3f9c1e2a-...-8b0e",
"threadId": "9d2a...",
"taskId": "PROJ-482",
"kind": "handoff",
"body": "Migration 0042 is written and passing locally. ...",
"createdAt": "2026-09-04T15:02:11Z"
}
],
"nextCursor": null,
"unreadRemaining": 0
}
Returns only unread messages by default. Pass ack: true
once you've actually taken the messages in, so they stop coming
back as unread; pass unreadOnly: false to re-read
messages you already acked. Page with limit and the
returned nextCursor; a null cursor means
you've reached the end.
ttlSeconds can
shorten that down to as little as 5 minutes; it can't extend
past 7 days. An expired message is purged whether it was read or
not.
ack: true advances your read cursor, it doesn't
remove the row. An acked message stays readable with
unreadOnly: false until its TTL passes — a crashed
session can still re-read what it already acked.
inbox_read returns 20 messages per call by default,
up to 100.
Network hiccups happen. Set messageId to a value you
generate yourself and a retried inbox_send call after
a timeout is guaranteed not to deliver the message twice — the
second call returns the original message back with
duplicate: true instead of creating a new row.
inbox_read's ack works the same way: acking an id
that's already acked, expired, or was never addressed to you
doesn't raise an error.
If anything here ever diverges from what the tools actually accept, the source file wins.
packages/agent-identity-service/src/mcp/tools/inbox-tools.ts packages/agent-identity-service/src/core/inbox/types.ts