Skip to Content

Upstream keys

The OpenTelemetry keys the Rius SDKs emit or read, referenced from the registries this one pins: core semantic conventions v1.44.0 and the GenAI semantic conventions at 0779402. Their definitions, types and stability are upstream’s: each key links to it. What is ours is the requirement level Rius gives the key and the note on how Rius uses it.

Generated from rius-semconv  1.5.0, the Rius attribute registry, and updated on each of its releases.

error

KeyTypeRequirement levelDefined inHow Rius uses it
error.typestringconditionally required (on error)OpenTelemetry v1.44.0Emitted. [1]

exception

KeyTypeRequirement levelDefined inHow Rius uses it
exception.typestringrecommendedOpenTelemetry v1.44.0Emitted.

gen_ai

KeyTypeRequirement levelDefined inHow Rius uses it
gen_ai.agent.idstringconditionally required (when the invoked agent is a hosted resource with an id)OpenTelemetry GenAIEmitted. [2]
gen_ai.agent.namestringconditionally required (when the agent is known)OpenTelemetry GenAIEmitted. [3]
gen_ai.agent.versionstringrecommendedOpenTelemetry GenAIEmitted. [4]
gen_ai.conversation.idstringrecommendedOpenTelemetry GenAIStored, not emitted. [5]
gen_ai.data_source.idstringconditionally required (when the retrieval has a single identifiable data source)OpenTelemetry GenAIEmitted. [6]
gen_ai.input.messagesanyopt inOpenTelemetry GenAIEmitted.
gen_ai.main_agent.descriptionstringrecommendedOpenTelemetry GenAIEmitted. [7]
gen_ai.main_agent.idstringconditionally required (always, whenever the main-agent entity is emitted)OpenTelemetry GenAIEmitted. [8]
gen_ai.main_agent.namestringrecommendedOpenTelemetry GenAIEmitted. [9]
gen_ai.operation.namestringrequiredOpenTelemetry GenAIEmitted.
gen_ai.output.messagesanyopt inOpenTelemetry GenAIEmitted.
gen_ai.output.typestringconditionally required (when the request specifies an output format)OpenTelemetry GenAIEmitted. [10]
gen_ai.provider.namestringconditionally required (when known)OpenTelemetry GenAIEmitted.
gen_ai.request.choice.countintconditionally required (when available and not 1)OpenTelemetry GenAIEmitted. [11]
gen_ai.request.encoding_formatsstring[]recommendedOpenTelemetry GenAIEmitted. [12]
gen_ai.request.frequency_penaltydoublerecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.max_tokensintrecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.modelstringconditionally required (when known)OpenTelemetry GenAIEmitted.
gen_ai.request.presence_penaltydoublerecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.previous_response.idstringrecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.reasoning.levelstringrecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.seedintconditionally required (when the request includes a seed)OpenTelemetry GenAIEmitted.
gen_ai.request.stop_sequencesstring[]recommendedOpenTelemetry GenAIEmitted.
gen_ai.request.streambooleanconditionally required (when streaming)OpenTelemetry GenAIEmitted. [13]
gen_ai.request.stream_cursorstringrecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.temperaturedoublerecommendedOpenTelemetry GenAIEmitted. [14]
gen_ai.request.top_kintrecommendedOpenTelemetry GenAIEmitted.
gen_ai.request.top_pdoublerecommendedOpenTelemetry GenAIEmitted.
gen_ai.response.finish_reasonsstring[]recommendedOpenTelemetry GenAIEmitted.
gen_ai.response.idstringrecommendedOpenTelemetry GenAIEmitted. [15]
gen_ai.response.modelstringrecommendedOpenTelemetry GenAIEmitted.
gen_ai.response.time_to_first_chunkdoublerecommendedOpenTelemetry GenAIEmitted. [16]
gen_ai.retrieval.documentsanyrecommendedOpenTelemetry GenAIEmitted. [17]
gen_ai.retrieval.top_kintrecommendedOpenTelemetry GenAIEmitted. [18]
gen_ai.system_instructionsanyopt inOpenTelemetry GenAIPlanned, not emitted yet. [19]
gen_ai.tool.call.argumentsanyopt inOpenTelemetry GenAIPlanned, not emitted yet. [20]
gen_ai.tool.call.idstringrecommendedOpenTelemetry GenAIEmitted. [21]
gen_ai.tool.call.resultanyopt inOpenTelemetry GenAIPlanned, not emitted yet. [22]
gen_ai.tool.definitionsanyopt inOpenTelemetry GenAIEmitted. [23]
gen_ai.tool.namestringrequiredOpenTelemetry GenAIEmitted.
gen_ai.tool.typestringrecommendedOpenTelemetry GenAIEmitted. [24]
gen_ai.usage.cache_read.input_tokensintrecommendedOpenTelemetry GenAIEmitted.
gen_ai.usage.cache_write.input_tokensintrecommendedOpenTelemetry GenAIEmitted. [25]
gen_ai.usage.input_tokensintrecommendedOpenTelemetry GenAIEmitted.
gen_ai.usage.output_tokensintrecommendedOpenTelemetry GenAIEmitted.
gen_ai.usage.reasoning.output_tokensintrecommendedOpenTelemetry GenAIEmitted.

