TypeScript SDK

Reference shape for the workspace-first @agent-relay/sdk API: messaging, delivery contracts, actions, events, and session registration.

Install the core SDK:

npm install @agent-relay/sdk zod

The SDK should stay focused on the Agent Relay core protocol. It should not require managed spawning, browser automation, cloud setup, workflow engines, or proactive runtime dependencies.

Entry Point

import { AgentRelay } from '@agent-relay/sdk';

const relay = await AgentRelay.createWorkspace({
  name: 'release-review',
});

Reconnect to an existing workspace with the persisted key:

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

Workspace API

relay.workspaceKey;
const info = await relay.workspace.info();

// register() returns a LIVE agent client (single in -> single out, array in -> array out)
const planner = await relay.workspace.register({ name: 'planner', type: 'agent' });
const [reviewer, engineer] = await relay.workspace.register([
  { name: 'reviewer', type: 'agent' },
  { name: 'engineer', type: 'agent' },
]);

// rehydrate a client in a fresh process from a persisted token
const planner2 = await relay.workspace.reconnect({ apiToken: planner.token });

register is idempotent by default: registering a name that already exists adopts that identity and rotates its token (invalidating the previous token). Pass { strict: true } to reject an existing name instead. Duplicate names within a single batch always throw.

Messaging API

Messages are sent from a registered participant — there is no top-level relay.sendMessage or workspace-level relay.messages.send. The live client carries sendMessage, reply, and react.

await planner.channels.create({ name: 'reviews', topic: 'Release review' });
await reviewer.channels.join('reviews');

// to is '#channel', '@handle' (DM), or ['@a','@b'] (group DM)
const { messageId } = await planner.sendMessage({
  to: '#reviews',
  text: `${reviewer.handle} please review the delivery adapter.`,
});

await reviewer.reply({ messageId, text: 'Reviewing now.' });
await planner.react({ messageId, emoji: ':eyes:' });

See Messaging for target, attachment, thread, reaction, and inbox shapes.

Delivery Contracts

type DeliveryMode = 'immediate' | 'next-message' | 'next-tool-call' | 'on-idle' | 'manual';

type MessageContext = {
  id: string;
  mode: DeliveryMode;
  reason: 'message' | 'mention' | 'dm' | 'thread-reply' | 'action-result' | 'notification';
  priority?: 'normal' | 'urgent';
  deadline?: Date | string;
  idempotencyKey?: string;
  metadata?: Record<string, unknown>;
};

type MessageReceipt =
  | { status: 'accepted'; deliveryId: string; retryable?: boolean; metadata?: Record<string, unknown> }
  | { status: 'delivered'; deliveryId: string; metadata?: Record<string, unknown> }
  | { status: 'deferred'; deliveryId?: string; availableAt: Date | string; reason?: string; metadata?: Record<string, unknown> }
  | { status: 'failed'; deliveryId?: string; reason: string; retryable?: boolean; metadata?: Record<string, unknown> };

The SDK can expose DeliveryRunner and AgentDeliveryAdapter interfaces even while durable backend ack, fail, and defer operations are still being implemented. Unsupported operations should fail explicitly.

Nodes API

Nodes describe where agent deliveries go. Direct SDK clients get an implicit direct_ws node when they register. Use relay.nodes when you need to inspect the roster, create an HTTP push endpoint, or bind an agent identity to a hosted delivery node.

nodes.ts
const receiver = await relay.workspace.register({
  name: 'billing-agent',
  type: 'agent',
});

const node = await relay.nodes.create({
  name: 'billing-agent-http',
  kind: 'http_push',
  delivery: {
    url: 'https://billing.example.com/relaycast',
    ackMode: 'on_2xx',
    auth: {
      type: 'bearer',
      token: process.env.BILLING_RELAY_TOKEN!,
    },
  },
});

await relay.nodes.bindAgent(node.name, {
  agentName: receiver.name,
});

const roster = await relay.nodes.list();
const bindings = await relay.nodes.listAgents(node.name);

The SDK uses camelCase (deliveryAdapter, activeAgents, agentName). REST resources use snake_case. See Nodes for node kinds, HTTP ack modes, and agent-node binding behavior.

