Skip to main content

Attribute conventions

The observability ecosystem hasn't agreed on attribute names for LLM telemetry yet. Langfuse and SigNoz use the (pre-standard) gen_ai.* convention from OTel's GenAI SIG; Phoenix uses Arize's OpenInference with llm.token_count.* and input.value / output.value.

hermes-otel emits both conventions on the same span so whichever backend reads from it picks up the data it's expecting. No vendor-specific adapter code per backend.

Token counts (on api.* spans)​

MetricLangfuse / gen_aiPhoenix / OpenInference
Prompt tokensgen_ai.usage.input_tokensllm.token_count.prompt
Completion tokensgen_ai.usage.output_tokensllm.token_count.completion
Total tokensgen_ai.usage.total_tokensllm.token_count.total
Cache readgen_ai.usage.cache_read_input_tokensllm.token_count.prompt_details.cache_read
Cache writegen_ai.usage.cache_creation_input_tokensllm.token_count.prompt_details.cache_write
Reasoninggen_ai.usage.reasoning.output_tokensllm.token_count.completion_details.reasoning

Cache read/write are only populated when the provider reports them (Anthropic's prompt caching, OpenAI's — both surface them in their API responses). Reasoning tokens are emitted only by reasoning-capable models and are a subset of completion/output tokens (already counted in the completion and total figures), surfaced separately for visibility — don't add them on top of the total.

Message content (on llm.* spans)​

Langfuse / gen_aiPhoenix / OpenInference
User messagegen_ai.input.messages (message JSON)input.value
Assistant responsegen_ai.output.messages (message JSON)output.value
Content type(not set)input.mime_type, output.mime_type

When conversation capture is on, input.value and gen_ai.input.messages become JSON of the full message list, input.mime_type becomes application/json, and hermes.conversation.message_count records how many messages were passed.

Model / request metadata​

Langfuse / gen_aiPhoenix / OpenInference
Model namegen_ai.request.modelllm.model_name
Providergen_ai.provider.name and the older gen_ai.systemllm.provider
Request parametersgen_ai.request.temperature, gen_ai.request.max_tokens, gen_ai.request.top_p, … (on api.*)llm.request.max_tokens
Finish reasongen_ai.response.finish_reasons (an array)llm.response.finish_reason

Request parameters are set only when Hermes passes them to the hook.

Tool spans​

Tool spans carry both conventions too:

AttributeMeaning
tool.name / gen_ai.tool.nameTool name
gen_ai.tool.call.idThe model's tool-call id (also on the approval.* span that gated it)
input.value / gen_ai.tool.call.argumentsTool args (JSON string)
output.value / gen_ai.tool.call.resultTool result (string)
hermes.tool.targetInferred file / URL (plugin-specific)
hermes.tool.commandInferred shell command (plugin-specific)
hermes.tool.outcomecompleted / error / timeout / blocked / cancelled (plugin-specific)
hermes.skill.nameInferred skill name (plugin-specific, optional)

See Tool identity.

Session / turn metadata (on the agent / cron root)​

AttributeMeaning
hermes.session.kindcli / telegram / discord / cron / ...
hermes.session.idHermes session ID
session.idStandard OTel alias of the above (gen_ai.conversation.id is the GenAI one)
user.idplatform:sender on gateway sessions, only with capture_sender_id: true
hermes.turn.*Turn summary (see Turn summary)

Resource-level attributes​

Set on the OTel Resource and therefore stamped on every span:

AttributeSource
service.namehermes-agent, or the profile name with service_name_from_profile (override via resource_attributes:; OTEL_SERVICE_NAME has no effect)
service.instance.idA UUID per process, so two Hermes processes never share a metric series
service.versionhermes-otel plugin version
process.pidProcess id
hermes.profileThe Hermes profile this process serves (also on the root span)
hermes.kanban.task_id, hermes.kanban.board, hermes.kanban.run_idA dispatcher-spawned kanban worker's card, board and attempt; absent elsewhere
openinference.project.nameproject_name / OTEL_PROJECT_NAME (the Phoenix project)
host.nameOnly with host_metrics: true
plus any resource_attributes: (and the deprecated global_tags:) from hermes_otel.yaml

The full list with every source is in the span attributes reference.

common_attributes: is the per-signal counterpart: its keys are set on every span, every log record and every metric point as plain attributes (never on the Resource), for backends that do not index resource attributes per signal. See Which attributes reach which signal.

Why dual-convention rather than pick one?​

Every backend supports a different set. Emitting both is cheap (same span, a few extra key-value pairs) and saves every user from writing their own mapping adapter. When the GenAI spec stabilises and Phoenix/Langfuse converge, this will simplify.

Roadmap​

  • OpenInference Tool span kind is now stable — already emitted.
  • The gen_ai.tool.* names are emitted already; they will follow the GenAI spec as it changes.
  • session.id is the standard OTel name; the plugin emits both hermes.session.id (for compatibility with older backends that key on it) and session.id. The former may be dropped in a future major version — watch the changelog.