Relay Observability
Find out what happened to a message between your agents, and what to do when delivery fails
Relay Observability
When your agents talk to each other through Relay, most messages just arrive. When one doesn't, you need answers: did it fail, time out, or never reach anywhere at all? Relay keeps a full paper trail of every message, from the moment it's published to the moment it's delivered, so you can find out.
Multi-agent coordination through Relay is a newer part of DorkOS. It works, but we're still hardening it end to end, so treat delivery guarantees as "strong, not bulletproof" while it matures.
Haven't turned Relay on yet? Start with Relay Messaging to enable it and register your first adapter.
The Fastest Way to Check Delivery Health
Open the Relay tab in the DorkOS sidebar. The Delivery Metrics Dashboard there shows how many messages delivered, failed, or landed in the dead letter queue, no terminal required. For most people, that's as deep as you'll ever need to go.
Everything below this point is deep reference: raw API calls, JSON shapes, and internals for anyone debugging a stuck message by hand.
Reference: Tracing and Metrics
Message Tracing
Every message published through Relay gets a trace ID. Each delivery to an endpoint creates a span, a complete record of that one delivery attempt: when it was sent, when it arrived, when processing finished, and whether it succeeded.
Trace and message IDs are ULIDs (sortable, unique IDs that look like 01HXABC123).
Trace Span Fields
Prop
Type
Looking Up a Trace
curl http://localhost:4242/api/relay/messages/01HX.../traceThe response includes every span in the trace chain, ordered by sentAt ascending:
{
"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
}
]
}For messages that fan out to multiple endpoints, the trace contains one span per endpoint.
Trace Statuses
| Status | Meaning |
|---|---|
sent | Message published, delivery in progress |
delivered | Message delivered and processed successfully |
failed | Subscription handler threw an error |
timeout | Message rejected (budget exceeded, access denied, TTL expired, no matching endpoints, or circuit breaker open) |
A healthy message moves from sent to delivered. The time between sentAt and deliveredAt is delivery latency. The time between deliveredAt and processedAt is processing latency, which includes the time the adapter (e.g., Claude Code) takes to handle the message.
Using MCP Tools
Agents can inspect traces without HTTP calls using the built-in MCP tools:
relay_get_trace Get the full delivery trace for a message by ID
relay_get_metrics Get aggregate delivery metrics for the busrelay_get_trace accepts a messageId and returns the same trace data as the REST endpoint. Use this when an agent needs to verify a message was delivered before proceeding.
Delivery Metrics
Relay computes aggregate delivery metrics straight from the trace store, covering the last 24 hours by default. These give you a summary of bus health without inspecting individual traces.
Fetching Metrics
curl http://localhost:4242/api/relay/trace/metrics{
"totalMessages": 1542,
"deliveredCount": 1480,
"failedCount": 12,
"deadLetteredCount": 50,
"avgDeliveryLatencyMs": 45.2,
"p50DeliveryLatencyMs": 28.6,
"p95DeliveryLatencyMs": 190.5,
"p99DeliveryLatencyMs": 420.1,
"activeEndpoints": 8,
"budgetRejections": {
"hopLimit": 0,
"ttlExpired": 0,
"cycleDetected": 0,
"budgetExhausted": 0
}
}Metrics Fields
Prop
Type
Interpreting the Numbers
A healthy bus has a high delivered-to-total ratio and low dead letter counts.
- High
deadLetteredCountrelative to total: Check budget rejections. A spike inhopLimitrejections usually means two agents stuck replying to each other in a loop (it happens to the best of us). TTL expirations may mean agents are too slow to process within the 1-hour window. - Rising
failedCount: Subscriber handlers are throwing errors. Check individual traces for theerrorMessagefield. The circuit breaker (a safety switch that stops sending to an endpoint that keeps failing) automatically stops delivering to endpoints with 5 consecutive failures. - High
avgDeliveryLatencyMs: A single slow endpoint can pull up the mean. Compare it againstp50DeliveryLatencyMs: an average far above the median means a few slow deliveries are skewing it. Inspect traces filtered by endpoint to find the bottleneck.
Relay metrics cover the last 24 hours by default, not the full history of the database.
budgetRejections counters return 0 in this endpoint. They're tracked at the RelayCore level
but not yet aggregated into trace store metrics.
When a Message Goes Missing
Work through these steps in order. Each one rules out a different cause.
Check the dead letter queue
Dead letters are messages that could not be delivered. Fetch them:
curl http://localhost:4242/api/relay/dead-lettersEach dead letter includes the original envelope with subject, payload, and budget. Common reasons:
- No matching endpoints: The subject has no registered endpoints. Verify with
GET /api/relay/endpoints. - Budget exceeded: The message's hop count, TTL, or call budget was exhausted.
- Access denied: The sender lacks permission to publish to the target subject. Check
access-rules.json.
Inspect the message trace
If the message was delivered but the handler failed, look up the trace:
curl http://localhost:4242/api/relay/messages/{messageId}/traceCheck status and errorMessage on each span. A failed status with an error message tells you exactly what went wrong. For Claude Code adapter failures, the error typically includes the Agent SDK error message.
Check endpoint health
If deliveries are being rejected, the endpoint's circuit breaker may be open. The circuit breaker opens after 5 consecutive failures (configurable in ~/.dork/relay/config.json). After a 30-second cooldown, it allows a single probe through. If the probe succeeds, normal delivery resumes.
Check rate limits and backpressure
If a sender is publishing too quickly, messages are rejected by the rate limiter. The default allows 100 messages per 60-second window per sender. If an endpoint's mailbox (its inbox of unprocessed messages) has too many unprocessed messages, backpressure triggers at 80% capacity (warning) and 100% capacity (rejection).
Both settings are tunable in ~/.dork/relay/config.json and hot-reloaded without a restart.
SSE Stream for Real-Time Monitoring
Subscribe to the Relay SSE stream for live visibility:
curl -N http://localhost:4242/api/relay/stream?subject=%3EThe subject query parameter filters which messages appear. Use > (URL-encoded as %3E) to see all messages, or provide a pattern like relay.agent.* to filter by audience.
The stream emits four event types:
| Event | Description |
|---|---|
relay_connected | Initial connection confirmation with the filter pattern |
relay_message | A message envelope matching the subscription pattern |
relay_backpressure | A backpressure signal from an endpoint approaching or exceeding mailbox capacity |
relay_signal | Other signals (dead letters, typing indicators, delivery receipts) |
The SSE stream is for debugging and monitoring. Long-lived SSE connections consume server resources, so close them when no longer needed. For production monitoring, use the REST API or the UI dashboard.