Skip to content

OpenTelemetry ​

Agent SDK sends traces, metrics, and logs to an OTLP collector when OTEL_* is set. Record with @opentelemetry/api, and there is no defineOtel.

bash
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20$OTEL_TOKEN"
export OTEL_SERVICE_NAME=incident-agent
agent-sdk serve --dir . --dev

http/protobuf is the default, http/json works, grpc warns and falls back to http/protobuf, metrics use delta temporality, and Grafana Cloud rejects delta, so set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative for Grafana Cloud or Prometheus. Providers load before bot/agent.ts, so a module-scope meter, tracer, or logger binds to them, and with no endpoint there are no exporters, fetch has no traceparent, and the session collector stays off.

Variables ​

VariableEffect
OTEL_EXPORTER_OTLP_ENDPOINTBase URL. Appends /v1/traces, /v1/metrics, /v1/logs.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTTraces URL, used as written. Overrides the base URL.
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTMetrics URL, used as written. Overrides the base URL.
OTEL_EXPORTER_OTLP_LOGS_ENDPOINTLogs URL, used as written. Overrides the base URL.
OTEL_EXPORTER_OTLP_HEADERSSent on every export. URL-encode values. Reread on each export.
OTEL_EXPORTER_OTLP_PROTOCOLhttp/protobuf (default) or http/json.
OTEL_SERVICE_NAMEservice.name. Defaults to the agent name, then unknown_service:node.
OTEL_RESOURCE_ATTRIBUTESservice.namespace, service.version, deployment.environment.name.
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCEdelta (default), cumulative, or lowmemory.
OTEL_LOGS_EXPORTERotlp exports logs. Unset, none, and console do not.
OTEL_SDK_DISABLEDtrue turns export off.
OTEL_TRACES_EXPORTERnone turns traces off.
OTEL_METRICS_EXPORTERnone turns metrics off.
OTEL_LOG_USER_PROMPTS1 adds prompt text to log records.
OTEL_LOG_TOOL_CONTENT1 adds tool payloads to log records.

Hosted agents ​

ts
export default defineAgent({
  hosting: {
    egressDomains: ["otlp-gateway-prod-us-east-0.grafana.net"],
    secretNames: [
      "OTEL_EXPORTER_OTLP_ENDPOINT",
      "OTEL_EXPORTER_OTLP_HEADERS",
      "OTEL_SERVICE_NAME",
    ],
  },
});

egressDomains is a hostname with no scheme or path. Secrets arrive with the first request that has an endpoint, and that export uses those headers and service.name. A later agent-sdk secret set updates headers on the next export, including a rotated key. A URL, protocol, or signal-list change replaces the exporter. service.name is fixed on the first export that attaches, so redeploy to change it.

What you can record ​

ts
import { metrics, trace } from "@opentelemetry/api";
import { logs } from "@opentelemetry/api-logs";
import { currentConversationId } from "@grok/agent-sdk/otel";

const tracer = trace.getTracer("incident-agent");
const lookups = metrics.getMeter("incident-agent").createCounter("incident.lookups");
const logger = logs.getLogger("incident-agent");

lookups.add(1, { "cursor.conversation.id": currentConversationId() });

Record after secrets load. A span or log that ends before exporters attach is still sent, up to 2048 of each, a metric recorded before the first collect is included, and HTTP, token, and tool spans carry cursor.conversation.id, while a span started during a turn or a tool call gets that attribute, a value you set is kept, the id is not propagated, and a metric gets it only when you set it.

Tag a turn ​

ts
async execute({ key }, ctx) {
  ctx.telemetry.setTraceAttributes({ "work.item": key });
  ctx.telemetry.setMetricAttributes({ "work.item": key });
  ctx.telemetry.setLogAttributes({ "work.item": key });
  return await jira.getIssue(key);
}

ctx.telemetry writes the current turn's store. A span started in that turn, including a span Agent SDK starts, gets the trace keys. A log record Agent SDK emits gets the log keys. cursor.token.usage recorded for that turn gets the metric keys. The same store is still there for a later tool in the turn. With export on, a later turn of the session keeps it. With export off, the next turn does not.

ts
import { telemetry } from "@grok/agent-sdk/telemetry";

telemetry.setTraceAttributes({ "work.item": ticket });

The global methods read the active context. Outside a turn they do nothing. Tags stay in this process. They are not written on the baggage header.

