Skip to content

Protocol Design

Overview

$mq9.AI.* is the protocol designed by mq9 for asynchronous Agent communication. The core problem it solves: asynchronous communication between Agents where the sender and receiver do not need to be online at the same time.

mq9 only handles the communication problem — how messages are reliably delivered. Message content is a byte array; mq9 does not parse, validate, or restrict it. Whether the upper layer uses A2A, MCP, or another protocol is none of mq9's concern.


mail_address Format Specification

Character set: lowercase letters (a-z), digits (0-9), dots (.)

Length: 1 to 128 characters

Case: strictly lowercase; a mail_address containing uppercase characters will be rejected by the broker

Position rules: . may only appear in the middle — the first and last characters must be a lowercase letter or digit; consecutive . are not allowed

Semantics: mail_address is an opaque string; . does not participate in protocol routing or matching and serves only as a visual grouping aid

Encoding: URL percent-encoding is not allowed

Valid examplesInvalid examples
task.001task-001 (contains hyphen)
agent.inboxtask_001 (contains underscore)
analytics.resultTask.001 (contains uppercase)
acme.org.task.queue.task.001 (leading dot)
session.20260502task.001. (trailing dot)
task..001 (consecutive dots)

Full address examples:

text
task.001
agent.inbox
analytics.result
acme.task.queue
session.20260502
order.processing.urgent
agent.001.inbox

Core Concepts

mail_address: The communication address defined by the user when creating a mailbox via MAILBOX.CREATE. It is not bound to an Agent identity — a single Agent may request different mail_addresses for different tasks. No cleanup is needed after use; TTL handles automatic expiry.

mail_address unguessability is the security boundary. Knowing a mail_address is sufficient to send messages or subscribe. Without the mail_address there is nothing to operate on. No tokens, no ACLs.

TTL: Declared at mailbox creation time; the mailbox is automatically destroyed on expiry and its messages are cleaned up along with it. Creating a mailbox with a duplicate name returns an error (mailbox xxx already exists); you can use QUERY to check whether a mailbox exists before creating it.

priority: Optional. If not specified, the default is normal priority; specifying urgent or critical raises the processing order. Same-priority messages follow FIFO; across priorities, higher priority is processed first. The storage layer guarantees ordering — consumers do not need to sort themselves.

msg_id: The unique identifier of each message (its offset in storage), used by clients for deduplication and deletion operations.

Consumption semantics: FETCH uses pull mode and supports both stateful consumption (pass group_name and the broker records the offset, resumable after reconnect) and stateless consumption (omit group_name, each call consumes independently according to the deliver policy with no offset recorded).

Message flow: Message arrives → written to storage → client actively calls FETCH to pull → ACK to confirm → broker advances the consumption offset for that group.


Protocol Overview

CategorySubjectDescription
Mailbox management$mq9.AI.MAILBOX.CREATECreate a mailbox
Messaging$mq9.AI.MSG.SEND.{mail_address}Send a message (priority via mq9-priority header)
Messaging$mq9.AI.MSG.FETCH.{mail_address}Fetch mailbox messages
Messaging$mq9.AI.MSG.ACK.{mail_address}Acknowledge a message
Messaging$mq9.AI.MSG.QUERY.{mail_address}Query messages in a mailbox
Messaging$mq9.AI.MSG.DELETE.{mail_address}.{msg_id}Delete a specific message
Agent management$mq9.AI.AGENT.REGISTERRegister an Agent
Agent management$mq9.AI.AGENT.UNREGISTERUnregister an Agent
Agent management$mq9.AI.AGENT.REPORTReport Agent status
Agent management$mq9.AI.AGENT.DISCOVERDiscover Agents

All commands use request/reply mode (nats request); the server always returns a response.


Response Format

Each command has its own response structure. All responses include an error field:

ValueMeaning
""Success
Non-empty stringFailure; the value is the error description

$mq9.AI.MAILBOX.CREATE

