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
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
error.type | string | conditionally required (on error) | OpenTelemetry v1.44.0 | Emitted. [1] |
exception
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
exception.type | string | recommended | OpenTelemetry v1.44.0 | Emitted. |
gen_ai
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
gen_ai.agent.id | string | conditionally required (when the invoked agent is a hosted resource with an id) | OpenTelemetry GenAI | Emitted. [2] |
gen_ai.agent.name | string | conditionally required (when the agent is known) | OpenTelemetry GenAI | Emitted. [3] |
gen_ai.agent.version | string | recommended | OpenTelemetry GenAI | Emitted. [4] |
gen_ai.conversation.id | string | recommended | OpenTelemetry GenAI | Stored, not emitted. [5] |
gen_ai.data_source.id | string | conditionally required (when the retrieval has a single identifiable data source) | OpenTelemetry GenAI | Emitted. [6] |
gen_ai.input.messages | any | opt in | OpenTelemetry GenAI | Emitted. |
gen_ai.main_agent.description | string | recommended | OpenTelemetry GenAI | Emitted. [7] |
gen_ai.main_agent.id | string | conditionally required (always, whenever the main-agent entity is emitted) | OpenTelemetry GenAI | Emitted. [8] |
gen_ai.main_agent.name | string | recommended | OpenTelemetry GenAI | Emitted. [9] |
gen_ai.operation.name | string | required | OpenTelemetry GenAI | Emitted. |
gen_ai.output.messages | any | opt in | OpenTelemetry GenAI | Emitted. |
gen_ai.output.type | string | conditionally required (when the request specifies an output format) | OpenTelemetry GenAI | Emitted. [10] |
gen_ai.provider.name | string | conditionally required (when known) | OpenTelemetry GenAI | Emitted. |
gen_ai.request.choice.count | int | conditionally required (when available and not 1) | OpenTelemetry GenAI | Emitted. [11] |
gen_ai.request.encoding_formats | string[] | recommended | OpenTelemetry GenAI | Emitted. [12] |
gen_ai.request.frequency_penalty | double | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.max_tokens | int | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.model | string | conditionally required (when known) | OpenTelemetry GenAI | Emitted. |
gen_ai.request.presence_penalty | double | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.previous_response.id | string | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.reasoning.level | string | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.seed | int | conditionally required (when the request includes a seed) | OpenTelemetry GenAI | Emitted. |
gen_ai.request.stop_sequences | string[] | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.stream | boolean | conditionally required (when streaming) | OpenTelemetry GenAI | Emitted. [13] |
gen_ai.request.stream_cursor | string | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.temperature | double | recommended | OpenTelemetry GenAI | Emitted. [14] |
gen_ai.request.top_k | int | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.request.top_p | double | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.response.finish_reasons | string[] | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.response.id | string | recommended | OpenTelemetry GenAI | Emitted. [15] |
gen_ai.response.model | string | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.response.time_to_first_chunk | double | recommended | OpenTelemetry GenAI | Emitted. [16] |
gen_ai.retrieval.documents | any | recommended | OpenTelemetry GenAI | Emitted. [17] |
gen_ai.retrieval.top_k | int | recommended | OpenTelemetry GenAI | Emitted. [18] |
gen_ai.system_instructions | any | opt in | OpenTelemetry GenAI | Planned, not emitted yet. [19] |
gen_ai.tool.call.arguments | any | opt in | OpenTelemetry GenAI | Planned, not emitted yet. [20] |
gen_ai.tool.call.id | string | recommended | OpenTelemetry GenAI | Emitted. [21] |
gen_ai.tool.call.result | any | opt in | OpenTelemetry GenAI | Planned, not emitted yet. [22] |
gen_ai.tool.definitions | any | opt in | OpenTelemetry GenAI | Emitted. [23] |
gen_ai.tool.name | string | required | OpenTelemetry GenAI | Emitted. |
gen_ai.tool.type | string | recommended | OpenTelemetry GenAI | Emitted. [24] |
gen_ai.usage.cache_read.input_tokens | int | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.usage.cache_write.input_tokens | int | recommended | OpenTelemetry GenAI | Emitted. [25] |
gen_ai.usage.input_tokens | int | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.usage.output_tokens | int | recommended | OpenTelemetry GenAI | Emitted. |
gen_ai.usage.reasoning.output_tokens | int | recommended | OpenTelemetry GenAI | Emitted. |
mcp
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
mcp.method.name | string | required | OpenTelemetry GenAI | Emitted. [26] |
mcp.protocol.version | string | recommended | OpenTelemetry GenAI | Emitted. [27] |
service
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
service.instance.id | string | required | OpenTelemetry v1.44.0 | Emitted. [28] |
service.name | string | required | OpenTelemetry v1.44.0 | Emitted. [29] |
service.version | string | recommended | OpenTelemetry v1.44.0 | Emitted. [30] |
session
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
session.id | string | recommended | OpenTelemetry v1.44.0 | Emitted. [31] |
telemetry
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
telemetry.distro.name | string | required | OpenTelemetry v1.44.0 | Planned, not emitted yet. [32] |
telemetry.distro.version | string | required | OpenTelemetry v1.44.0 | Planned, not emitted yet. [33] |
user
| Key | Type | Requirement level | Defined in | How Rius uses it |
|---|---|---|---|---|
user.id | string | recommended | OpenTelemetry v1.44.0 | Emitted. [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.