promql
sum by (work_item) (cursor_token_usage_total)

Grafana renames . to _, and each distinct metric value is its own series, including a new id on every turn. Keys match [A-Za-z][A-Za-z0-9._-]* and are at most 128 characters, values are 1 to 256 characters, a session holds 32 trace keys, 32 metric keys, and 32 log keys, a later call replaces a key, and a metric value must not be a secret, an email, or a raw user id.

Outbound calls send traceparent only. With export off, the tags last for that turn, a span in the turn sees them, the next turn does not, and nothing is exported.

ctx.host.otel is deprecated, still runs this release, and logs one warning per process. setAttributes writes one map onto spans and token usage, and the calls above replace it.

Trace and log keys are accepted as written. A log record keeps a key Agent SDK already set. setMetricAttributes rejects cursor.token.type, agent_sdk.channel, cursor.tool.name, cursor.tool.kind, cursor.tool.status, agent_sdk.turn_status, agent_sdk.subagent_name, agent_sdk.sdk_agent_id, and agent_sdk.sdk_agent_url. The call throws and leaves the runtime value in place.

Prompt text and tool payloads stay off log records unless OTEL_LOGS_EXPORTER=otlp and OTEL_LOG_USER_PROMPTS or OTEL_LOG_TOOL_CONTENT is set.

Limits ​

Custom span processors, samplers, views, your own SDK, library auto-instrumentation, and a register() hook are not supported, and OTEL_TRACES_SAMPLER and OTEL_METRIC_EXPORT_INTERVAL are read when the provider is built, before hosted secrets load, so set them and restart, because a later value only logs a warning. If a module replaces a provider Agent SDK registered, the global API uses that provider and Agent SDK shuts down a provider it created only when it is not the registered one, and if a context manager is already registered, Agent SDK logs a warning and leaves it, so load Agent SDK first or the turn store does not stay on the active context.

Migrating from serve({ otel }) ​

serve({ otel }) is removed, so delete that option, bot/otel.ts, agent/otel.ts, and bot/instrumentation.ts, and do not call defineOtel. Each signal has one URL, and a second exporter for the same signal has no variable.

Old optionSet this instead
serviceNameOTEL_SERVICE_NAME
serviceVersionservice.version in OTEL_RESOURCE_ATTRIBUTES
resourceAttributesOTEL_RESOURCE_ATTRIBUTES, comma-separated, URL-encoded values
exporters[].urlOTEL_EXPORTER_OTLP_ENDPOINT (appends /v1/traces, /v1/metrics, /v1/logs)
exporters[].url with signalsOTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, used as written
exporters[].headersOTEL_EXPORTER_OTLP_HEADERS, comma-separated, URL-encoded values
exporters[].protocolOTEL_EXPORTER_OTLP_PROTOCOL, or OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL
exporters[].signalsThe per-signal URL. OTEL_TRACES_EXPORTER=none, OTEL_METRICS_EXPORTER=none, or OTEL_LOGS_EXPORTER=none stops that signal.
traces: falseOTEL_TRACES_EXPORTER=none
traces: trueLeave OTEL_TRACES_EXPORTER unset. Traces start when an endpoint is set.
traces.sampleRatioOTEL_TRACES_SAMPLER=parentbased_traceidratio and OTEL_TRACES_SAMPLER_ARG, before process start
metrics: falseOTEL_METRICS_EXPORTER=none
metrics: trueLeave OTEL_METRICS_EXPORTER unset. Metrics start when a metrics URL is set.
metrics.exportIntervalMillisOTEL_METRIC_EXPORT_INTERVAL, milliseconds, before process start
logs: falseUnset OTEL_LOGS_EXPORTER, or set none
logs: trueOTEL_LOGS_EXPORTER=otlp
logs.userPromptsOTEL_LOGS_EXPORTER=otlp and OTEL_LOG_USER_PROMPTS=1
logs.toolContentOTEL_LOGS_EXPORTER=otlp and OTEL_LOG_TOOL_CONTENT=1
otel: falseNo endpoint, or OTEL_SDK_DISABLED=true

Put those names in hosting.secretNames and the collector host in hosting.egressDomains.

bash
agent-sdk secret set my-agent OTEL_SERVICE_NAME OTEL_EXPORTER_OTLP_ENDPOINT OTEL_EXPORTER_OTLP_HEADERS