Create a mailbox with a user-defined mail_address.

Request Fields

FieldTypeRequiredDescription
namestringNoThe mail_address for the mailbox; must conform to the format specification. Auto-generated by the broker if omitted.
ttlu64?NoTime-to-live in seconds; 0 means never expires

Response Fields

FieldTypeDescription
errorstringEmpty on success; error message on failure
mail_addressstringThe mailbox address after successful creation

Example

bash
nats request '$mq9.AI.MAILBOX.CREATE' '{"name": "agent.translator.inbox", "ttl": 0}'
# Response
{"error":"","mail_address":"agent.translator.inbox"}

# Duplicate creation → error
nats request '$mq9.AI.MAILBOX.CREATE' '{"name": "agent.translator.inbox"}'
# Response
{"error":"mailbox agent.translator.inbox already exists","mail_address":""}

$mq9.AI.MSG.SEND.

Send a message to the specified mailbox. The payload is a byte array; mq9 does not parse the content.

Request Parameters

Subject: $mq9.AI.MSG.SEND.{mail_address}

Payload: Arbitrary byte array; mq9 does not parse the content.

Headers (all optional)

HeaderDescription
mq9-key: {key}Dedup/compaction key. For the same key, the storage layer retains only the latest message, overwriting older ones
mq9-delay: {seconds}Delay delivery by this many seconds. The message is not visible in FETCH results until the delay expires. Delayed messages return msg_id: -1
mq9-ttl: {seconds}Message-level TTL in seconds. The message expires at send_time + ttl, independent of the mailbox TTL
mq9-tags: {tag1},{tag2}Comma-separated user tags, e.g. billing,vip. Filterable via the tags field in QUERY
mq9-priority: {value}Message priority: normal (default) / urgent / critical. Defaults to normal if omitted

SEND Response Fields

FieldTypeDescription
errorstringEmpty on success; error message on failure (e.g., mailbox does not exist)
msg_idi64Storage offset assigned after write; -1 for delayed messages

Priority Description

ValueTypical use case
normal (default)Task dispatch, result return, status reporting
urgentApproval requests, important notifications
criticalTask interruption, emergency instructions

Same-priority messages follow FIFO; across priorities: critical > urgent > normal.

SEND Example

bash
# Normal message
nats request '$mq9.AI.MSG.SEND.agent.translator.inbox' '{"text":"hello"}'
# Response
{"error":"","msg_id":0}

# With key (storage retains only the latest message for the same key)
nats request '$mq9.AI.MSG.SEND.task.001.callback' \
  -H "mq9-key:status" \
  '{"status":"running"}'
# Response
{"error":"","msg_id":1}

# With tags (filterable via QUERY tags field)
nats request '$mq9.AI.MSG.SEND.agent.order.inbox' \
  -H "mq9-tags:billing,vip" \
  '{"order_id":"o-001"}'
# Response
{"error":"","msg_id":2}

# Delayed delivery — 60 seconds (msg_id -1 means delayed)
nats request '$mq9.AI.MSG.SEND.agent.translator.inbox' \
  -H "mq9-delay:60" \
  '{"text":"delayed task"}'
# Response
{"error":"","msg_id":-1}

# Message-level TTL — expires 300 seconds after send (independent of mailbox TTL)
nats request '$mq9.AI.MSG.SEND.agent.translator.inbox' \
  -H "mq9-ttl:300" \
  '{"text":"short-lived message"}'
# Response
{"error":"","msg_id":3}

# Urgent message (priority via header)
nats request '$mq9.AI.MSG.SEND.agent.translator.inbox' \
  -H "mq9-priority:urgent" \
  '{"alert":"please expedite"}'
# Response
{"error":"","msg_id":4}

$mq9.AI.MSG.FETCH.

Pull messages from a mailbox. Supports two consumption modes: stateful consumption (pass group_name and the server records the offset) and stateless consumption (omit group_name and each call starts independently according to the deliver policy).

