agent-relay-server
v0.4.29
Published
Lightweight HTTP message relay for inter-agent communication across machines
Maintainers
Readme
Agent Relay
A lightweight HTTP message bus that lets AI coding agents talk to each other across sessions, projects, and machines.

Why
You're running three Claude Code sessions: one debugging a backend, another writing tests, a third reviewing code on a different machine. They can't talk to each other. Agent Relay fixes that.
- Direct messaging:
"tell the backend agent to check the migration" - Capability routing: send to
cap:reviewand any agent with that skill picks it up - Labels: name your sessions (
"backend fixing") and address them naturally - Claim-based tasks: post a task, exactly one agent grabs it (atomic, no duplicates)
- Threading: full conversation chains between agents
- Live dashboard: agents, messages, and stats in real time
- Closed-loop tasks: external systems can create deduped work items agents claim, progress, resolve, and report back through callbacks
How It Works
┌──────────────────┐ ┌──────────────────┐
│ Claude Code │ ┌────────┐ │ Scripts / CI │
│ Codex │◄───────►│ Relay │◄───────►│ Monitoring │
│ Any HTTP client │ HTTP │ Server │ HTTP │ Support desks │
└──────────────────┘ └────────┘ └──────────────────┘Agent Relay has two parts:
- The relay server - a single Bun process with SQLite. Stores agents, messages, tasks, and presence. Exposes a plain HTTP API and a live dashboard.
- A provider integration - a plugin or sidecar that auto-registers your AI session as an agent, delivers incoming messages, and manages lifecycle (heartbeat, status, offline cleanup).
Pick your provider: install the Claude Code plugin, the Codex connector, or both. The shared config is the same four env vars either way. See the full mental model for routing, identity, and task lifecycle details, or the provider spec if you want to build your own integration.
Extension Model
Agent Relay is built around four extension points:
Agents do work. Providers host agents. Integrations create work. Channels carry conversations.
That split keeps the system small without making everything a one-off plugin:
- Agents are running AI sessions or workers that can receive messages, claim tasks, and produce results.
- Providers connect an AI runtime to the relay. The Claude plugin and Codex connector are providers.
- Integrations connect external systems that create or update work. CI, monitoring, support desks, and deployment tools fit here.
- Channels connect communication surfaces where humans and systems already talk. Telegram is the first channel; Slack, email, SMS, Matrix, Discord, and web chat are natural next ones.
If you want to contribute, pick the shape that matches your system. Building a new AI runtime adapter? Build a provider. Sending alerts or tickets into Agent Relay? Build an integration. Opening Agent Relay to another messaging app? Build a channel.
Quick Start
1. Start the relay server
# requires Bun (https://bun.sh)
bunx agent-relay-server@latestDashboard at http://localhost:4850.
2. Connect your agents
Claude Code (requires 2.1.105+):
claude plugin marketplace add edimuj/agent-relay
claude plugin install agent-relay@agent-relayCodex (requires Bun + Codex CLI):
curl -fsSL https://unpkg.com/agent-relay-codex@latest/install-codex.sh | bash
# restart your shell, then:
codex-relayWindows: irm https://unpkg.com/agent-relay-codex@latest/install-codex.ps1 | iex
3. Done. It's autonomous
There is no step 3. Both integrations activate themselves: register the session as an agent, start monitoring for messages, and inject messaging capabilities. No user interaction required.
Open a second session and say:
"Send a message to the other agent: hey, what are you working on?"
Going multi-machine? Set AGENT_RELAY_TOKEN on the server and export it on each client machine. The dashboard asks for the token on first load. See Deployment for details.
Configuration
Both integrations share the same provider env vars:
| Env var | Default | Purpose |
|---------|---------|---------|
| AGENT_RELAY_URL | http://localhost:4850 | Relay server URL |
| AGENT_RELAY_TOKEN | unset | Auth token (required for remote relays) |
| AGENT_RELAY_CAPS | chat | Comma-separated agent capabilities |
| AGENT_RELAY_TAGS | unset | Extra comma-separated tags added to provider defaults |
| AGENT_RELAY_LABEL | unset | Human-friendly agent label set at registration |
| AGENT_RELAY_CHANNELS | all | Comma-separated channel subscriptions; unchannelled direct work still arrives |
| AGENT_RELAY_PROFILE | unset | Named profile loaded from the profiles file |
| AGENT_RELAY_PROFILES_FILE | ~/.config/agent-relay/profiles.json | JSON profile file |
| AGENT_RELAY_APPROVAL | open | Approval mode: open, guarded, read-only |
Profiles preconfigure labels, tags, capabilities, channels, approval mode, and meta fields:
{
"backend-tester": {
"label": "backend tester",
"tags": ["backend", "api", "test"],
"capabilities": ["chat", "review", "test", "backend"],
"channels": ["backend", "qa"],
"approval": "guarded",
"meta": { "role": "backend-tester" }
}
}Launch with AGENT_RELAY_PROFILE=backend-tester codex or
AGENT_RELAY_PROFILE=backend-tester claude. Explicit env vars override the
profile where they overlap.
Codex has additional tuning vars documented in codex/README.md. The interactive codex-relay launcher starts codex app-server and connects the TUI with codex --remote; codex-relay --headless starts a relay-only session and prints a codex resume --remote attach command.
Agent IDs are deterministic: {hostname}-{project}-{session-hash}.
Message Targeting
| Target | Example | Behavior |
|--------|---------|----------|
| Agent ID | "macmini2-cli-myproject-a1b2c3" | Direct to one agent |
| Tag | "tag:backend" | All agents with that tag |
| Capability | "cap:review" | All agents with that capability |
| Label | "label:test writer" | All agents with that human-set label |
| Broadcast | "broadcast" | Everyone |
What the Agent Sees
The plugin or sidecar registers the agent and injects messaging context:
Agent Relay active. Your agent ID: macmini2-cli-myproject-a1b2c3
Relay URL: http://localhost:4850 | Server: 0.4.16 | Plugin: 0.4.16
Approval mode: openIncoming messages arrive as monitor notifications (Claude) or live turns (Codex):
[msg:42] from backend-agent: Migration looks clean, tests pass. Ready to merge.Integrations And Tasks
Agent Relay can act as a secure ingress layer for scripts, monitoring systems,
CI jobs, and support tools. External tools should use integration tokens instead
of the full AGENT_RELAY_TOKEN.
Configure integrations with AGENT_RELAY_INTEGRATIONS:
export AGENT_RELAY_INTEGRATIONS='[
{
"name": "ops-monitor",
"token": "ops-secret",
"scopes": ["tasks:create"],
"targets": ["cap:ops"],
"channels": ["alerts"],
"callbackUrl": "https://ops.example/agent-relay/callback"
}
]'Create or update a task:
curl -sS -X POST "$AGENT_RELAY_URL/api/integrations/events" \
-H "Authorization: Bearer ops-secret" \
-H "Content-Type: application/json" \
-d '{
"type": "alarm",
"severity": "critical",
"dedupeKey": "prod-api:5xx-rate",
"title": "prod-api 5xx rate high",
"body": "5xx rate is above 8% for 5 minutes",
"target": "cap:ops",
"channel": "alerts"
}'dedupeKey gives predictable update semantics: if the same source posts the same
dedupe key while the task is active, Agent Relay increments occurrenceCount,
updates lastSeenAt, records a task event, and does not spam agents with another
claimable message. Posting "status": "resolved" marks the active task done.
When an integration has callbackUrl, Agent Relay posts task lifecycle events
(creation, claim, status changes) back to the caller.
Integration tokens can also be used as scoped API tokens when the full admin
AGENT_RELAY_TOKEN would be too broad. Supported scopes include stats:read,
health:read, events:read, channels:read, agents:read, agents:write, messages:read,
messages:write, tasks:read, tasks:write, pairs:read, pairs:write,
system:write, and *.
The event ingress endpoint also accepts the legacy tasks:create and
events:create scopes.
Task lifecycle API:
| Method | Path | Purpose |
|--------|------|---------|
| POST | /integrations/events | Scoped integration ingress; creates or dedupes tasks |
| GET | /tasks | List tasks (?status=, ?source=, ?target=, ?limit=) |
| GET | /tasks/:id | Get one task |
| GET | /tasks/:id/events | Get task event history |
| POST | /tasks/:id/claim | Claim a task as an agent |
| PATCH | /tasks/:id/status | Update status/result/progress |
Example connectors live under examples/integrations/.
Deployment
Single process, embedded SQLite, no external dependencies beyond Bun.
bunx agent-relay-server@latest # localhost only
AGENT_RELAY_TOKEN=... PORT=8080 HOST=0.0.0.0 bunx agent-relay-server@latest # remote accessLocalhost is intentionally frictionless. If you bind to a non-loopback address,
set AGENT_RELAY_TOKEN first.
For multi-machine setups, run the server on one machine and set AGENT_RELAY_URL
on the others (works great over Tailscale):
export AGENT_RELAY_URL=http://100.x.y.z:4850
export AGENT_RELAY_TOKEN=your-shared-tokenDo not expose Agent Relay directly to the public internet. It is designed for a trusted localhost/VPN/LAN boundary, not as a multi-tenant public service.
Managed daemon
For an always-on relay, install as a user-level daemon:
bun install -g agent-relay-server@latest
agent-relay setup --yes
agent-relay daemon install --binary "$(command -v agent-relay)" --enable --start --yessetup creates a managed env file with a generated token, loopback bind, and a
durable SQLite path. daemon install auto-detects systemd (Linux) and
LaunchAgents (macOS).
Lifecycle: agent-relay daemon status|logs|restart|stop|start|enable|disable|uninstall
Server environment variables
| Variable | Default | Purpose |
|----------|---------|---------|
| PORT | 4850 | Server port |
| HOST | 127.0.0.1 | Bind address |
| DB_PATH | agent-relay.db | SQLite database path |
| STALE_TTL_MS | 120000 | Mark agents offline after this heartbeat gap |
| OFFLINE_PRUNE_MS | 86400000 | Delete offline agents older than this |
| REAP_INTERVAL_MS | 60000 | Reaper cadence for stale/offline cleanup |
| RETENTION_DAYS | 30 | Auto-prune messages older than this |
| AGENT_RELAY_TOKEN | unset | Shared bearer token required for non-loopback binds |
| AGENT_RELAY_CORS_ORIGINS | same-origin only | Comma-separated browser origins, or * |
| AGENT_RELAY_ALLOW_UNAUTH | unset | Set 1 to allow unauthenticated non-loopback binds |
| AGENT_RELAY_INTEGRATIONS | [] | JSON integration token/scopes/target/callback config |
| AGENT_RELAY_INTEGRATION_RATE_LIMIT_PER_MINUTE | 120 | Per-integration event ingress limit |
Version compatibility
The provider integrations check the server version on startup and warn if they diverge. Server, Claude plugin, and Codex packages share version numbers. Upgrade a host with:
agent-relay upgrade --dry-run
agent-relay upgrade --yesagent-relay upgrade detects optional providers. It refreshes Codex when codex-relay is installed, updates the Claude plugin when claude has agent-relay@agent-relay installed, and restarts the managed agent-relay.service unless --no-restart is passed.
Provider-specific aliases are available when you know what you want:
agent-relay upgrade --providers codex
agent-relay upgrade --providers claude
agent-relay-codex upgrade --dry-runAPI Reference
Base URL: http://localhost:4850/api
When AGENT_RELAY_TOKEN is set, pass it with either header:
curl -H "Authorization: Bearer $AGENT_RELAY_TOKEN" http://localhost:4850/api/stats
curl -H "X-Agent-Relay-Token: $AGENT_RELAY_TOKEN" http://localhost:4850/api/statsAgents
| Method | Path | Purpose |
|--------|------|---------|
| POST | /agents | Register or update an agent |
| GET | /agents | List agents (filter: ?tag=, ?machine=, ?status=) |
| GET | /agents/find | Find by capability (?capability=review) |
| GET | /agents/:id | Get one agent |
| POST | /agents/:id/heartbeat | Keep-alive |
| PATCH | /agents/:id/label | Set human-friendly label |
| DELETE | /agents/:id | Remove agent |
Messages
| Method | Path | Purpose |
|--------|------|---------|
| POST | /messages | Send a message |
| GET | /messages | Poll inbox (?for=agentId&unread=true) |
| GET | /messages/:id | Get single message |
| GET | /messages/:id/thread | Get full thread |
| POST | /messages/:id/claim | Claim a claimable task |
| PATCH | /messages/:id | Mark as read |
| DELETE | /messages/:id | Delete message |
| GET | /messages/cursor | Latest message ID (for poller bootstrap) |
Pair Sessions
Pair sessions are exclusive two-agent live chats backed by normal relay messages. They are useful when you want two agent-sessions to collaborate in real time without turning the relay into a group chat. The sessions can be claude to codex, codex to codex or claude to claude, since agent-relay is provider-independent.
agent-relay /pair codex "Debug flaky auth tests"
agent-relay pair codex --objective "Debug flaky auth tests"
agent-relay pair accept PAIR_ID --agent "$AGENT_RELAY_ID"
agent-relay pair send PAIR_ID --from "$AGENT_RELAY_ID" --body "What do you see?"
agent-relay /message codex "Can you look at that failing action?"
agent-relay /send-claimable tag:backend "Please claim and fix the failing API test"
agent-relay /disconnect
agent-relay /status
agent-relay /label backend-fixer
agent-relay /tags backend tests urgentInside provider sessions, the Codex plugin and Claude plugin ship command
skills for /pair, /message, /send-claimable, /disconnect, /status,
/label, and /tags. If one of those names collides with another installed
skill, invoke the provider-prefixed form such as /agent-relay:pair or
/agent-relay:message. The CLI tries to auto-detect the current agent id from
provider state; pass --from or --agent if you want to be explicit.
If the target is already in a pending or active pair, the relay returns
409 Busy. If a target like codex matches multiple available agents, the
relay asks for a more specific target such as id:..., label:..., tag:...,
cap:... or machine:....
| Method | Path | Purpose |
|--------|------|---------|
| POST | /pairs | Create a pair invite |
| GET | /pairs | List pairs (?agent=, ?status=) |
| GET | /pairs/:id | Get one pair |
| POST | /pairs/:id/accept | Accept a pending pair |
| POST | /pairs/:id/reject | Reject a pending pair |
| POST | /pairs/:id/messages | Send a pair message |
| POST | /pairs/:id/hangup | End a pending or active pair |
Tasks
| Method | Path | Purpose |
|--------|------|---------|
| GET | /channels | List registered communication channels such as Telegram |
| GET | /integrations | List configured and observed task/system integrations |
| POST | /integrations/events | Integration event ingress |
| GET | /tasks | List tasks |
| GET | /tasks/:id | Get one task |
| GET | /tasks/:id/events | Get task event history |
| POST | /tasks/:id/claim | Claim a task |
| PATCH | /tasks/:id/status | Update task status/result |
Server-Sent Events
| Method | Path | Purpose |
|--------|------|---------|
| GET | /events | SSE stream (?for=agentId for filtered, omit for firehose) |
Events: message.new, message.claimed, message.deleted, agent.status, agent.removed
Stats
| Method | Path | Purpose |
|--------|------|---------|
| GET | /stats | Version, agent counts, message counts |
Architecture
src/
├── cli.ts # server CLI, setup, daemon commands
├── daemon.ts # OS daemon planning/execution
├── index.ts # Bun.serve entry, static files, CORS
├── setup.ts # onboarding env-file generation
├── routes.ts # HTTP router and API handlers
├── db.ts # SQLite schema, queries, CRUD
├── sse.ts # Server-Sent Events
└── types.ts # TypeScript interfaces
public/
├── index.html # Dashboard markup
└── dashboard.js # Dashboard Alpine model and behavior
claude/ # Claude Code plugin (npm: agent-relay-plugin)
├── .claude-plugin/plugin.json
├── monitors/monitors.json # Auto-start inbox monitor
└── hooks/
├── relay-monitor.sh # Register, inject context, poll
├── approval-gate.sh # PreToolUse/PermissionDenied enforcement
├── set-status.sh # Turn hooks: busy/idle status updates
├── session-end.sh # Mark agent offline
└── poll-inbox.sh # Inbox poll loop
codex/ # Codex integration (npm: agent-relay-codex)
├── bin/agent-relay-codex.ts # Launcher CLI (install/start/doctor)
├── live-sidecar.ts # App-server <-> relay bridge
├── hooks/session-start.ts # Starts one sidecar per launched thread
└── plugin/ # Codex plugin bundle
client/ # TypeScript client (npm: agent-relay-client)
└── index.ts # RelayClient class + typesDevelopment
See CONTRIBUTING.md for contribution guidelines and SECURITY.md for vulnerability reporting.
git clone https://github.com/edimuj/agent-relay.git
cd agent-relay
bun run dev # server with hot reload
claude --plugin-dir ./claude # test plugin locallyLicense
AGPL-3.0-or-later. See LICENSE.
Related
- callmux - MCP multiplexer: parallel execution, batching, caching, pipelining, and shared infrastructure for any AI agent
- tokenlean - Make agents less wasteful: Token-efficient CLI tool-toolbox for AI agents
- claude-mneme / codex-mneme - Lightweight persistent agent memory: so every session picks up where the last one left off
- agent-awareness - Real-time situational awareness for your agents
