steps docs
start here Writing pipelines resourceswebhooksexprcontrol-flowagentsattempts-timeoutworkspaceinfratemplatingmcpcomplete Reference webobservabilityauthenticationagents-internalsaws-workersgcp-workersdo-workersconformance

Observability

steps exports traces and metrics with OpenTelemetry, configured the standard way: the OTEL_* environment variables every collector and hosted backend documents. Nothing in a pipeline file and no flag turns it on, because where telemetry goes is the operator's decision, not the pipeline author's.

It is off unless the environment names an exporter or an endpoint, per signal. With neither set, nothing is installed and nothing is dialled.

It exports OTLP over HTTP (http/protobuf), which Honeycomb and every OpenTelemetry collector accept:

# Honeycomb (api.eu1.honeycomb.io for the EU region); traces land in the dataset named by the service, steps
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io \
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=<your API key> \
OTEL_EXPORTER_OTLP_METRICS_HEADERS=x-honeycomb-team=<your API key>,x-honeycomb-dataset=steps-metrics \
steps web

# a local collector
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 steps run pipeline.yml
VariableEffect
OTEL_EXPORTER_OTLP_ENDPOINTTurns traces and metrics on, exporting OTLP there
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / …_METRICS_ENDPOINTTurns on just that signal
OTEL_EXPORTER_OTLP_HEADERS / …_METRICS_HEADERSHeaders sent with every export, such as Honeycomb's x-honeycomb-team; Honeycomb also needs x-honeycomb-dataset on metrics
OTEL_TRACES_EXPORTER / OTEL_METRICS_EXPORTERotlp turns the signal on (to localhost:4318 with no endpoint); none keeps it off whatever else is set; anything else is refused
OTEL_EXPORTER_OTLP_PROTOCOLhttp/protobuf, the default and the only one; grpc is refused rather than silently ignored
OTEL_METRIC_EXPORT_INTERVALMilliseconds between metric exports (default 60000)
OTEL_SDK_DISABLED=trueOff, whatever else is set
OTEL_SERVICE_NAMEOverrides the default service name, steps

Everything else the OpenTelemetry SDK reads (headers, sampling, resource attributes, batching) applies as documented there.

What is traced

Log lines written under a traced request carry trace_id and span_id, so a line can be found from its trace and back.

What is measured

MetricKindAttributesWhat it answers
cicd.pipeline.run.durationhistogram, secondssteps.pipeline, cicd.pipeline.name (the job), cicd.pipeline.resultHow long runs take, and — its count — how many finished, by result
cicd.pipeline.run.activeup-down countersteps.pipeline, cicd.pipeline.nameHow many runs are executing now
steps.step.durationhistogram, secondssteps.pipeline, steps.step.kind, cicd.pipeline.task.run.result, steps.skip.reason on a skipWhich kinds of step are slow, and how often the cache saves work: the share of steps whose result is skip with a cache reason
steps.queue.pendinggaugesteps.pipelineBuilds steps web has queued and not yet started
gen_ai.client.token.usagehistogram, tokensgen_ai.request.model, gen_ai.token.type (input/output)What agents spend, per model; recorded only when the provider reported usage
gen_ai.client.operation.durationhistogram, secondsgen_ai.request.model, error.type on failureHow long model calls take

No metric carries a run id or a step name, whose every value is new; a trace has both. The daemon's HTTP requests and the docker client's calls are measured too, under OpenTelemetry's standard http.server.* and http.client.* names.

A run's span opens before validation, worker leases, image pulls and preflight, so a run any of them refuses is still a trace — an errored RUN span, with whatever the refusal called (a preflight's model probe, an image pull) underneath — and is counted in the run metrics. What a step says while it runs (a retry's attempt 2/3, a worker starting, an image being pulled) is an event on its span.

GET /healthz answers ok to a platform's health check, without credentials, and is not traced.