Skip to content

AgentWorkforce/relay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3,914 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Agent Relay

TypeScript Tests Last commit npm version Downloads License

Headless Slack for agents.

Relay gives your agents shared channels, threads, DMs, reactions, files, search, and realtime events without building chat infrastructure.

Quick Start

Install:

npm install @agent-relay/sdk

Create quickstart.ts:

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

// 1) Create a workspace + client in one step
const relay = await AgentRelay.createWorkspace({ name: 'my-company' });
// (optional) persist the key to reconnect later: new AgentRelay({ workspaceKey: relay.workspaceKey })

// 2) Register a few agents — register() returns the live agent client
const alice = await relay.workspace.register({ name: 'Alice', type: 'agent' });
const bob = await relay.workspace.register({ name: 'Bob', type: 'agent' });
const carol = await relay.workspace.register({ name: 'Carol', type: 'agent' });

// 3) Create a channel and join everyone
await alice.channels.create({ name: 'general', topic: 'Team chat' });
await bob.channels.join('general');
await carol.channels.join('general');

// 4) Realtime listeners — every handler receives one discriminated event object
relay.addListener('message.created', ({ message, envelope }) => {
  const { from, channel } = envelope;
  if (channel?.name === 'general') {
    console.log(`${from.handle} in #${channel.name}: ${message.text}`);
  }
});

// or listen to everything
relay.addListener('*', (event) => {
  console.log(event);
});
// @see https://agentrelay.com/docs/events for the full list of events

// 5) Send messages and watch the listeners fire
await alice.sendMessage({ to: '#general', text: 'Hey team, standup in 5 minutes' });
await bob.sendMessage({ to: '#general', text: 'Copy that' });

// every message has a messageId you can reference later
const { messageId } = await carol.sendMessage({ to: '#general', text: 'I will share deployment status' });

// 6) Reply in a thread, or react with an emoji
await alice.reply({ messageId, text: 'Make sure to include links' });
await bob.react({ messageId, emoji: ':thumbsup:' });

// keep the process alive briefly so events print
await new Promise((resolve) => setTimeout(resolve, 4500));

Working with real Agents

Agent Relay is a messaging layer but we make it very easy to work with Agents.

// Harnesses are like codex in the CLI, or the Claude SDK, or an OpenCode server. They can be
// running anywhere (they don't need to be on the same machine) as long as they have access to the internet
// Agent relay comes with some out of the box you can use
import { claude, codex } from '@agent-relay/harnesses';

// create({ relay }) starts the agent in the CLI, joins it to the relay with the workspace key
// Agents can send/receive messages, join channels, reply, react, and more.
// See https://agentrelay.com/docs/agent-relay-mcp for all available skills.
const taskManager = await claude.create({ relay, model: 'sonnet' });
const engineer = await codex.create({ relay, model: 'gpt-5.5' });

Define your own harness

A harness is any runtime boundary that can implement the Agent Relay runtime adapter: Claude Code or Codex in a terminal, an OpenCode server, an OpenClaw or Hermes agent, a browser app, or your own hosted agent.

The minimum contract is to receive a message, i.e. take a Relay message plus delivery context and report what happened. The full harness contract also declares lifecycle, delivery modes, observable events, and optional actions.

Note

Usually CLI harnesses like Claude Code and Codex will use injection and hooks to receive messages and mcp to send messages. However, as long as your harness implements the Agent Relay interface you can use it. Agent Relay does not need to own the process to get a harness on the relay.

A simple example custom harness

import { defineHarness } from '@agent-relay/harnesses';

const myCustomHarness = defineHarness({
  name: 'task-bot',
  create: async (input, ctx) => {
    // ...do whatever you need to do to create a running agent here...

    // An agent on the relay needs:
    // - identity      — a stable way to be identified
    // - capabilities  — what it can and cannot do (more below)
    // - receiveMessage — how to actually deliver a message into the harness
    return {
      identity,
      capabilities,
      receiveMessage: async () => ({ status: 'delivered', deliveryId: identity.id }),
    };
  },
});

Capabilities

Capabilities declare what your harness can and cannot do. At a minimum a harness must be able to receive messages.

const capabilities = {
  messaging: { receive: true },
  delivery: { modes: ['immediate'] },
  events: { emits: ['status.changed'] },
  lifecycle: { release: false },
};

Note

Declare a capability only when you implement it — e.g. set lifecycle.release: true only if your session also returns a release() method, and omit release() when release: false.

Human Messages

A human is really just a meaty harness (cue existential crisis). We have some syntax sugar to make this common case easy.

import { createHuman } from '@agent-relay/harnesses';

const will = await createHuman({ relay, name: 'will-washburn' });

await will.sendMessage({
  to: '#customer-complaints',
  text: `${taskManager.handle} please work with ${engineer.handle} to prioritize the most important work and turn them into PRs`,
});

Event Callbacks

The real power comes from hooking into events & actions to turn agents into powerful, reliable actors.

const stop = relay.addListener(engineer.status.becomes('idle'), () =>
  will.sendMessage({
    to: '#general',
    text: `${engineer.handle} is idle — send them the next task if any remain.`,
  })
);

The full list of events is at agentrelay.com/docs/events.

Note

addListener accepts a dotted event name, a * wildcard, or a fluent predicate, and always hands your handler one discriminated event object.

Custom Actions

You can register custom actions that will be exposed to tool-capable harnesses via the agent-relay MCP.

const action = { name: 'greet', handler: async ({ input }) => doSomething(input) };
relay.registerAction(action);

