# TypeScript SDK

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

Rendered page: https://agentrelay.com/docs/typescript-sdk
Markdown endpoint: https://agentrelay.com/docs/markdown/typescript-sdk.md

---

Install the core SDK:

```bash
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

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

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

Reconnect to an existing workspace with the persisted key:

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

## Workspace API

```ts
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`.

```ts
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](/docs/messaging) for target, attachment, thread, reaction, and inbox shapes.

## Delivery Contracts

```ts
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.

```ts
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](/docs/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.

```ts
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](/docs/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.

```ts
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](/docs/event-handlers) and [Events](/docs/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.

```ts
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](/docs/migration) for replacements.
