Relay
Built-in messaging between agents, humans, and external platforms like Telegram
Relay
Relay is the messaging layer built into DorkOS. It handles routing and delivery for messages flowing between agents, humans, and external platforms like Telegram. Relay runs by default — set DORKOS_RELAY_ENABLED=false to disable it.
What Relay Does
When you have agents working across different projects, they need a way to reach you and each other. An agent finishing a task should be able to message you on Telegram. An agent that finds a breaking API change should be able to notify the agent working on the frontend.
Relay provides this. Agents publish messages to named subjects. Relay routes those messages to the right recipients — whether that is your phone, your browser console, or another agent. Agents do not need to know each other's internals: they only agree on subject naming.
Subjects and Endpoints
Relay organizes communication around subjects — hierarchical dot-separated names that describe where a message goes. Subjects follow a three-tier hierarchy: relay.{audience}.{identifier}.
Common patterns:
relay.agent.{agentId}— Messages addressed to a specific agentrelay.human.console.{clientId}— Messages destined for a human's consolerelay.human.telegram.{chatId}— Messages routed to a Telegram chatrelay.system.tasks.{scheduleId}— System messages from the Task scheduler
Endpoints
An endpoint is a registered destination on the bus. Registering an endpoint for a subject creates a Maildir directory structure on disk to receive messages. Messages arrive as JSON files in the endpoint's new/ directory and are indexed in SQLite.
Endpoints are durable. Even if no subscriber is actively listening, messages are delivered to the endpoint's Maildir and held for later pickup.
Wildcards
Relay supports two wildcard tokens:
*matches exactly one segment (relay.agent.*matchesrelay.agent.backend, notrelay.agent.backend.tasks)>matches one or more trailing segments (relay.agent.>matches both)
The Envelope
Every message is wrapped in an envelope that carries routing metadata alongside the payload:
- id — A ULID that uniquely identifies the message and provides time-ordering
- subject — The target subject for delivery
- from — The sender's subject identifier
- replyTo — Optional subject for response routing
- budget — Resource constraints that prevent runaway message chains
- payload — The actual message content (any JSON-serializable value)
Budget Enforcement
Every envelope carries a budget that constrains how far a message can travel:
- hopCount / maxHops — How many times the message has been forwarded. Default maximum: 5 hops. Prevents infinite loops where agent A sends to agent B, which sends back to agent A.
- ttl — A Unix timestamp after which the message expires. Default: 1 hour.
- callBudgetRemaining — API calls an agent may make when processing this message.
The budget also carries an ancestor chain — sender identifiers for every hop. If a message would be delivered to a sender that already appears in the chain, it is rejected as a cycle.
Budget limits are enforced per-delivery, not per-publish. A message published to three endpoints consumes one hop for each delivery.
Access Control
Relay enforces sender-to-subject rules that determine who can publish to what. Rules are {from, to, action, priority} tuples supporting subject wildcards. The first matching rule wins. If no rules match, the default is allow — you start open and add deny rules to restrict specific paths.
Access rules live in access-rules.json inside the Relay data directory and are watched for changes, so edits take effect without a restart.
Adapters
Relay connects to external platforms through adapters — plugins that bridge external channels into the Relay subject hierarchy. DorkOS ships with five built-in adapters:
- Claude Code adapter — Routes messages to Claude sessions. When a message arrives on
relay.agent.*, this adapter creates or resumes a Claude session and passes the message as a prompt. Response chunks flow back to the sender's reply-to subject. - Telegram adapter — Connects a Telegram bot to the bus via grammy. Inbound messages publish to
relay.human.telegram.{chatId}. Outbound messages on matching subjects are delivered to the chat. Supports real-time streaming viasendMessageDraft. - Telegram Chat SDK adapter — An experimental alternative Telegram adapter backed by the Chat SDK. Same bot token, different integration path. Useful as a reference for building Chat SDK-backed adapters.
- Slack adapter — Bridges Slack workspaces into Relay using Socket Mode. Supports streaming responses, threading, and typing indicators.
- Webhook adapter — A generic HTTP webhook bridge with HMAC-SHA256 signature verification for both directions.
You can add custom adapters from npm packages or local file paths. Plugin adapters implement the same RelayAdapter interface as built-in adapters.
Storage
Relay uses two storage systems:
- Maildir — A filesystem-based message store. Each endpoint gets a directory with
new/,cur/, andfailed/subdirectories. Messages are delivered as atomic file writes, making the store resilient to crashes. - SQLite —
~/.dork/relay/index.dbprovides fast querying by subject, sender, status, and time range. WAL mode enables concurrent read/write access. If the index becomes corrupted, it can be rebuilt from Maildir.
Tracing
Each published message gets a trace ID. Each delivery to an endpoint creates a span recording timing, budget consumption, and error details. The trace store lives in ~/.dork/relay/index.db alongside the message index and provides:
- Per-message trace lookup (every delivery attempt for a given message)
- Aggregate delivery metrics (success/failure counts, latency percentiles)
- Budget rejection breakdowns
Reliability
Three mechanisms protect endpoints under load:
- Rate limiting — Per-sender sliding window. Default: 100 messages per 60 seconds.
- Circuit breakers — After 5 consecutive delivery failures, an endpoint's circuit opens and rejects further deliveries for 30 seconds before retrying.
- Backpressure — When an endpoint's mailbox reaches capacity (default: 1000 messages), new deliveries are rejected.
All reliability settings can be tuned in ~/.dork/relay/config.json. Changes are hot-reloaded.
Integration with Sessions
When Relay is enabled, session messaging routes through Relay instead of calling the Agent SDK directly. A POST to /api/sessions/:id/messages publishes to relay.agent.{sessionId}. The Claude Code adapter picks it up and routes it to the SDK. Response chunks flow back through relay.human.console.{clientId} and into the client's SSE stream as relay_message events.
Every session message is tracked in the Relay index with full delivery tracing, and the same access control and budget rules apply to interactive sessions as to agent-to-agent communication.