FETCH Request Fields

FieldTypeRequiredDescription
group_namestring?NoConsumer group name. When provided, enables stateful consumption — members of the same group share an offset. When omitted, consumption is stateless: each call starts according to the deliver policy with no offset recorded.
deliverstringNoStarting point policy; defaults to latest. For stateful consumption, only takes effect when there is no existing offset record or when force_deliver: true.
from_timeu64?NoEffective when deliver: "from_time"; Unix timestamp in seconds
from_idu64?NoEffective when deliver: "from_id"; fetch starts from this msg_id (inclusive)
force_deliverbool?NoOnly valid for stateful consumption. When true, the existing offset is ignored and consumption restarts according to deliver
configobject?NoFetch behavior configuration; see below

deliver Policy

ValueDescription
latest (default)Only pull new messages from this point forward
earliestStart from the oldest message in the mailbox
from_timeStart from after a specified timestamp; requires the from_time field
from_idStart from a specified msg_id (inclusive); requires the from_id field

Offset Behavior for Stateful Consumption (group_name provided)

ConditionBehavior
Offset record exists and force_deliver: falseResume from the last checkpoint; deliver has no effect
Offset record exists and force_deliver: trueIgnore the offset and restart according to the deliver policy
No offset recordStart according to the deliver policy (first-time consumption)

Stateless Consumption (group_name omitted)

The server generates a temporary random group, uses the deliver policy to locate the starting point, and does not commit an offset after consumption. Suitable for inspection, debugging, or one-off reads.

config Fields

FieldTypeDefaultDescription
num_msgsu32?100Maximum number of messages to pull in a single call
max_wait_msu64?500How long the server waits when there is no data (milliseconds). Defaults to 500ms if omitted; 0 means return immediately without waiting. An empty list is returned when the wait expires, preventing clients from hammering the server in a tight poll loop.

FETCH Response Fields

FieldTypeDescription
errorstringEmpty on success; error message on failure (e.g., mailbox does not exist)
messagesarrayList of messages; each entry contains msg_id, payload, priority, and create_time

FETCH Example

bash
# Stateless consumption: no group_name, starts from the latest message each time (default deliver: latest)
nats request '$mq9.AI.MSG.FETCH.task.001.callback' '{}'

# Stateless consumption: full read from the earliest message each time
nats request '$mq9.AI.MSG.FETCH.task.001.callback' \
  '{"deliver": "earliest"}'

# Stateful consumption: resume from checkpoint if offset exists; first call pulls only new messages (default deliver: latest)
nats request '$mq9.AI.MSG.FETCH.task.001.callback' \
  '{"group_name": "worker-group-1"}'

# Stateful consumption: resume from checkpoint if offset exists; first call starts from earliest message
nats request '$mq9.AI.MSG.FETCH.task.001.callback' \
  '{"group_name": "worker-group-1", "deliver": "earliest"}'

# Stateful consumption: force reset offset and restart from earliest
nats request '$mq9.AI.MSG.FETCH.task.001.callback' \
  '{"group_name": "worker-group-1", "deliver": "earliest", "force_deliver": true}'

# Specify max messages per call (default is 100)
nats request '$mq9.AI.MSG.FETCH.task.001.callback' \
  '{"group_name": "worker-group-1", "config": {"num_msgs": 50}}'

$mq9.AI.MSG.ACK.

Confirm that a message has been processed; the broker advances the consumption offset for the consumer group.

ACK Request Fields

FieldTypeRequiredDescription
group_namestringYesConsumer group name, matching the one used in FETCH
mail_addressstringYesMailbox address
msg_idu64YesThe ID of the message to acknowledge (from the FETCH response)

ACK Response Fields

FieldTypeDescription
errorstringEmpty on success; error message on failure

ACK Example

