Skip to main content

Span hierarchy

Every Hermes turn produces a nested tree of spans. This page documents what each one is for and the data it carries.

The tree

session.{platform} / cron [GENERAL]
└── llm.{model} [LLM]
├── api.{model} [LLM]
│ └── tool.{name} [TOOL]
│ └── tool.{name} [TOOL] (parallel tool calls — siblings)
└── api.{model} [LLM] (second round-trip after tool results)
  • session.* / cron — the root for each turn. Present when session hooks are available in the Hermes build; absent on older versions (the llm.* span becomes the root).
  • llm.* — one per logical model turn. Wraps one or more HTTP round-trips to the provider.
  • api.* — one per HTTP round-trip. Tools run during a round-trip, so their parent is api.*, not llm.*.
  • tool.* — one per tool invocation. Parallel tool calls are siblings under the same api.*.

session.* / cron

The root span. Name is derived from the Hermes session kind (session.cli, session.telegram, session.discord, cron, etc.).

Span kind: GENERAL (no OpenInference-specific kind).

Key attributes, set at start:

AttributeSource
openinference.project.nameOTEL_PROJECT_NAME / HERMES_OTEL_PROJECT_NAME
hermes.session.kindFrom Hermes (cli, telegram, cron, etc.)
hermes.session.idHermes session ID
session.idSame as above, standard OTel naming
user.idHermes user ID (when available)

Key attributes, set at end (the turn summary):

AttributeTypeMeaning
hermes.turn.tool_countintDistinct tool names invoked
hermes.turn.toolsstringSorted CSV of distinct tool names (≤500 chars)
hermes.turn.tool_targetsstring|-joined distinct file paths / URLs
hermes.turn.tool_commandsstring|-joined distinct shell commands
hermes.turn.tool_outcomesstringSorted CSV of distinct outcome statuses
hermes.turn.skill_countintDistinct skills inferred
hermes.turn.skillsstringSorted CSV of distinct skill names
hermes.turn.api_call_countintNumber of pre_api_request hooks fired
hermes.turn.final_statusstringcompleted · interrupted · incomplete · timed_out

See Turn summary for why these exist.

llm.*

One per logical model turn. Name is llm.{model} (e.g. llm.claude-sonnet-4-6).

Span kind: LLM (OpenInference).

AttributeConventionMeaning
llm.model_nameOpenInferenceModel name
llm.providerOpenInferenceProvider (anthropic, openai, etc.)
input.valueOpenInferenceUser message or full conversation history (see below)
input.mime_typeOpenInferencetext/plain or application/json
output.valueOpenInferenceFinal assistant response
output.mime_typeOpenInferencetext/plain
gen_ai.request.modelgen_aiModel name (for Langfuse / SigNoz)
gen_ai.content.promptgen_aiUser message (same content as input.value when both are strings)
gen_ai.content.completiongen_aiAssistant response
hermes.conversation.message_counthermes-specificWhen capture_conversation_history: true

By default input.value is the latest user turn only. To see the full message list the model saw, enable conversation capture.

api.*

One per HTTP round-trip to the provider. Name is api.{model}.

Span kind: LLM (OpenInference).

AttributeConventionMeaning
llm.token_count.promptOpenInferencePrompt tokens
llm.token_count.completionOpenInferenceCompletion tokens
llm.token_count.totalOpenInferenceSum of the above
llm.token_count.cache_readOpenInferencePrompt tokens read from cache (if provider reports)
llm.token_count.cache_writeOpenInferencePrompt tokens written to cache (if provider reports)
gen_ai.usage.input_tokensgen_aiPrompt tokens (for Langfuse)
gen_ai.usage.output_tokensgen_aiCompletion tokens (for Langfuse)
gen_ai.usage.cache_read_input_tokensgen_aiCache read (if provider reports)
gen_ai.usage.cache_creation_input_tokensgen_aiCache write (if provider reports)
llm.invocation_parametersOpenInferenceJSON of temperature, max_tokens, etc.
gen_ai.response.finish_reasongen_aistop, tool_use, length, etc.
http.duration_mshermes-specificWall-clock duration of the HTTP call

The api.* span is the right place to look for token counts — not the parent llm.* (which doesn't carry per-call counts, because a turn can have multiple api.* calls).

tool.*

One per tool invocation. Name is tool.{name} (e.g. tool.bash, tool.read_file).

Span kind: TOOL (OpenInference).

AttributeConventionMeaning
tool.nameOpenInferenceTool name
input.valueOpenInferenceTool args (JSON)
output.valueOpenInferenceTool result
hermes.tool.targethermes-specificInferred file path / URL (see Tool identity)
hermes.tool.commandhermes-specificInferred shell command
hermes.tool.outcomehermes-specificcompleted · error · timeout · blocked
hermes.skill.namehermes-specificSkill inferred from args paths (optional)

Errors: hermes.tool.outcome=error also maps the span's StatusCode to ERROR. Timeouts and blocked tools stay OK so dashboards don't count them as failures.

Why this shape?

The tree mirrors the agent's execution structure:

  • One root per turn so you can filter "one user question worth of work" in the backend UI.
  • llm.* as a logical parent of all api.* because the conversation-with-the-model is one coherent thing even when it takes multiple HTTP calls.
  • tool.* under api.* because tools run between rounds of model inference, within a specific HTTP response's tool_calls. The api.* parent makes that explicit.

See Attribute conventions for the dual-convention mapping side-by-side.