Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Metrics: reporting over OpenTelemetry

Otōto can send its counts to an OpenTelemetry collector, beside the ones Claude Code sends of itself (claude_code.token.usage, claude_code.cost.usage). Together they show what handing questions to a small model saves, measured rather than estimated: calls, the small model’s tokens and time, and Claude’s cost, by person and by team.

Only counts leave the machine: never a question, code, or an answer. Reporting is off unless you set where it goes.

Turn it on

In ~/.config/ototo/config.toml (or the organisation’s file, for everyone on a machine):

otlp_endpoint = "http://<collector>:4318"         # the collector's OTLP/HTTP port
otlp_headers = "Authorization=Bearer <token>"     # optional, comma-separated
otlp_attributes = "team=platform"                # optional, added to every series
# otlp_interval = 60                              # seconds between sends; also sent when the process exits

ototo doctor says whether the collector accepts what is sent, and which e-mail address goes with it.

What is sent

MetricUnitAttributes
ototo.calls1tool, repo, model, endpoint, outcome (ok, flagged, unfinished, error)
ototo.local_tokenstokenstool, model, endpoint, type (input, output)
ototo.turns1tool, model
ototo.durations (histogram)tool, delegated
ototo.fallbacks1failed, answered_by
ototo.cost.usageUSDmodel, endpoint
ototo.claims1tool, verdict (backed, unbacked, unchecked, contradicted)
ototo.reply.charscharstool
ototo.servergaugeversion, OS, CPU, plugins, where its settings come from, whether Claude Code’s setup is managed, its endpoints
mcp.server.operation.durations (histogram)mcp.method.name (tools/call), gen_ai.tool.name, error.type (tool_error)
gen_ai.client.token.usage{token} (histogram)gen_ai.operation.name (chat), gen_ai.provider.name, gen_ai.request.model, gen_ai.token.type, ototo.endpoint

The last two are OpenTelemetry’s own names (its MCP and GenAI conventions), sent beside Otōto’s so that a dashboard built on those conventions covers Otōto with other MCP servers and model clients. ototo.server is what each running ototo serve says of itself, so a dashboard can show the servers and developers running now, and the versions in use.

Values are cumulative from each process’s start, sent as OTLP/HTTP in JSON. Through a collector’s Prometheus exporter the names gain unit suffixes, as Claude Code’s do: ototo_calls_total, ototo_local_tokens_total, ototo_cost_usage_USD_total beside claude_code_cost_usage_USD_total.

Who and where

Every series carries service.name (ototo), service.namespace, service.version, service.instance.id (one a process), host.name, user.name and user.email.

  • user.email is the address of the Claude account Claude Code is signed in with, which Claude Code puts on its own metrics: the two then compare person by person with nothing set per person. user.email=<address> in otlp_attributes sends another, and user.email= (empty) sends none.
  • The standard OTEL_RESOURCE_ATTRIBUTES adds attributes (deployment.environment.name=prod, team=…); otlp_attributes wins over it. Either can set service.namespace; neither can change service.name.

Claude Code’s own metrics, to the same collector

Claude Code reports its tokens and cost when told where, by its own settings (CLAUDE_CODE_ENABLE_TELEMETRY, OTEL_METRICS_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT). For a team, ototo managed --collector <url> --team <name> writes those into Claude Code’s managed settings with the same team label as Otōto’s: see For organisations.