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
| Metric | Unit | Attributes |
|---|---|---|
ototo.calls | 1 | tool, repo, model, endpoint, outcome (ok, flagged, unfinished, error) |
ototo.local_tokens | tokens | tool, model, endpoint, type (input, output) |
ototo.turns | 1 | tool, model |
ototo.duration | s (histogram) | tool, delegated |
ototo.fallbacks | 1 | failed, answered_by |
ototo.cost.usage | USD | model, endpoint |
ototo.claims | 1 | tool, verdict (backed, unbacked, unchecked, contradicted) |
ototo.reply.chars | chars | tool |
ototo.server | gauge | version, OS, CPU, plugins, where its settings come from, whether Claude Code’s setup is managed, its endpoints |
mcp.server.operation.duration | s (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.emailis 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>inotlp_attributessends another, anduser.email=(empty) sends none.- The standard
OTEL_RESOURCE_ATTRIBUTESadds attributes (deployment.environment.name=prod,team=…);otlp_attributeswins over it. Either can setservice.namespace; neither can changeservice.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.
With no collector of your own, the hub is one to run, with Prometheus and Grafana and two dashboards over both sets of metrics.