DorkOSDorkOS
Guides

Relay Messaging

Enable the Relay message bus and send messages between agents, humans, and external platforms

Relay Messaging

Relay is the inter-agent message bus built into DorkOS. It routes messages between agents, humans, and external platforms like Telegram using a subject-based publish/subscribe model. Every message is persisted to disk, indexed in SQLite, and tracked with delivery tracing.

This guide walks through enabling Relay, sending your first message, configuring adapters, and integrating with the Pulse scheduler.

What is Relay?

Relay provides the communication layer for multi-agent workflows. Rather than agents calling each other directly, they publish messages to named subjects and Relay handles routing those messages to the right recipients. This decoupling means agents do not need to know about each other -- they only need to agree on subject naming conventions.

Messages flow through a pipeline that includes subject validation, access control checks, rate limiting, budget enforcement, and per-endpoint circuit breakers. Failed deliveries land in a dead letter queue for later inspection. The entire delivery path is traced end-to-end so you can see exactly what happened to any message.

When Relay is enabled, it also takes over the standard DorkOS session messaging path. Instead of POST /api/sessions/:id/messages directly calling the Agent SDK, the message is published to relay.agent.{sessionId} and the Claude Code adapter picks it up. This means every session message benefits from Relay's tracing, budget enforcement, and reliability features.

Enabling Relay

Set the environment variable

Add DORKOS_RELAY_ENABLED=true to your environment. You can set this in your .env file or export it directly:

export DORKOS_RELAY_ENABLED=true

Start DorkOS

Launch DorkOS as usual. The server log will confirm that Relay is active:

dorkos
DorkOS server listening on port 4242 // [!code highlight]
Relay message bus enabled // [!code highlight]

When Relay starts, it creates its data directory at ~/.dork/relay/ with the SQLite index database and Maildir message store.

Verify with the health endpoint

Check that Relay is reported in the server configuration:

curl http://localhost:4242/api/config

The response includes relay.enabled: true when the subsystem is active.

Relay is fully opt-in. When DORKOS_RELAY_ENABLED is not set or is false, all Relay routes return 404 and the session messaging path uses the direct Agent SDK call. You can toggle Relay on and off without losing any data -- the Maildir files and SQLite index persist between restarts.

Sending Messages

Messages are sent by publishing to a subject. Every message is wrapped in an envelope that carries routing metadata alongside the payload.

Using the REST API

Publish a message with a POST request to the Relay messages endpoint:

curl -X POST http://localhost:4242/api/relay/messages \
  -H 'Content-Type: application/json' \
  -d '{
    "subject": "relay.agent.backend",
    "payload": { "task": "Run the test suite" },
    "from": "relay.human.console.my-client"
  }'

The response includes the message ID and delivery results:

{
  "id": "01HX...",
  "delivered": 1,
  "deadLettered": 0
}

Using MCP Tools

When interacting with Claude Code through DorkOS, the agent has access to built-in MCP tools for Relay operations. These tools allow agents to send messages, read inboxes, and manage endpoints without making HTTP calls:

relay_send                Send a message to a Relay subject
relay_inbox               Read inbox messages for an endpoint
relay_list_endpoints      List all registered endpoints
relay_register_endpoint   Register a new endpoint
relay_get_trace           Get the full delivery trace for a message
relay_get_metrics         Get aggregate delivery metrics
relay_list_adapters       List all adapters with status
relay_enable_adapter      Enable a disabled adapter
relay_disable_adapter     Disable a running adapter
relay_reload_adapters     Hot-reload adapter configuration from disk

An agent can send a message by calling the relay_send tool with a subject, payload, and sender identifier. The tool handles envelope construction and delivery internally.

Subject Naming

Subjects use a hierarchical dot-separated format: relay.{audience}.{identifier}. Common patterns include:

Subject PatternPurpose
relay.agent.{agentId}Messages addressed to a specific agent
relay.human.console.{clientId}Messages destined for a human's browser console
relay.human.telegram.{chatId}Messages routed to a Telegram chat
relay.system.pulse.{scheduleId}System messages from the Pulse scheduler

Wildcards are supported for subscriptions: * matches exactly one segment, and > matches one or more trailing segments. For example, subscribing to relay.agent.> receives messages addressed to any agent.

Budget Constraints

Every message carries a budget that prevents runaway chains. You can set budget fields when publishing:

curl -X POST http://localhost:4242/api/relay/messages \
  -H 'Content-Type: application/json' \
  -d '{
    "subject": "relay.agent.backend",
    "payload": { "task": "Refactor the auth module" },
    "from": "relay.human.console.my-client",
    "budget": {
      "maxHops": 3,
      "ttlMs": 300000,
      "callBudgetRemaining": 50
    }
  }'

If you omit budget fields, Relay applies defaults: 5 maximum hops, 1-hour TTL, and no call budget limit. For automated agent-to-agent workflows, always set explicit budgets to prevent unexpected resource consumption.

Built-in Adapters

Adapters bridge external platforms into the Relay subject hierarchy. DorkOS ships with three built-in adapters configured through ~/.dork/relay/adapters.json.

Claude Code Adapter

The Claude Code adapter is enabled by default when Relay starts. It subscribes to relay.agent.* subjects and routes incoming messages to Claude Agent SDK sessions. Response chunks flow back through Relay to the sender's replyTo subject.

{
  "adapters": [
    {
      "id": "claude-code",
      "type": "claude-code",
      "builtin": true,
      "enabled": true,
      "config": {
        "maxConcurrent": 3,
        "defaultTimeoutMs": 300000
      }
    }
  ]
}

