Install the core SDK:
npm install @agent-relay/sdk zodThe 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 });Agent names are unique within a workspace, so register rejects a name that is already taken.
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.
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';
relay.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.handle}`,
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 send_message, reply, 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';
relay.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.handle}`, 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.