Metrics, alerting, and HA peer forwarding
On this page
- Provenance and clean-room citations
- OpenMetrics scrape surface
- Endpoint specification
- Exposition conventions
- Metric catalogue
- REST metrics API
- Operation: listMetrics
- Schema: Metric
- Wire protocol correlation for metrics queries
- Alerting rules and delivery
- Alerting REST operations
- Alerting REST schemas
- Rule types and metadata specifications
- Alert delivery wire protocol
- Relay control plane extensions: External alert channels
- Supported external channel types
- Security and secret resolution
- High availability peer forwarding conventions
- Cluster connection topology
- Peer forwarding addressing and environment configuration
- Network requirements
Relay provides observability and operational alerting compatible with DBOS Conductor. This specification details the OpenMetrics scrape surface, the Conductor REST metrics contract, the alerting rule data model, the WebSocket alert delivery protocol, and the high-availability peer forwarding conventions.
Provenance and clean-room citations
All specifications in this document are derived from permitted public sources:
- DBOS Public Documentation:
- Metrics reference:
https://docs.dbos.dev/production/metrics(confirmed 2026-09-08) - Alerting guide:
https://docs.dbos.dev/production/alerting(confirmed 2026-09-08) - Self-hosting Conductor guide:
https://docs.dbos.dev/production/hosting-conductor(confirmed 2026-09-08)
- Metrics reference:
- Vendored Conductor OpenAPI Specification:
- Specification file:
api/spec/openapi.json(OpenAPI 3.1.0, SHA256:b5dc31eb29686a84fe0390a7446b5acdbc0dd05846cc94a746de649b92880722) - Schemas:
Metric(#/components/schemas/Metric),AlertingRule(#/components/schemas/AlertingRule),CreateAlertInputBody(#/components/schemas/CreateAlertInputBody) - Paths:
#/paths/~1v2~1orgs~1{orgName}~1apps~1{appName}~1metrics,#/paths/~1v2~1orgs~1{orgName}~1apps~1{appName}~1alerting-rules,#/paths/~1v2~1orgs~1{orgName}~1apps~1{appName}~1alerting-rules~1{ruleId}
- Specification file:
- DBOS Transact SDKs:
- Go SDK (
https://github.com/dbos-inc/dbos-transact-golang, commitfb3e33e0b4c3c709b9271eb935adce5eaf9386f5):dbos/conductor_protocol.go:alertMessage("alert"),getMetricsMessage("get_metrics"),alertRequest,alertConductorResponse,getMetricsConductorRequest,getMetricsConductorResponsedbos/conductor.go:handleAlertRequest(lines 1295-1336)dbos/internal/sysdb/system_database.go:MetricData,GetMetrics(lines 5767, 5773)
- Python SDK (
https://github.com/dbos-inc/dbos-transact-py, commit2b93e1467a5464f817ef5d11aa5f10d3d2253761):- Alert handler decorator:
@DBOS.alert_handler
- Alert handler decorator:
- TypeScript SDK (
https://github.com/dbos-inc/dbos-transact-ts, commit749a4d420127e97715bf1f5d8caabf496a40af0b):- Alert handler hook:
DBOS.setAlertHandler
- Alert handler hook:
- Go SDK (
OpenMetrics scrape surface
Conductor exposes application metrics via a single Prometheus-compatible HTTP endpoint.
Endpoint specification
- Path:
/v1/metrics - Method:
GET - Authentication: Bearer token in the
Authorizationrequest header (Authorization: Bearer <API_KEY>). The key must carry theapplication.readormetric.readpermission. - Content Negotiation:
Accept: application/openmetrics-textor Prometheus text exposition format. - Filtering Parameters (repeatable query parameters):
applications: Limits emitted metrics to the specified application name(s).workflow_names: Limits workflow-family metrics to the specified workflow name(s). Step families are labeled bystep_name, a separate namespace, and are left unfiltered by this parameter.metrics: Limits emitted metrics to the specified metric family names (e.g.,dbos_conductor_v1_workflow_success_rate).
- Label Stability:
queue_nameis always present on queue-labeled families and may be empty for workflows that were never enqueued. Pending families carry no queue dimension.
Exposition conventions
- Metric Family Prefix: All metric names use the prefix
dbos_conductor_v1_. - Universal Label: Every metric series carries an
applicationlabel identifying the owning application. - OpenMetrics Type: Every metric emitted by this endpoint is exposed as an OpenMetrics
gauge. - Aggregation Window: Scrapes return data for the most recently completed clock-aligned minute (
[T-1m, T)whereTis the current minute boundary). - Measurement Classes:
- Rate: Per-second average computed over the 60-second aggregation window. These values are pre-averaged gauges; scrapers must not apply the PromQL
rate()function to them. Rate series carry the window end as their timestamp. - Point-in-time: Instantaneous snapshot value at scrape time, without an explicit timestamp.
- Windowed: Aggregate values (such as maximum latency or queue wait) computed across events completed within the window, carrying the window end as their timestamp. Aggregations across multiple label groups must use
max(). - Timestamp Units: Prometheus text exposition stamps timestamps in milliseconds; OpenMetrics stamps them in seconds.
- Rate: Per-second average computed over the 60-second aggregation window. These values are pre-averaged gauges; scrapers must not apply the PromQL
Metric catalogue
The tables below catalogue the OpenMetrics families defined by Conductor. Executor counts are evaluated directly from the control plane registry. Workflow and step families are computed per application by dispatching aggregate requests to a healthy executor over the existing WebSocket protocol for the most recently completed clock-aligned minute, with live executors taking precedence and series omitted when no executor answers.
In-scope metric families (Scale tier)
| Metric Name | Flavor | Labels | Origin | Status | Description |
|---|---|---|---|---|---|
dbos_conductor_v1_executor_count |
Point-in-time | application, status, application_version |
Computed (Control Plane) | Supported | Number of registered executors. Series omitted when no executors are connected. status takes values such as HEALTHY, DISCONNECTED, or DEAD. |
dbos_conductor_v1_workflow_started_rate |
Rate | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, created window) |
Supported | Workflows created per second over the 60-second window. |
dbos_conductor_v1_workflow_dequeued_rate |
Rate | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, dequeued window) |
Supported | Enqueued workflows dequeued per second. Excludes workflows that were never enqueued. |
dbos_conductor_v1_workflow_success_rate |
Rate | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, completed window, SUCCESS) |
Supported | Workflows that completed successfully per second. |
dbos_conductor_v1_workflow_failed_rate |
Rate | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, completed window, ERROR or MAX_RECOVERY_ATTEMPTS_EXCEEDED) |
Supported | Workflows terminating in ERROR or MAX_RECOVERY_ATTEMPTS_EXCEEDED per second. |
dbos_conductor_v1_workflow_cancelled_rate |
Rate | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, completed window, CANCELLED) |
Supported | Workflows cancelled per second. |
dbos_conductor_v1_workflow_enqueued_count |
Point-in-time | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, status ENQUEUED) |
Supported | Current number of workflows in ENQUEUED state. |
dbos_conductor_v1_workflow_pending_count |
Point-in-time | application, workflow_name |
Executor dispatch (get_workflow_aggregates, status PENDING) |
Supported | Current number of workflows in PENDING (executing) state. |
dbos_conductor_v1_workflow_oldest_enqueued_timestamp_seconds |
Point-in-time | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, status ENQUEUED, select_min_created_at) |
Supported | Unix timestamp (seconds) of oldest currently enqueued workflow. Series omitted if queue is empty. Age derived via time() - metric. |
dbos_conductor_v1_workflow_oldest_pending_timestamp_seconds |
Point-in-time | application, workflow_name |
Executor dispatch (get_workflow_aggregates, status PENDING, select_min_created_at) |
Supported | Unix timestamp (seconds) of oldest currently executing workflow. Series omitted if no workflows are pending. Age derived via time() - metric. |
dbos_conductor_v1_workflow_max_queue_wait_seconds |
Windowed | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, completed successfully in window) |
Supported | Maximum queue wait (created to first started) in seconds across workflows completing in the window. |
dbos_conductor_v1_workflow_max_total_latency_seconds |
Windowed | application, workflow_name, queue_name |
Executor dispatch (get_workflow_aggregates, completed successfully in window) |
Supported | Maximum total latency (created to completed) in seconds across workflows completing in the window. |
dbos_conductor_v1_step_success_rate |
Rate | application, step_name |
Executor dispatch (get_step_aggregates, SUCCESS) |
Supported | Steps completed successfully per second. |
dbos_conductor_v1_step_failed_rate |
Rate | application, step_name |
Executor dispatch (get_step_aggregates, statuses other than SUCCESS, treated as errors) |
Supported | Steps terminating with an error per second. |
dbos_conductor_v1_step_max_duration_seconds |
Windowed | application, step_name |
Executor dispatch (get_step_aggregates, SUCCESS, select_max_duration_ms) |
Supported | Maximum single-step duration in seconds across steps completing successfully in the window. |
REST metrics API
In addition to the Prometheus scrape endpoint, Conductor provides a REST API to query historical metrics.
Operation: listMetrics
- Method:
GET - Path:
/v2/orgs/{orgName}/apps/{appName}/metrics - Query Parameters:
startTime(required, ISO 8601 / RFC 3339 date-time)endTime(required, ISO 8601 / RFC 3339 date-time)
- Response:
200 OKwith JSON array ofMetricobjects.
Schema: Metric
{
"type": "object",
"required": ["appId", "metricType", "metricName", "granularity", "timeBucket", "value"],
"properties": {
"appId": { "type": "string" },
"metricType": {
"type": "string",
"description": "Known values: workflow_count, step_count (reported by executor SDKs), recovery_count (reported by Conductor)."
},
"metricName": { "type": "string" },
"granularity": { "type": "integer", "format": "int32" },
"timeBucket": { "type": "string", "format": "date-time" },
"value": { "type": "integer", "format": "int64" }
},
"additionalProperties": false
}
Wire protocol correlation for metrics queries
When a client queries /v2/orgs/{orgName}/apps/{appName}/metrics, Relay dispatches a get_metrics request over the WebSocket hub to an active executor for the application:
{
"type": "get_metrics",
"request_id": "<uuid>",
"start_time": "<RFC3339>",
"end_time": "<RFC3339>",
"metric_class": "<class>",
"application_name": ["<app_name>"]
}
The executor queries its local system database (SysDB.GetMetrics) and replies:
{
"type": "get_metrics",
"request_id": "<uuid>",
"metrics": [
{
"metric_name": "<name>",
"metric_type": "workflow_count",
"value": 42.0
}
]
}
The control plane merges executor-reported metrics (workflow_count, step_count) with control plane metrics (recovery_count tracked in Relay's store) to construct the REST response.
Alerting rules and delivery
Conductor provides managed alerting rules that trigger when failure or latency thresholds are breached.
Alerting REST operations
- List rules:
GET /v2/orgs/{orgName}/apps/{appName}/alerting-rules(listAlertingRules) ->200 OKarray ofAlertingRule. - Create rule:
POST /v2/orgs/{orgName}/apps/{appName}/alerting-rules(createAlertingRule) ->201 CreatedwithAlertingRule. - Delete rule:
DELETE /v2/orgs/{orgName}/apps/{appName}/alerting-rules/{ruleId}(deleteAlertingRule) ->204 No Content.
Alerting REST schemas
Schema: CreateAlertInputBody
{
"type": "object",
"required": ["ruleType", "ruleMetadata"],
"properties": {
"ruleType": {
"type": "string",
"enum": ["WorkflowFailure", "SlowQueue", "UnresponsiveApplication"]
},
"ruleMetadata": { "type": "object" },
"minIntervalSecs": { "type": "integer", "format": "int32" },
"receivingAppName": { "type": "string" }
},
"additionalProperties": false
}
Schema: AlertingRule
{
"type": "object",
"required": [
"id",
"appId",
"receivingAppId",
"ruleType",
"ruleMetadata",
"minIntervalSecs",
"lastFiredAt"
],
"properties": {
"id": { "type": "string" },
"appId": { "type": "string" },
"receivingAppId": { "type": "string" },
"ruleType": {
"type": "string",
"enum": ["WorkflowFailure", "SlowQueue", "UnresponsiveApplication"]
},
"ruleMetadata": { "type": "object" },
"minIntervalSecs": { "type": ["integer", "null"], "format": "int32" },
"lastFiredAt": { "type": ["string", "null"], "format": "date-time" }
}
}
Rule types and metadata specifications
The ruleMetadata object holds condition thresholds and evaluation parameters. Values in the metadata payload delivered to handlers are string-encoded:
-
WorkflowFailure:- Condition: Triggered when the number of failed workflows exceeds a count threshold in a configured time window.
- Scope: Filtered by
workflow_name(or*for all workflows). - Delivered Metadata Keys:
workflow_name: Workflow name filter or*.failed_workflow_count: String count of failures observed.threshold: String count threshold.period_secs: String time window duration in seconds.
-
SlowQueue:- Condition: Triggered when any workflow remains in a queue longer than a duration threshold.
- Scope: Filtered by
queue_name(or*for all queues). - Delivered Metadata Keys:
queue_name: Queue name filter or*.stuck_workflow_count: String count of stuck workflows.threshold_secs: String queue wait threshold in seconds.
-
UnresponsiveApplication:- Condition: Triggered when an application has no connected executors, or all connected executors fail health checks.
- Scope: Application level.
- Routing Requirement: Because the monitored application is offline,
receivingAppNamemust specify a distinct active application to receive the alert. - Delivered Metadata Keys:
application_name: Name of the unresponsive application.connected_executor_count: String count of connected executors (typically"0").
Per ADR 0008, Relay aligns its alerting rule types strictly with the upstream OpenAPI specification enum (WorkflowFailure, SlowQueue, UnresponsiveApplication). Declarative manifests and REST write endpoints enforce this enum, rejecting unspec'd rule types at configuration time to prevent schema drift and client validation failures.
Alert delivery wire protocol
When an alert condition is met and the minimum interval (minIntervalSecs) has elapsed since lastFiredAt, Relay delivers the alert over the WebSocket connection of the designated receiving application.
WebSocket notification: alert
{
"type": "alert",
"request_id": "7b79d282-3e28-4e56-91e8-3a9a7a92b02a",
"name": "WorkflowFailure",
"message": "Workflow failure threshold exceeded",
"metadata": {
"workflow_name": "processPayment",
"failed_workflow_count": "5",
"threshold": "3",
"period_secs": "60"
}
}
Per the normative WebSocket protocol specification (docs/protocol/executor-ws.md:214-217), alert is a notification frame dispatched by Relay to connected executors with {name, message, metadata} (conductor_protocol.go:613-618). The executor acknowledges with alertConductorResponse carrying success; Relay waits for the acknowledgement with a short timeout and ignores its payload, logging only dispatch failures. If an envelope error occurs during delivery, the wire envelope error field is error_message (never error or payload).
If the application has registered an alert handler (@DBOS.alert_handler, dbos.SetAlertHandler, DBOS.setAlertHandler), the handler executes. If no handler is registered, the SDK automatically logs the alert as a warning.
Relay control plane extensions: External alert channels
In addition to upstream WebSocket delivery to connected executors, Relay supports dispatching alert notifications to external operational channels configured in operator manifests (relay.yaml).
Supported external channel types
webhook: Generic HTTP POST webhook with replay protection.url: Target endpoint URL (HTTP or HTTPS).secret: Optional HMAC-SHA256 secret (or resolved viasecret_from).- Headers:
X-Relay-Timestamp: Unix epoch timestamp in seconds.X-Relay-Signature:sha256=followed by hex-encoded HMAC-SHA256 of<timestamp>.<payload>.User-Agent:relay-alerting/1.0.
slack: Slack Incoming Webhooks format.url: Webhook URL.- Delivers JSON with formatted
textsummarizing the alert.
pagerduty: PagerDuty Events API v2 format.url: Endpoint URL (defaults tohttps://events.pagerduty.com/v2/enqueue).routing_key: Integration routing key.- Delivers
event_action: "trigger"with alert metadata.
Security and secret resolution
External channel destinations and secret resolution (secret_from.env, secret_from.file) are restricted exclusively to operator declarative manifests. Tenant-facing REST APIs strictly disallow filesystem probing, and secrets are redacted from all API read responses.
High availability peer forwarding conventions
In high-availability (HA) multi-node deployments, Relay nodes run behind a load balancer and share a common Postgres store.
Cluster connection topology
- Executor Ownership:
- Each executor establishes an outbound WebSocket connection to the cluster.
- The load balancer routes the connection to any Relay node.
- The receiving node records ownership and a connection lease in the shared Postgres database (
instancestable). Surviving instances adopt expired leases upon heartbeat timeout. - Exactly one Relay node owns an executor WebSocket at any given time.
- Inbound Dispatch and Forwarding:
- External client requests (from
dbosctl, the dashboard, or HTTP APIs) land on an arbitrary Relay node via the load balancer. - If the target executor is owned by the node that received the HTTP request, it dispatches the message directly across the local WebSocket.
- If the target executor is owned by a peer node, the local node reads the owner address from the database, signs the payload using HMAC-SHA256 with a timestamp, and forwards the request directly to
/internal/v1/forward/{appID}on that peer over HTTP. - Forwarded requests carry an
X-Relay-Forward-Hopheader. Requests withhop >= 1are rejected with HTTP 409 Conflict to prevent forwarding loops. Signatures verify with a maximum allowed timestamp drift of 30 seconds. - The owning peer dispatches the request to the executor over the WebSocket, receives the reply, and responds to the forwarding peer.
- External client requests (from
Peer forwarding addressing and environment configuration
Peer-to-peer forwarding traffic flows directly between Relay nodes and does not traverse the external load balancer.
| Environment Variable | Default Value | Description |
|---|---|---|
DBOS__ADVERTISE_ADDRESS |
127.0.0.1 |
Routable IP address or hostname peers use to forward requests to this instance. Must be set to a cluster-routable address in multi-node deployments. Do not include a port number. |
DBOS__CONDUCTOR_PORT |
8090 |
TCP port Conductor/Relay listens on and advertises to peer nodes for internal forwarding. |
Network requirements
- Peer Routability: Every node in the cluster must be able to establish direct TCP connections to
<DBOS__ADVERTISE_ADDRESS>:<DBOS__CONDUCTOR_PORT>on all peer nodes. - Database Consistency: All nodes must connect to the same PostgreSQL instance or high-availability database cluster (
DBOS__CONDUCTOR_DB_URL). - Authentication and Integrity: Internal peer requests are authenticated via HMAC-SHA256 signatures, validated with bounded timestamp drift (30 seconds) and loop prevention headers (
X-Relay-Forward-Hop).