Relay Messaging
Enable Relay and send messages between agents, humans, and external platforms
Relay Messaging
Relay is DorkOS's built-in messaging layer. 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 end-to-end.
This guide covers enabling Relay, sending your first message, configuring adapters, and integrating with Tasks.
What Relay Does
Agents publish messages to named subjects. Relay handles routing those messages to the right recipients. Agents don't need to know about each other — they only need to agree on subject naming.
Messages flow through subject validation, access control, rate limiting, budget enforcement, and per-endpoint circuit breakers. Failed deliveries land in a dead letter queue. The full delivery path is traced so you can see exactly what happened to any message.
When Relay is enabled, it takes over the standard session messaging path. Instead of POST /api/sessions/:id/messages calling the Agent SDK directly, the message is published to relay.agent.{sessionId} and the Claude Code adapter picks it up. Every session message benefits from Relay's tracing, budget enforcement, and reliability.
Enabling Relay
Relay is enabled by default on fresh installs. When DorkOS starts, you should see:
DorkOS server listening on port 4242
Relay message bus enabledRelay creates its data directory at ~/.dork/relay/ on first start.
To verify, check the config endpoint:
curl http://localhost:4242/api/configThe response includes relay.enabled: true when the subsystem is active.
To disable Relay, set DORKOS_RELAY_ENABLED=false in your .env file. All Relay routes will
return 404 and session messaging will use the direct Agent SDK call. The Maildir files and SQLite
index persist between restarts — you can toggle Relay without losing data.
Sending Messages
Using the REST API
Publish a message with a POST to the 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
Agents running inside DorkOS have built-in MCP tools for Relay operations — no HTTP calls required:
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_unregister_endpoint Unregister an endpoint (use to clean up dispatch inboxes)
relay_send_and_wait Blocking request/reply — sends a message and waits for the response
relay_send_async Fire-and-poll — dispatches a message, returns immediately with an inbox to poll
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 diskrelay_send_and_wait (added in v0.8.0) handles request/reply in a single call. It registers an ephemeral inbox, publishes the message with that inbox as replyTo, and blocks until the agent replies. Use this for synchronous patterns where you need the response before proceeding.
relay_send_async returns immediately with a dispatch inbox subject. The agent runs asynchronously; poll relay_inbox for progress events and the final result. Use this for long-running tasks where blocking is not appropriate. Call relay_unregister_endpoint to clean up when you receive done: true.
Subject Naming
Subjects use a hierarchical dot-separated format: relay.{audience}.{identifier}.
| Subject Pattern | Purpose |
|---|---|
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.tasks.{scheduleId} | System messages from the Task scheduler |
Wildcards work for subscriptions: * matches exactly one segment, > matches one or more trailing segments. Subscribing to relay.agent.> receives messages addressed to any agent.
Budget Constraints
Every message carries a budget that prevents runaway chains:
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, no call budget limit. For automated agent-to-agent workflows, set explicit budgets to prevent unexpected resource consumption.
Built-in Adapters
Adapters bridge external platforms into the Relay subject hierarchy. DorkOS ships with five 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
}
}
]
}maxConcurrent limits parallel agent sessions. defaultTimeoutMs sets the maximum time a session can run before termination.
Telegram Adapter
The Telegram adapter connects a Telegram bot to the Relay bus using grammy. Inbound messages from Telegram are published to relay.human.telegram.{chatId}, and outbound messages on matching subjects are delivered back to the chat. Supports real-time streaming via Telegram's sendMessageDraft API.
{
"id": "telegram",
"type": "telegram",
"builtin": true,
"enabled": true,
"config": {
"token": "123456:ABC-DEF..."
}
}Telegram Chat SDK Adapter
An experimental alternative Telegram adapter backed by the Chat SDK. Uses the same BotFather token but integrates via Chat SDK's adapter abstraction instead of grammy directly. Streaming uses post+edit at ~500ms intervals (vs the native adapter's 200ms sendMessageDraft).
{
"id": "telegram-chatsdk",
"type": "telegram-chatsdk",
"builtin": true,
"enabled": true,
"config": {
"token": "123456:ABC-DEF...",
"mode": "polling"
}
}Slack Adapter
The Slack adapter bridges Slack workspaces into Relay using Socket Mode — no public URL required. Supports streaming responses, threading, typing indicators via emoji reactions, and fine-grained access control.
{
"id": "slack",
"type": "slack",
"builtin": true,
"enabled": true,
"config": {
"botToken": "xoxb-...",
"appToken": "xapp-...",
"signingSecret": "abc123...",
"streaming": true,
"typingIndicator": "reaction",
"respondMode": "thread-aware",
"dmPolicy": "open"
}
}respondMode controls when the bot responds in channels: thread-aware (default -- responds to @mentions and thread replies where the bot is already participating), mention-only (only @mentions), or always (every message). dmPolicy controls DM access: open (default) or allowlist (restrict to user IDs in dmAllowlist). Per-channel overrides are available via channelOverrides.
Webhook Adapter
The webhook adapter provides a generic HTTP bridge with HMAC-SHA256 signature verification — both inbound (external services posting to DorkOS) and outbound (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.
Hot Reload
Adapter configuration is watched via chokidar. Edit ~/.dork/relay/adapters.json and DorkOS automatically reconciles the running adapters — stopping removed or disabled adapters and starting newly enabled ones. No server restart required.
Trigger a reload explicitly:
curl -X POST http://localhost:4242/api/relay/adapters/reloadMessage Tracing
Every message published through Relay gets a trace that tracks delivery end-to-end. 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
}
]
}Span statuses: sent (published, delivery in progress), delivered (processed successfully), failed (handler errored), timeout (rejected by budget, access control, or TTL expiry).
For deeper observability, see the Relay Observability guide.
Relay and Tasks Integration
When both Relay and Tasks are enabled, scheduled jobs are dispatched through Relay. Tasks publishes to relay.system.tasks.{scheduleId}, and the Claude Code adapter starts the agent session.
Scheduled runs then get Relay's full delivery pipeline — budget enforcement, tracing, dead letter handling, and access control.
export DORKOS_RELAY_ENABLED=true
export DORKOS_TASKS_ENABLED=true
dorkosSubscribe to relay.system.tasks.* via the SSE stream to observe all Tasks dispatch events.
Configuration Reference
Prop
Type
Data Directory
| Path | Purpose |
|---|---|
~/.dork/relay/index.db | SQLite index database (messages, traces; WAL mode) |
~/.dork/relay/endpoints/ | Maildir message store, one directory per endpoint |
~/.dork/relay/adapters.json | Adapter configuration (hot-reloaded) |
~/.dork/relay/config.json | Reliability settings (rate limits, circuit breakers, backpressure) |
~/.dork/relay/access-rules.json | Access control rules (hot-reloaded) |
Reliability Settings
The config.json file controls rate limiting, circuit breakers, and backpressure. Changes are hot-reloaded.
Prop
Type
REST API Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/relay/messages | Publish a message to a subject |
GET | /api/relay/messages | List messages with filters and cursor pagination |
GET | /api/relay/messages/:id | Get a single message by ID |
GET | /api/relay/messages/:id/trace | Get the full delivery trace for a message |
GET | /api/relay/endpoints | List registered endpoints |
POST | /api/relay/endpoints | Register a new endpoint |
DELETE | /api/relay/endpoints/:subject | Unregister an endpoint |
GET | /api/relay/endpoints/:subject/inbox | Read inbox for an endpoint |
GET | /api/relay/dead-letters | List dead letter messages |
GET | /api/relay/metrics | Relay system metrics |
GET | /api/relay/trace/metrics | Aggregate delivery metrics |
GET | /api/relay/stream | SSE event stream (supports ?subject= filter) |
GET | /api/relay/adapters | List adapters with status |
GET | /api/relay/adapters/:id | Get a single adapter's status |
POST | /api/relay/adapters/reload | Hot-reload adapter configuration |
POST | /api/relay/adapters/:id/enable | Enable an adapter |
POST | /api/relay/adapters/:id/disable | Disable an adapter |
POST | /api/relay/webhooks/:adapterId | Inbound webhook receiver |