The maxConcurrent field limits how many parallel agent sessions the adapter will run. The defaultTimeoutMs sets the maximum time an agent session can run before being terminated.

Telegram Adapter

The Telegram adapter connects a Telegram bot to the Relay bus. Inbound messages from Telegram are published to relay.human.telegram.{chatId}, and outbound messages on matching subjects are delivered back to the Telegram chat.

To enable it, add a Telegram adapter entry to adapters.json with your bot token:

{
  "id": "telegram",
  "type": "telegram",
  "builtin": true,
  "enabled": true,
  "config": {
    "token": "123456:ABC-DEF..."
  }
}

Webhook Adapter

The webhook adapter provides a generic HTTP bridge with HMAC-SHA256 signature verification. It supports both inbound webhooks (external services posting to DorkOS) and outbound delivery (Relay messages forwarded as HTTP requests).

{
  "id": "my-webhook",
  "type": "webhook",
  "builtin": true,
  "enabled": true,
  "config": {
    "url": "https://example.com/hook",
    "secret": "your-hmac-secret",
    "subjectPrefix": "relay.webhook.incoming"
  }
}

Inbound webhooks are received at POST /api/relay/webhooks/{adapterId}. The adapter verifies the HMAC signature, extracts the payload, and publishes it to the configured subject prefix. Secret rotation is supported with a 24-hour transition window where both old and new secrets are accepted.

Hot Reload

Adapter configuration is watched for changes via chokidar. When you edit ~/.dork/relay/adapters.json, DorkOS automatically reconciles the running adapters -- stopping removed or disabled adapters and starting newly enabled ones. No server restart is required.

You can also trigger a reload via the API:

curl -X POST http://localhost:4242/api/relay/adapters/reload

Message Tracing

Every message published through Relay gets a trace that tracks its delivery end-to-end. Each delivery to an endpoint creates a span with timing information, budget consumption, and error details.

Retrieve the trace for a specific message:

curl http://localhost:4242/api/relay/messages/{messageId}/trace
{
  "traceId": "01HXABC123",
  "spans": [
    {
      "id": "01HXDEF456",
      "messageId": "01HXABC123",
      "traceId": "01HXABC123",
      "subject": "relay.agent.backend",
      "status": "delivered",
      "sentAt": "2025-02-26T12:00:00.000Z",
      "deliveredAt": "2025-02-26T12:00:00.050Z",
      "processedAt": "2025-02-26T12:00:00.200Z",
      "errorMessage": null,
      "metadata": null
    }
  ]
}

The trace span statuses are: sent (published, delivery in progress), delivered (endpoint received and processed successfully), failed (handler errored), and timeout (rejected by budget, access control, or TTL expiry).

For more detailed observability, see the Relay Observability guide.

Relay and Pulse Integration

When both Relay and Pulse are enabled, scheduled jobs are dispatched through Relay instead of calling the Agent SDK directly. The Pulse scheduler publishes a dispatch message to relay.system.pulse.{scheduleId}, and the Claude Code adapter picks it up and starts the agent session.

This integration means scheduled runs benefit from Relay's full delivery pipeline -- budget enforcement, tracing, dead letter handling, and access control all apply.

To enable both subsystems:

export DORKOS_RELAY_ENABLED=true
export DORKOS_PULSE_ENABLED=true
dorkos

When a scheduled run executes, you can trace its message through Relay to see exactly when it was dispatched, delivered, and processed. The relay.system.pulse.* subject pattern lets you subscribe to all Pulse dispatch events via the SSE stream.

Configuration Reference

Relay stores its configuration and data in the ~/.dork/relay/ directory.

Prop

Type

Data Directory Structure

PathPurpose
~/.dork/dork.dbConsolidated SQLite database (relay index, traces, and other DorkOS tables; WAL mode via Drizzle ORM)
~/.dork/relay/endpoints/Maildir message store, one directory per endpoint
~/.dork/relay/adapters.jsonAdapter configuration (hot-reloaded)
~/.dork/relay/config.jsonReliability settings (rate limits, circuit breakers, backpressure)
~/.dork/relay/access-rules.jsonAccess control rules (hot-reloaded)

Reliability Settings

The config.json file controls rate limiting, circuit breakers, and backpressure. Changes are hot-reloaded without a server restart.

Prop

Type

REST API Endpoints

MethodPathDescription
POST/api/relay/messagesPublish a message to a subject
GET/api/relay/messagesList messages with filters and cursor pagination
GET/api/relay/messages/:idGet a single message by ID
GET/api/relay/messages/:id/traceGet the full delivery trace for a message
GET/api/relay/endpointsList registered endpoints
POST/api/relay/endpointsRegister a new endpoint
DELETE/api/relay/endpoints/:subjectUnregister an endpoint
GET/api/relay/endpoints/:subject/inboxRead inbox for an endpoint
GET/api/relay/dead-lettersList dead letter messages
GET/api/relay/metricsRelay system metrics
GET/api/relay/trace/metricsAggregate delivery metrics
GET/api/relay/streamSSE event stream (supports ?subject= filter)
GET/api/relay/adaptersList adapters with status
GET/api/relay/adapters/:idGet a single adapter's status
POST/api/relay/adapters/reloadHot-reload adapter configuration
POST/api/relay/adapters/:id/enableEnable an adapter
POST/api/relay/adapters/:id/disableDisable an adapter
POST/api/relay/webhooks/:adapterIdInbound webhook receiver

Next Steps