Telemetry Data Reference

Reference for every metric, span, and attribute Droid exports over OpenTelemetry, in both the droid.* and GenAI semantic convention formats.

This page is the reference for what arrives at your collector once OTEL export is configured. The export format decides which of the two surfaces you receive.

Export formats

Your collector can receive one of two conventions. Factory's own collector receives the droid.* metric catalog and is unaffected by this choice.

FormatYour collector receives
legacy (default)The droid.* metric catalog, plus the droid.message.* and droid.tool.* event spans when message content logging is on.
genaiThe OpenTelemetry GenAI semantic conventions: gen_ai.* spans and gen_ai.* metrics, consumable by off-the-shelf GenAI dashboards.

Set it centrally with telemetry.format in org-managed settings, or per machine with OTEL_TELEMETRY_FORMAT. The organization's value wins: where telemetry.format is set to anything at all, the environment variable is not read, and an unrecognized value falls back to legacy rather than to the machine. This keeps a developer's shell from switching an organization's collector onto a different convention.

Warning

The two formats replace each other; they do not stack. Under genai, your collector receives no droid.* metric and no droid.message.* / droid.tool.* span at all. Rebuild dashboards before you switch an organization over, and expect the switch to be visible as a gap in any droid.* query.

Durations also change unit. Every gen_ai.* duration is in seconds, per the semantic conventions. Every droid.* duration is in milliseconds.

Exported metrics

Under the default legacy format, all metrics use the droid.* namespace. Every instrument declares a unit.

MetricInstrumentUnitMeaning
droid.code.files_modifiedCounter{file}Files created or updated by an edit tool.
droid.code.files_readCounter{file}Files read.
droid.code.lines_modifiedCounter{line}Lines added and removed, split by an operation attribute.
droid.git.commitsCounter{commit}Commits created.
droid.git.pull_requestsCounter{pull_request}Pull requests created.
droid.tool.invocationsCounter{invocation}Tool executions.
droid.tool.execution_timeHistogrammsTool execution duration.
droid.command.blockedCounter{command}Commands blocked by policy. Carries the tool name only, never the command text.
droid.mcp.tool_invocationsCounter{invocation}MCP tool calls.
droid.skill.invocationsCounter{invocation}Skill runs.
droid.skill.installedCounter{skill}Inventory of installed non-builtin skills, emitted once per session.
droid.hook.invocationsCounter{invocation}Hook commands run.
droid.slash_command.invocationsCounter{invocation}Slash commands run.
droid.auth.login_successCounter{login}Successful logins.
droid.session.countCounter{session}Sessions started. Compaction successors and forks are not counted.
droid.repo.metadataCounter{repository}One per workspace, carrying repository metadata as attributes.
Note

droid.skill.installed was previously emitted once per model call, which multiplied the count by the number of calls in a session. It is now emitted once per session, so values recorded before Droid 0.199.0 are inflated and are not comparable with later ones.

Common attributes

Every data point includes these attributes automatically:

AttributeDescription
user.idAuthenticated user ID. Removed under aggregate granularity.
user.emailAuthenticated user email. Removed under aggregate granularity.
organization.idOrganization ID
session.idCurrent Droid session ID
model.idActive model ID (when available)
parent_session.idParent session, when DROID_PARENT_SESSION_ID is set

Tool-specific attributes (tool.name, mcp.server, skill.name, etc.) are included where applicable. Resource attributes include service.name (cli), service.version, os.type, os.version, host.arch, and terminal.type, plus telemetry.granularity when the organization runs in aggregate mode.

GenAI semantic convention export

Set telemetry.format to genai and your collector receives the OpenTelemetry GenAI semantic conventions instead of the droid.* surface. This is what off-the-shelf GenAI dashboards expect, so you can point one at Droid without writing custom queries.

Spans

Each user turn produces one flat trace: a root span with the model calls and tool runs as direct children. Tool spans are siblings of model calls, never nested inside them.

SpanKindParent
invoke_agent droidInternalNone. This is the trace root.
chat <model>Clientinvoke_agent droid
execute_tool <tool>Internalinvoke_agent droid