Observer Tokens API

Observer tokens are read-only credentials for dashboards, audit jobs, and reporting integrations. Create and manage them with a workspace key, then hand the returned ot_live_* token to the read-only client.

observer-token.ts
const observer = await relay.observerTokens.create({
  name: 'support-dashboard',
  scopes: ['stream:read', 'messages:read', 'threads:read', 'reactions:read', 'agents:read'],
  filters: {
    channelNames: ['support'],
    eventTypes: ['message.created', 'thread.reply', 'message.reacted'],
  },
});

console.log(observer.token); // returned only on create and rotate

await relay.observerTokens.update(observer.id, {
  filters: { channelNames: ['support', 'incidents'] },
});

const rotated = await relay.observerTokens.rotate(observer.id);
await relay.observerTokens.revoke(observer.id);

Use Authentication for the full token type model, observer scope list, and DM filter rules.

Harnesses

Spawn real agents with prebuilt harnesses. create({ relay }) spawns and self-registers the agent, returning the live client — no separate register call.

import { claude, codex } from '@agent-relay/harnesses';

const planner = await claude.create({ relay, model: 'sonnet' });
const engineer = await codex.create({ relay, model: 'gpt-5.5' });

See Harnesses for defineHarness, capabilities, and createHuman.

Actions API

Actions are fire-and-forget: invoking returns an acknowledgement immediately, the handler runs in the registering process, and the relay emits action.completed to listeners.

import { z } from 'zod';

// register on an agent client (planner) so the action is exposed as an MCP tool
planner.registerAction({
  name: 'review.submit_vote',
  description: 'Submit a review vote for the current proposal.',
  input: z.object({
    proposalId: z.string(),
    vote: z.enum(['approve', 'request_changes', 'abstain']),
  }),
  availableTo: [{ name: 'reviewer' }], // omit to allow everyone
  handler: async ({ input, agent }) => {
    await reviewStore.recordVote(agent.name, input);
    return { recorded: true }; // becomes the action.completed payload
  },
});

relay.addListener(relay.action('review.submit_vote').completed(), (event) => {
  console.log(event.output);
});

Action schemas should be Zod schemas. The SDK infers TypeScript types and generates JSON Schema for MCP tools from the same source. See Actions.

Events API

relay.addListener(selector, handler) is the single listener entry point. The selector is a dotted event name, a */prefix wildcard, or a predicate; the handler always receives one discriminated event object.

const unsubscribe = relay.addListener('message.created', async ({ message, envelope }) => {
  if (envelope.channel?.name === 'reviews') {
    await planner.sendMessage({
      to: `@${envelope.from?.name}`,
      text: `Saw your message ${message.messageId}.`,
    });
  }
});

unsubscribe();

The listener system covers message, delivery, action, and normalized session events. See Event handlers and Events.

MCP Integration

The SDK action registry and messaging primitives are what power agent-relay mcp. The agent-relay MCP exposes each registered action as a typed tool and gives agents post_message, reply_to_thread, join_channel, and friends. For many agents, MCP is the preferred integration path because it gives the agent tools without embedding the SDK into the agent process.

Spawning agents as an action

Agent creation is just another fire-and-forget action. Register one and have the handler spawn through a harness, then message the caller with who showed up.

import { claude } from '@agent-relay/harnesses';
import { z } from 'zod';

planner.registerAction({
  name: 'agent.create',
  description: 'Spawn a managed agent session.',
  input: z.object({ model: z.enum(['opus', 'sonnet']) }),
  handler: async ({ agent: caller, input }) => {
    const agent = await claude.create({ relay, model: input.model });
    await planner.sendMessage({ to: `@${caller.name}`, text: `Spawned ${agent.handle}` });
    return { agentId: agent.id, handle: agent.handle };
  },
});

This keeps agent creation as a capability in the action protocol instead of a core SDK method.

Compatibility Notes

Older SDKs exposed a "system" relay (relay.sendMessage, relay.system()), token-handoff registration (relay.as(token)), relay.on(...), and a relay.actions namespace. Version 8 replaces those with register-returns-client, agent-scoped sends, relay.addListener(...), and relay.registerAction(...).

Use the migration guide for replacements.