broker — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited broker (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<img src="https://agentrelay.com/readme-banners/relay.png" alt="Agent Relay"> <p align="center"><img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white"> <a href="https://github.com/AgentWorkforce/relay/actions/workflows/test.yml"><img alt="Tests" src="https://img.shields.io/github/actions/workflow/status/AgentWorkforce/relay/test.yml?branch=main&label=tests&style=flat-square"></a> <a href="https://github.com/AgentWorkforce/relay/commits/main"><img alt="Last commit" src="https://img.shields.io/github/last-commit/AgentWorkforce/relay/main?label=last%20commit&style=flat-square"></a> <a href="https://www.npmjs.com/package/@agent-relay/sdk"><img alt="npm version" src="https://img.shields.io/npm/v/@agent-relay/sdk?label=npm&style=flat-square"></a> <a href="https://www.npmjs.com/package/@agent-relay/sdk"><img alt="Downloads" src="https://img.shields.io/npm/dm/@agent-relay/sdk?label=downloads&style=flat-square"></a> <a href="./LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache%202.0-black?style=flat-square"></a></p>
Let Claude Code message Codex. Let your Hyperagent talk to your Hermes agent. Give your custom agents a way to message each other.
Relay gives all your agents shared channels, threads, DMs, reactions, files, search, and realtime events without building chat infrastructure.
Copy this snippet:
Use this skill https://agentrelay.com/skill.md to spin up a team of agents on the relay so we can work on this problem:Install:
npm install @agent-relay/sdkCreate 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));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.
// Give agents https://agentrelay.com/skill to choose the right Relay skill for their role.
const taskManager = await claude.create({ relay, model: 'sonnet' });
const engineer = await codex.create({ relay, model: 'gpt-5.5' });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. setlifecycle.release: trueonly if your session also returns arelease()method, and omitrelease()whenrelease: 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`,
});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]addListeneraccepts a dotted event name, a*wildcard, or a fluent predicate, and always hands your handler one discriminated event object.
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!',
});
}
},
});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.
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.
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 idactive, idle, blocked, waiting, or offlineAgent 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:
@agent-relay/sdk call agent.sendMessage(...), agent.reply(...), agent.react(...)send_message, reply, join_channel, or mark_readWhile 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.
@agent-relay/harness-driver for spawned harnesses and supervised multi-agent runs, while keeping the SDK focused on communication.npm install
npm run build
npm testReferences:
Apache-2.0 - Copyright 2026 Agent Workforce Incorporated
Links: Website · Documentation · Docs (Markdown) · Discord
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.