mcp

KeyTypeRequirement levelDefined inHow Rius uses it
mcp.method.namestringrequiredOpenTelemetry GenAIEmitted. [26]
mcp.protocol.versionstringrecommendedOpenTelemetry GenAIEmitted. [27]

service

KeyTypeRequirement levelDefined inHow Rius uses it
service.instance.idstringrequiredOpenTelemetry v1.44.0Emitted. [28]
service.namestringrequiredOpenTelemetry v1.44.0Emitted. [29]
service.versionstringrecommendedOpenTelemetry v1.44.0Emitted. [30]

session

KeyTypeRequirement levelDefined inHow Rius uses it
session.idstringrecommendedOpenTelemetry v1.44.0Emitted. [31]

telemetry

KeyTypeRequirement levelDefined inHow Rius uses it
telemetry.distro.namestringrequiredOpenTelemetry v1.44.0Planned, not emitted yet. [32]
telemetry.distro.versionstringrequiredOpenTelemetry v1.44.0Planned, not emitted yet. [33]

user

KeyTypeRequirement levelDefined inHow Rius uses it
user.idstringrecommendedOpenTelemetry v1.44.0Emitted. [34]

Notes

[1] error.type: OTel general error.type: on an MCP tools/call it is “tool_error” when the result carries isError (the tool ran and reported failure), else the exception class when the call itself raised.

[2] gen_ai.agent.id: The identifier of a HOSTED agent resource, not of an agent in general: the conventions give an AWS Bedrock agent ARN and a GCP Agent Registry id as the examples, and say it is NOT RECOMMENDED to record in-memory agent instance ids here, because those are transient. So an in-process agent leaves it unset and there is no configured default to fall back to: a name is the only identity such an agent has. Adopted rather than deferred because the callers who do run hosted agents have nowhere else to put the id.

[3] gen_ai.agent.name: The agent this process IS, stamped once on the resource so every span it emits groups under the same name its heartbeats do. Without it the backend falls through to service.name per span, and a process whose agent name differs from its service name has its agents view and its trace list disagreeing about what it is called.

[4] gen_ai.agent.version: The version of the agent an AGENT span INVOKED: the same callee gen_ai.agent.name and gen_ai.agent.id describe on that span, never the process’s own version and never service.version. Registry type is a plain string and the conventions’ own examples are “1.0.0” and “2025-05-01”, so the caller’s value is taken verbatim: there is no format to validate.

[5] gen_ai.conversation.id: pydantic-ai writes it. Neither SDK emits or rewrites it: they write session.id, one name for one fact. The Rius backend falls back to it for session.id (tools/go_reference_only.yaml, GenAIConversationID).

[6] gen_ai.data_source.id: The index, collection or knowledge base a retrieval ran against. Identity, set at span creation: it is what a retriever span is named after and the only thing that distinguishes two otherwise identical searches.

[7] gen_ai.main_agent.description: What the agent this process is does, in free text. Stamped only when configured, and only with gen_ai.main_agent.id. Replaces rius.main_agent.description.