Attributes, by span:

AttributeSpanNotes
gen_ai.operation.nameallinvoke_agent, chat, or execute_tool.
gen_ai.agent.namerootAlways droid.
gen_ai.conversation.idallThe Droid session ID.
session.idallThe same value, kept under its general-convention name so you can join against metrics.
error.typeallPresent on failure. On a tool span, the literal rejected means the call was denied by policy rather than failed.
gen_ai.request.modelchatOmitted when no model ID is known.
gen_ai.provider.namechatSee Provider names.
gen_ai.response.finish_reasonschatAn array of one string.
server.address, server.portchatThe model API host. The port is omitted when the scheme default applies.
gen_ai.tool.name, gen_ai.tool.call.idexecute_toolA model-authored name or ID that fails a shape check is replaced with invalid_tool_name or invalid_tool_use_id.
gen_ai.tool.typeexecute_toolAlways function.
gen_ai.input.messagesrootMessage content only. See Message content logging.
gen_ai.output.messageschatMessage content only.
gen_ai.tool.call.arguments, gen_ai.tool.call.resultexecute_toolMessage content only.

gen_ai.input.messages and gen_ai.output.messages are JSON strings holding the conventions' message array:

JSON
[{ "role": "user", "parts": [{ "type": "text", "content": "..." }] }]

Metrics

Every GenAI instrument is a histogram, including the two call counts. To total tool calls, sum the histogram rather than reading a counter.

MetricUnitMeaning
gen_ai.client.operation.durationsModel call duration.
gen_ai.client.operation.time_to_first_chunksModel call time to first streamed chunk.
gen_ai.execute_tool.durationsTool execution duration.
gen_ai.invoke_agent.durationsTurn duration.
gen_ai.invoke_agent.inference_calls{inference_call}Model calls per turn.
gen_ai.invoke_agent.tool_calls{tool_call}Tool calls per turn.

Datapoints carry gen_ai.operation.name, plus gen_ai.request.model and gen_ai.provider.name on the model instruments, gen_ai.tool.name on the tool instrument, gen_ai.agent.name on the turn instruments, and error.type on failures. The common attributes are stamped on top of those.

Provider names

gen_ai.provider.name is the model vendor, derived from the model ID prefix, not Factory's routing provider. It resolves to one of anthropic, openai, gcp.gemini, x_ai, deepseek, or mistral_ai. A model whose vendor has no value in the conventions gets no provider attribute rather than a guessed one, so do not build a dashboard that requires the attribute to be present.

Note

Two behaviors will otherwise look like gaps in your data:

  • A denied tool call produces a span but no duration datapoint. The call never ran, and a zero-second measurement would distort the distribution of the calls that did. Denied calls still count toward gen_ai.invoke_agent.tool_calls.
  • Subagent turns are separate trace roots. The parent_session.id linkage that the droid.* surface carries has no equivalent in the GenAI conventions, so a subagent's trace cannot be joined to its parent's under this format.

Resource attributes

To attach your own identity or lineage attributes (for example team.id, repo, environment), set the standard OTEL variable per machine:

Bash
export OTEL_RESOURCE_ATTRIBUTES="team.id=platform,environment=ci"

Your pairs reach your collector only. They are never sent to Factory's, including a pair that spoofs an identity key.

They are applied in three places:

  • On the resource of exported metrics.
  • On the resource of exported spans.
  • Spread onto individual metric datapoint attributes, which is legacy behavior kept because existing dashboards query it. There is no span-attribute equivalent.

Two collisions resolve against you. Droid's own resource values win, so a pair setting service.name, service.version, os.type, os.version, host.arch, or terminal.type is ignored. On datapoints, the identity keys user.id, user.email, organization.id, session.id, and parent_session.id are dropped outright rather than merged, so an operator-set pair cannot impersonate a user.

Note

There is no org-managed (Raw Configuration) field for resource attributes, and headers cannot carry them. To attribute headless or CI runs, set OTEL_RESOURCE_ATTRIBUTES, and DROID_PARENT_SESSION_ID for lineage, in each runner's environment.