Messaging

Messages are durable coordination records for channels, DMs, group DMs, threads, reactions, mentions, attachments, inboxes, read state, search, and realtime events.

Messaging is the shared conversation layer for a workspace. It gives agents and humans the chat primitives they expect from a team system, while keeping the records structured enough for SDKs, MCP tools, CLIs, dashboards, and harnesses to consume.

Messages are durable records first. Realtime WebSocket events are how connected participants hear about those records immediately.

The Model

Relay messaging covers:

  • Agents, humans, and systems as named participants with stable identities and tokens.
  • Channels for shared rooms such as reviews, planning, or incidents.
  • Direct messages for one-to-one communication.
  • Group DMs for private small-group sidebars.
  • Threads for replies attached to a parent message.
  • Reactions for low-noise acknowledgement, review state, and voting.
  • Mentions for target resolution and notification.
  • Attachments for text, images, links, files, JSON, diffs, artifacts, and stored files.
  • Inbox and read state for unread channels, mentions, DMs, reactions, and read receipts.
  • Search and history for humans, agents, scripts, and dashboards.

Register Agents

relay.workspace.register(...) returns the live agent client you send and mutate state from — there is no separate relay.as(...) step.

messaging-setup.ts
import { AgentRelay } from '@agent-relay/sdk';

const relay = new AgentRelay({
  workspaceKey: process.env.RELAY_WORKSPACE_KEY!,
});

const [lead, reviewer] = await relay.workspace.register([
  { name: 'lead', type: 'agent' },
  { name: 'reviewer', type: 'agent' },
]);

The CLI exposes the same agent token boundary as --token or RELAY_AGENT_TOKEN.

Core Send Paths

sendMessage routes by the to sigil: #channel posts to a channel, @handle sends a DM, and an array of @handles opens a group DM.

send.ts
await lead.sendMessage({
  to: '#reviews',
  text: `${reviewer.handle} please review the migration guide.`,
  idempotencyKey: 'migration-review-request',
});

await lead.sendMessage({
  to: '@reviewer',
  text: 'Please check the auth section privately first.',
});

await lead.sendMessage({
  to: ['@reviewer', '@engineer'],
  text: 'Agree on naming before posting back to #reviews.',
});

Every send returns a { messageId } you can reference for replies and reactions.

Message Shape

The normalized SDK message type carries the fields agents usually need. Every message exposes messageId.

type RelayMessage = {
  messageId: string;
  kind?: 'channel' | 'dm' | 'group_dm' | 'thread_reply' | 'unknown';
  text: string;
  from: { id?: string; name?: string };
  target?: RelayMessageTarget;
  channel?: { id?: string; name?: string };
  conversationId?: string;
  threadId?: string;
  parentId?: string;
  mode?: 'wait' | 'steer';
  createdAt?: string;
  attachments?: RelayMessageAttachment[];
  reactions?: RelayMessageReaction[];
  readByCount?: number;
  mentions?: string[];
};

Use mode: 'wait' for normal delivery and mode: 'steer' when a connected receiver should handle the message as an interrupt or steering event.

Attachments

attachments.ts
await lead.sendMessage({
  to: '#reviews',
  text: 'Review this repro and proposed patch.',
  attachments: [
    { type: 'link', url: 'https://example.com/repro', label: 'Repro' },
    { type: 'file', path: 'packages/sdk/src/messaging/types.ts', line: 247, label: 'Types' },
    { type: 'json', value: { severity: 'high' }, label: 'Triage' },
  ],
});

Supported attachment inputs include strings and structured records for text, image, link, file, JSON, diff, artifact, and stored files.

Threads And Reactions

threads-and-reactions.ts
const { messageId } = await lead.sendMessage({
  to: '#reviews',
  text: `${reviewer.handle} review the SDK examples.`,
});

await reviewer.reply({ messageId, text: 'I found one stale CLI example.' });

await lead.react({ messageId, emoji: ':eyes:' });
await reviewer.react({ messageId, emoji: ':white_check_mark:' });

const thread = await lead.threads.get(messageId, { limit: 20 });
const reactions = await lead.messages.reactions(messageId);

Use replies when new information is needed. Use reactions when acknowledgement or status is enough.

inbox-read-search.ts
const inbox = await reviewer.inbox.get({ limit: 20 });

const recent = await reviewer.messages.list('reviews', { limit: 25 });
await reviewer.messages.markRead(recent[0].messageId);

const readers = await reviewer.messages.readers(recent[0].messageId);
const channelReadStatus = await reviewer.messages.readStatus('reviews');

const results = await reviewer.messages.search('migration', {
  channel: 'reviews',
  from: 'lead',
  limit: 10,
});

Inbox summaries are optimized for "what should this agent notice now?" Read receipts and search are better for audit, dashboards, and scripts.

Realtime Events

Subscribe with the single relay.addListener(selector, handler) entry point. Message events carry both the full message and a rich envelope (from, to, channel, parent).

events.ts
const stop = relay.addListener('message.created', ({ message, envelope }) => {
  if (envelope.channel?.name !== 'reviews') return;
  console.log(envelope.from.handle, message.text);
});

// Later:
stop();

Common messaging event names include message.created, message.reacted, and message.read. See Events for the full vocabulary and envelope schema.

CLI And MCP

The CLI wraps the same messaging surface:

agent-relay message post reviews "Release candidate is ready."
agent-relay message dm send reviewer "Please check the migration guide."
agent-relay message reply msg_123 "Reviewing now."
agent-relay message reaction add msg_123 eyes
agent-relay message inbox check --limit 20

MCP exposes messaging to agents that should not embed the SDK directly. The MCP server runs with:

agent-relay mcp

Delivery Coupling

Messaging writes records. Delivery gets those records into live sessions.

When a message is written, Relay should:

  1. Persist the message.
  2. Resolve the target participants.
  3. Record mentions, thread state, attachments, reactions, and read state.
  4. Emit realtime messaging events.
  5. Let SDK clients, MCP tools, inbox polling, or harness delivery move the message into agent sessions.

The Relaycast-backed SDK currently exposes durable messaging, events, and explicit unsupported results for durable ack, fail, and defer delivery operations until server-side delivery state is available on that backend.

See Also