[8] gen_ai.main_agent.id: The stable identifier of the agent this process is, and the identity of the conventions’ gen_ai.main_agent entity: no id, no entity. It is the configured main-agent id when one is set (the SDK setting, or a gen_ai.main_agent.id the operator already put on the resource), otherwise the resolved agent name as is. The name is stable across restarts and readable, which a generated id would not be; the conventions say it MUST NOT come from an in-memory or process-local id. A name-derived id meets that MUST but not the conventions’ SHOULD that the id be the provider-assigned identifier of the agent resource (a Bedrock ARN, a GCP Agent Registry id): configure the main-agent id as one when there is one. Renaming the agent changes a derived id, so the SDKs log a one-time hint that a stable id can be set. With neither an id nor a usable name (empty, or the unknown_service placeholder), no gen_ai.main_agent.* key is emitted at all.

Replaces rius.main_agent.id, which was only ever the configured id: the derived value is never copied into it.

[9] gen_ai.main_agent.name: The name of the agent this process is, from the same setting as gen_ai.agent.name on the resource, and suppressed the same way: a process whose name is the unknown_service placeholder stamps none. Emitted only with gen_ai.main_agent.id, because the conventions make the id the entity’s identity and say the entity is not emitted without one. Replaces rius.main_agent.name.

[10] gen_ai.output.type: The output modality the client ASKED for (text / json / image / speech in the registry today), Conditionally Required when the request specifies one. Identity: it describes the request, so it is known at span creation and must ride pending snapshots. Note the key is NOT under gen_ai.request., so PENDING_IDENTITY_PREFIXES does not cover it and it needs its own allowlist entry. Recorded verbatim rather than validated against the enum: the conventions are free to extend it, and a value we do not recognise is still the caller’s truth. Not content, for the reason the response id is not.

[11] gen_ai.request.choice.count: “The target number of candidate completions to return”: OpenAI’s n.

[12] gen_ai.request.encoding_formats: Reached only through model parameters (and so through the request parameter table), named because the normalizer’s per-key type guard is keyed by it.

[13] gen_ai.request.stream: Streaming, per the GenAI inference-span conventions: gen_ai.request.stream (boolean, Conditionally Required when streaming) and gen_ai.response.time_to_first_chunk (double, seconds, “measured from request issuance”, Recommended for streaming requests). Both are set by record_first_token: a first chunk arriving is what proves the request streamed, and the SDK has no earlier hook for it.

[14] gen_ai.request.temperature: Sampling parameters. Produced from a generation’s model parameters through GEN_AI_REQUEST_PARAMETERS, and by normalization, which promotes them out of a third-party instrumentor’s request bag (llm.invocation_parameters). Names and types are the GenAI registry’s.

[15] gen_ai.response.id: The provider’s own identifier for the completion (“chatcmpl-123”), Recommended on an inference span. Metadata: it exists only once the provider has answered, so it is a post-call setter and never reaches a pending snapshot. Not content (an opaque id says nothing about what was said), so it survives capture_content=False.

[16] gen_ai.response.time_to_first_chunk: The whole time-to-first-chunk signal: no first-token event is written beside it. For a span with no first-chunk event, the backend synthesizes the gen_ai.first_token event it stores from this value, at span start plus the duration and never after the span’s end; a recorded first-chunk event keeps its own time. Seconds since the span’s start. Natively, record_first_token sets it. The normalizers fill it, never overwriting a value the span carries: from the conventions’ metric spelling gen_ai.client.operation.time_to_first_chunk (pydantic-ai, @ai-sdk/otel) or the Vercel AI SDK’s ai.response.msToFirstChunk, and otherwise from a producer’s first-chunk marker event (OpenInference’s “First Token Stream Event”, the Vercel AI SDK’s ai.stream.firstChunk) as the first marker’s time minus the span’s start, never negative. That marker is left on the span as recorded, and its time is not clamped to the span’s end: LlamaIndex ends its stream_chat span before the first chunk arrives, and the backend keeps a recorded marker’s time as it is.