bash
nats request '$mq9.AI.MSG.ACK.task.001.callback' \
  '{"group_name": "worker-group-1", "mail_address": "task.001.callback", "msg_id": 5}'
# Response
{"error":""}

$mq9.AI.MSG.QUERY.

Query messages currently stored in a mailbox. Does not affect subscription delivery.

QUERY Request Fields

FieldTypeDescription
keystring?Query by key; returns the latest message for that key
limitu64?Maximum number of messages to return
sinceu64?Return messages after this timestamp

QUERY Response Fields

FieldTypeDescription
errorstringEmpty on success
messagesarrayList of messages

QUERY Example

bash
# Query all messages
nats request '$mq9.AI.MSG.QUERY.task.001.callback' '{}'

# Query the latest message with key=status
nats request '$mq9.AI.MSG.QUERY.task.001.callback' '{"key": "status"}'

# Most recent 10 messages
nats request '$mq9.AI.MSG.QUERY.task.001.callback' '{"limit": 10}'

# Messages after a specific timestamp
nats request '$mq9.AI.MSG.QUERY.task.001.callback' '{"since": 1234567890}'

$mq9.AI.MSG.DELETE.{mail_address}.

Delete a specific message from a mailbox.

DELETE Example

bash
nats request '$mq9.AI.MSG.DELETE.task.001.callback.2' ''
# Response
{"error":"","deleted":true}

$mq9.AI.AGENT.REGISTER

Register an Agent. The body contains upper-layer protocol content (the current example uses an A2A AgentCard); mq9 does not interfere with the body and only requires that it carry a mailbox field as the routing identifier. The same applies if a different protocol is used in the future.

REGISTER Example

bash
nats request '$mq9.AI.AGENT.REGISTER' \
  '{ ...AgentCard, mailbox = "mq9://broker/agent.translator.inbox"... }'
# Response
{"error":""}

$mq9.AI.AGENT.UNREGISTER

Unregister an Agent.

UNREGISTER Example

bash
nats request '$mq9.AI.AGENT.UNREGISTER' \
  '{ ...mailbox = "mq9://broker/agent.translator.inbox"... }'
# Response
{"error":""}

$mq9.AI.AGENT.REPORT

Report Agent status. The body contains upper-layer protocol content; mq9 does not interfere.

REPORT Example

bash
nats request '$mq9.AI.AGENT.REPORT' \
  '{ ...mailbox = "mq9://broker/agent.translator.inbox", status fields defined by the upper-layer protocol... }'
# Response
{"error":""}

$mq9.AI.AGENT.DISCOVER

Search for registered Agents by criteria. Returns the raw list of registered content; mq9 does not transform or wrap it.

DISCOVER Request Fields

FieldTypeDescription
textstring?Full-text search query (keyword-based)
semanticstring?Semantic search query (natural language, vector-based). Takes priority over text when both are provided.
limitnumber?Maximum number of results per page (default 20)
pagenumber?Page number, starting from 1 (default 1)

When neither text nor semantic is provided, all registered Agents for the tenant are returned.

DISCOVER Example

bash
# Full-text search
nats request '$mq9.AI.AGENT.DISCOVER' '{"text": "payment invoice"}'

# Semantic search (vector-based, takes priority over text)
nats request '$mq9.AI.AGENT.DISCOVER' '{"semantic": "process a payment and generate invoice"}'

# Pagination: page 2, 10 results per page
nats request '$mq9.AI.AGENT.DISCOVER' '{"text": "payment", "limit": 10, "page": 2}'

# List all
nats request '$mq9.AI.AGENT.DISCOVER' '{}'
# Returns: [{ ...raw registered content... }, ...]

Error Reference

ScenarioResponse example
mailbox does not exist (SEND/SUB/QUERY/DELETE){"error":"mailbox xxx does not exist"}
mailbox already exists (CREATE is not idempotent){"error":"mailbox xxx already exists","mail_address":""}
msg_id does not exist (DELETE){"error":"message not found"}