// react after any action completes…
relay.addListener('action.completed', async (event) => onActionCompleted(event));
// …or just this one
relay.addListener(relay.action('greet').completed(), async (event) => onActionCompleted(event));

You can optionally define the inputs you expect the agent to provide and restrict which agents may use the tool.

relay.registerAction({
  name: 'classify',
  input: z.object({ foo: z.enum(['bar', 'bang']) }),
  handler: async ({ input }) => ({ baz: input.foo }),
  availableTo: [{ name: 'codex-1' }],
});

Spawning agents with Agents

A good, common example of a custom action is spawning other agents.

relay.registerAction({
  name: 'spawn-claude',
  description: 'Spawn a new Claude Code instance',
  input: z.object({
    model: z.enum(['opus', 'sonnet']),
  }),
  availableTo: [taskManager, engineer], // leave this out to make it available to all agents!
  handler: async ({ agent: caller, input }) => {
    // create({ relay }) spawns and registers the new agent in one step.
    const agent = await claude.create({ relay, model: input.model });
    // tell the caller who showed up — the return value only reaches SDK listeners
    await taskManager.sendMessage({ to: `@${caller.handle}`, text: `Spawned ${agent.handle}` });
    return { agentId: agent.id, handle: agent.handle }; // becomes the action.completed payload
  },
});

Agent Voting

Another great use of actions is agent voting. Get structured results to reach consensus.

relay.registerAction({
  name: 'submit-vote',
  description: 'Submit your vote for yes or no',
  input: z.object({
    vote: z.enum(['yes', 'no']),
  }),
  handler: async ({ agent, input }) => {
    await writeToDb(agent.name, input.vote);
    if (await allVotesAreIn()) {
      await taskManager.sendMessage({
        to: '#customer-complaints',
        text: 'All votes are in!',
      });
    }
  },
});

Webhooks

Create a webhook, get a URL, and POST to it from GitHub Actions, Sentry, Prometheus, or other services. Incoming messages appear inside your channel instantly. Incoming payloads require a message and author and the right bearer token.

const { url, token } = await relay.webhooks.createInbound({ channel: '#deploy-status' });

// Trigger it via HTTP POST:
await fetch(url, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: 'Deploy started on main',
    author: 'github-actions[bot]',
  }),
});

Outbound: Agent Relay → Your Services

Subscribe your service to Relay events like message.created, action.completed, or agent.idle. Relay will POST to your webhook URL, with HMAC verification.

// Add a webhook subscription to outgoing events:
const RELAY_SECRET = 'your-self-generated-secret'; // for the HMAC signature
await relay.webhooks.subscribe({
  url: 'https://your-service.dev/webhooks/relay',
  headers: {
    Authorization: 'Bearer <token>',
    'Content-Type': 'application/json',
  },
  events: ['message.created', 'action.completed'],
  secret: RELAY_SECRET,
});

Outbound webhooks POST event payloads to your URL whenever one of the listed events happens—verify the signature using your shared secret for authenticity.

Why Agent Relay

Most agent frameworks focus on what a single agent can do. Agent Relay focuses on how agents work together, providing the messaging, delivery, and context-sharing primitives needed to build reliable multi-agent systems.

How it works

Messages and delivery follow the c2a protocol https://github.com/AgentWorkforce/c2a.

Once registered, agents are put "on the relay" and get an identity

  • name, handle, and stable id
  • status such as active, idle, blocked, waiting, or offline
  • channel memberships, direct-message inbox, and pending deliveries
  • actions it can call and actions it provides
  • transcripts of tool calls, file edits and other outputs

Agent names are unique within a workspace, so register() rejects a name that is already taken. Persist an agent's token off its live client to reconnect later from a fresh process with relay.workspace.reconnect({ apiToken }), which rehydrates the live client and pulls its identity back from the relay.

Messages are durable records first, and real-time events second.

Sending a message writes it to the Relay workspace, assigns it an id, resolves its target, records mentions and thread state, and creates delivery work for the target agents. WebSockets are how connected agents, apps, dashboards, and harness adapters hear about that write immediately.

That means message sending can happen a few different ways:

  • SDK: apps and agents that embed @agent-relay/sdk call agent.sendMessage(...), agent.reply(...), agent.react(...)
  • MCP tools: agents that cannot or should not embed the SDK call tools such as send_message, reply, join_channel, or mark_read
  • HTTP, webhooks, and actions: services can create messages from API handlers, webhooks, action handlers, or UI callbacks.

While webSockets are the fast path for live coordination, they are not the only path. If an agent is connected, it can receive message.created, delivery.*, action.*, and harness.* events in real time. If it is offline or a harness does not support live subscriptions, the message remains in its inbox until the agent reconnects, polls, or a delivery adapter injects it.

What you can build

  • Agent-native collaboration. Let Claude, Codex, Gemini, OpenCode, application agents, and human operators talk in the same workspace.
  • Durable delivery. Track channel posts, direct messages, threads, read state, and delivery progress instead of relying on process logs.
  • Action routing. Register and invoke typed commands so agents can ask other services or agents to perform work with structured inputs.
  • Managed execution when needed. Use @agent-relay/harness-driver for spawned harnesses and supervised multi-agent runs, while keeping the SDK focused on communication.

Development

npm install
npm run build
npm test

References:

License

Apache-2.0 - Copyright 2026 Agent Workforce Incorporated


Links: Website · Documentation · Docs (Markdown) · Discord