[17] gen_ai.retrieval.documents: What the retriever RETURNED, as the conventions define it: a JSON array of objects each with an optional id and an optional score. Identifiers and relevance only, never document text, which is why the spec does not mark it sensitive and why it is NOT on the content allowlist. It is metadata: known only once the search has run, so it never reaches a pending snapshot, and it survives capture_content=False because a document id is not content.

[18] gen_ai.retrieval.top_k: How many documents the retriever was ASKED for, the spec’s own framing (“also known as k, limit, or max_num_results”). Identity: it is a property of the request, known before the search runs, so it rides pending snapshots.

[19] gen_ai.system_instructions: The system prompt, apart from the input messages. The TypeScript SDK writes it when it normalizes a Vercel AI SDK span, as an inline literal with no semconv constant, which is why the row is planned rather than shipped. Content, listed in CONTENT_ATTRIBUTES by key.

[20] gen_ai.tool.call.arguments: The arguments the model passed to the tool. The TypeScript SDK writes it from the Vercel AI SDK’s ai.toolCall.args, as an inline literal with no semconv constant, which is why the row is planned rather than shipped. Content, listed in CONTENT_ATTRIBUTES by key.

[21] gen_ai.tool.call.id: The identifier of the tool call this execution answers: the id the MODEL minted on its tool-call message, which is what joins a tool span back to the generation that asked for it. Identity, set at span creation, so it needs an allowlist entry. Never derived: only the caller holds the model’s response. Its sibling gen_ai.tool.call.arguments IS content and is listed in CONTENT_ATTRIBUTES; an opaque id is not.

[22] gen_ai.tool.call.result: What the tool returned. Written as gen_ai.tool.call.arguments is, from ai.toolCall.result; planned for the same reason. Content, listed in CONTENT_ATTRIBUTES by key.

[23] gen_ai.tool.definitions: The request’s tool/function definitions, serialized verbatim (provider shapes differ; the backend reads names and sizes from either). Content, not identity, so it is listed in CONTENT_ATTRIBUTES.

[24] gen_ai.tool.type: What KIND of tool ran, the conventions’ own examples being “function” (client-side), “extension” (agent-side) and “datastore”. Identity for the same reason the call id is: caller-supplied, known before the tool runs. A free string, not an enum, in the registry.

[25] gen_ai.usage.cache_write.input_tokens: semconv-genai renamed cache_creation -> cache_write (PR #440) before our cache fields first shipped; no released SDK version carries the old name.

[26] mcp.method.name: OTel MCP semantic conventions (semantic-conventions-genai, Development stability). mcp.method.name is the REQUIRED attribute of an MCP client span and the marker everything downstream keys on: a local TOOL span has the same kind, name and I/O shape, so this is what tells the two apart.

[27] mcp.protocol.version: The version the initialize handshake negotiated, not the one we asked for.

[28] service.instance.id: OTel standard identity of one process lifetime (one uuid per client, minted at init). The heartbeat payload’s instance_id carries the SAME value, which is what lets the backend join heartbeats to traces and count replicas.

[29] service.name: OTel standard logical service name. The backend falls back to it when a process names no agent.

[30] service.version: OTel standard version of the deployed service. Metadata about the process, not about any one span. Stamped only when a version is actually known: there is deliberately no placeholder default, because a fake one merges every deployment into a single bucket, the same mistake unknown_service makes for service.name.

[31] session.id: The session grouping key. OpenInference’s spelling, and the one the sink reads first; gen_ai.conversation.id is deliberately not emitted alongside it, one name for one fact.

[32] telemetry.distro.name: Names the distribution that exported the span. Both SDKs set it today as an inline literal with no semconv constant, which is why the row is planned rather than shipped.

[33] telemetry.distro.version: The SDK version that exported the span. Inline literal in both SDKs today, like telemetry.distro.name.

[34] user.id: The end-user identity. OpenInference’s spelling, also what Langfuse reads and what the OTel registry lists; the sink additionally accepts OTel’s enduser.id / enduser.pseudo.id from third-party instrumentors, but the SDKs emit one name for one fact.

Last updated on