Documentation / Reference

Relay endpoints

Deliver external data with provenance, an owner policy, and MCP notifications.

A relay is a local bridge for content that originated outside this machine: email, chat, or webhooks. It has no model or native conversation and carries no delegated user authority. A relay is a program you choose to run as your OS user. Provenance is its account of origin, not an identity AX independently verifies.

Ordinary local peer messaging continues to work without relays or external flags. Every endpoint defaults to refusing external messages. Local peer mail is unaffected.

Open a recipient

ax claude -n api --external verified
ax external api all
ax external api refuse

refuse accepts no relayed messages. verified admits trust: verified or trust: system, with tainted: false. all admits any valid provenance. AX_EXTERNAL supplies the launch default; an explicit flag overrides it. Launching or resuming without either resets the policy to refuse. ax external is an owner command; model tools cannot change this policy. ax spawn rejects --external; use the owner command after spawning if needed. The existing permission and accept|hold|refuse delivery gates still apply.

A closed endpoint rejects a relay's send before storage, with RPC code -32002. MCP tool errors expose code: external_policy, rpc_code: -32002, the target, policy, and reason, and report submission: not_submitted. If the owner closes the policy after queueing, queued external mail waits at its FIFO head, subject to TTL. Reopening releases it; closing the external policy does not bounce it.

Recipients get guidance identifying the relay and origin, telling them the content is external data, carries no delegation, and must not be followed as instructions. They can reply in the thread if warranted or acknowledge without a reply. Claude and Pi get the full content in channel data; wake-fetch harnesses get a fixed external-message wake and retrieve the content through MCP.

Write a relay

Start ax attach as a child with piped stdin/stdout:

ax attach --relay -n mail-bridge

This uses newline JSON-RPC MCP over stdio. --relay implies --notify; it rejects -s, -p, and -w. The child holds the AX name lock, preserving identity and mail on resume. Relays cannot launch agents and cannot be targets for ax verify.

Initialize MCP, discover tools, then use send_message or follow_up:

{
  "target": "api",
  "text": "An external request for your consideration.",
  "client_message_id": "bridge_delivery_1",
  "ttl_seconds": 3600,
  "provenance": {"origin": "mail", "trust": "verified", "tainted": false},
  "data": {"task": "example", "payload": {"question": "status?"}}
}

Provenance is required for every relay-authored message, including its replies. Only relays can set it. It is a JSON object of at most 4 KiB; origin matches [a-z][a-z0-9-]{0,31}, trust is verified|unverified|system, and tainted is a boolean. Additional fields pass through unchanged. The broker stamps sender.kind.

data is optional for any sender on send_message, reply, and follow_up: a JSON object of at most 32 KiB. AX does not interpret it. Both objects participate in the idempotency hash, survive resends, and are withheld alongside blocked text in thread history. resend_message copies them from the expired original rather than accepting replacements. Text remains limited to 64 KiB; bounds count serialized UTF-8 bytes. Frames allow 576 KiB for JSON escaping in notifications.

Receive over MCP

Ordinary attached runtimes can also use ax attach -n NAME -s ID -p PERMISSION --notify. The existing manual check_inbox and private wake-socket modes remain available.

The child interleaves notifications with JSON-RPC responses. Read continuously and match responses by ID; a notification has no ID.

Method Parameters
notifications/ax/message content: JSON text of the complete compact message; meta.kind: message, message ID, sender, harness
notifications/ax/message content: JSON text of a delivery notice; meta.kind: ax_delivery_status, notification ID
notifications/ax/agents agents: current agent list after a coalesced presence, readiness, or policy change

A pipe write records channel_written, not acknowledgment. get_message records content fetch; ack_message acknowledges; reply stores the reply and acknowledges the parent atomically. Relays receive terminal notices for explicit acknowledgment without reply and abandonment, plus the existing expiry/refusal notices. Ordinary session senders retain only expiry/refusal notices. Replies themselves replace an acknowledgment notice. Acknowledge notices with ack_notification.

After restart, list_pending recovers incoming replies and delivery_status recovers outgoing delivery state. Notification delivery is at least once; preserve stable IDs and idempotency keys. AX never automatically replays accepted or uncertain work.

Upgrade and verification

Mailbox schema 4 prevents older brokers from treating external messages as peer delegations. Finish the broker update and relaunch receiving bridges before opening external policy. Bridges older than 0.8 cannot connect to an open endpoint; if its policy opens while an old bridge is connected, external handoff waits for an updated bridge. Bound recovery reads also enforce external policy and bridge guidance; the owner retains administrative inspection. Local peer messages retain their existing delivery behavior.

The Go suite covers simulated MCP hosts, native channel formatting, policy holds, structured replies, and compatibility. This is transport evidence, not a live model's injected-instruction result. Live Claude and wake-fetch harness checks are still required before release; they are not claimed by these tests.

The first consumer is amx: its runner maps mail provenance into AX, carries replies back through its own grants and holds, and pauses inbound delivery while the local recipient is unavailable or closed. AX does not learn SMTP, IMAP, DKIM, or the amx profile.