Metrics Reference
This page is the consolidated reference for the metrics AxonFlow emits: what each one measures, which labels it carries, where it lives (a Prometheus counter you scrape, or a usage record you query), and which edition provides it.
Two conventions used throughout:
- Edition. Community metrics are emitted by every edition (Enterprise deployments include all Community metrics). Enterprise metrics require an Enterprise deployment — in Community the features behind them are absent (their ingest endpoints return
501). - Store. Most metrics are Prometheus series scraped from the agent's and orchestrator's
/prometheusendpoints. The Claude Code usage counters are different: they are stored as usage records in the platform database, not as Prometheus series. Queries and dashboards must target the right store — a PromQL query will never see a usage record, and vice versa.
Aggregate metrics vs detailed logs
Metrics answer "how much / how often" — request volume, block rates, token and cost totals, per-developer usage. They are aggregates, and Grafana is the right surface for them.
They are deliberately not the audit trail. Every individual policy decision — who asked, what was decided, which policy fired, what was redacted — is recorded as a per-decision audit record with a signed decision chain. For that detail, use the audit log and the customer portal (decisions feed, log explorer, per-decision drill-down). A useful rule of thumb: if the question contains a who or a which request, you want the portal's detailed logs; if it contains a how many or a trend, you want these metrics in Grafana.
Core runtime metrics (Community)
Emitted by the agent and orchestrator; scrape /prometheus on each. These drive the bundled Grafana dashboard.
Agent
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_agent_requests_total | counter | status | Requests processed by the agent |
axonflow_agent_request_duration_milliseconds | histogram | type | Agent request latency |
axonflow_agent_policy_evaluations_total | counter | — | Policy-engine evaluations (an evaluation can allow or block) |
axonflow_agent_blocked_requests_total | counter | — | Requests blocked by policy |
Gateway mode
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_gateway_precheck_requests_total | counter | status, approved | Gateway pre-check calls |
axonflow_gateway_precheck_duration_milliseconds | histogram | — | Pre-check policy-evaluation latency |
axonflow_gateway_audit_requests_total | counter | status, provider | Gateway audit calls |
axonflow_gateway_audit_duration_milliseconds | histogram | — | Gateway audit latency |
axonflow_gateway_llm_tokens_total | counter | provider, model, type | LLM tokens reported through gateway audit |
axonflow_gateway_llm_cost_usd_total | counter | provider, model | Estimated LLM spend (USD) reported through gateway audit |
axonflow_gateway_rbi_pii_detected_total | counter | pii_type, blocked | India (RBI) PII detections in pre-check |
axonflow_gateway_indonesia_pii_detected_total | counter | pii_type, blocked | Indonesia PII detections in pre-check |
Orchestrator & connectors
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_orchestrator_requests_total | counter | status | Requests processed by the orchestrator |
axonflow_orchestrator_request_duration_milliseconds | histogram | type | Orchestrator request latency (routing + LLM execution) |
axonflow_orchestrator_policy_evaluations_total | counter | — | Dynamic-policy evaluations |
axonflow_orchestrator_blocked_requests_total | counter | — | Requests blocked by dynamic policies |
axonflow_orchestrator_llm_calls_total | counter | provider, status | LLM provider API calls |
axonflow_connector_calls_total | counter | connector, operation, status | MCP connector calls |
axonflow_connector_duration_milliseconds | histogram | connector, operation | MCP connector latency |
axonflow_connector_errors_total | counter | connector, operation, error_type | MCP connector failures |
Decision Mode metrics (Community)
Emitted for POST /api/v1/decide traffic. The origin label is a closed six-value integration bucket (claude-code, claude-desktop, sdk, plugin, gateway, unknown) — see the Grafana dashboard page for how it is classified.
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_decision_requests_total | counter | verdict, stage, origin | Decisions by verdict (allow / deny / needs_approval), stage (llm / tool / agent) and caller integration |
axonflow_decision_duration_milliseconds | histogram | origin | Decision handler latency |
axonflow_decision_obligations_total | counter | obligation, stage, origin | Obligations attached to an allow verdict — obligation="redact_pii" is the "redacted" signal (redaction is an obligation on allow, not a separate verdict) |
axonflow_decision_blocks_total | counter | policy, origin | Deny verdicts by the single blocking policy (per-tenant custom policy ids are collapsed to tenant_custom to keep cardinality bounded) |
axonflow_decision_audit_write_failures_total | counter | reason | Failures writing the decision's canonical audit record (nodb / empty_decision_id / marshal / insert) — insert or marshal is a persistence failure worth alerting on |
OTLP-ingest metrics (Enterprise, v9.5.0+)
Enterprise deployments can ingest the native OpenTelemetry stream Claude Code and Claude Cowork emit — per-request events on POST /v1/logs, aggregate usage counters on POST /v1/metrics. See Claude Cowork & Claude Code OTEL Ingest for setup. In Community both endpoints return 501 — the metrics in this section only exist on Enterprise deployments.
Export-reject counter (Prometheus)
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_otel_ingest_rejected_total | counter | route, tenant, reason | OTLP export requests rejected (status ≥ 400) at /v1/logs and /v1/metrics |
This is the self-diagnosis signal for a misconfigured exporter (an OTLP client fails silently on its side). route is the ingest path; tenant is the Basic-auth org the client attempted (a bounded label set — an unauthenticated caller cannot mint unbounded series, and because it is the attempted name, corroborate a surprising spike with the agent's [OTELIngest] log lines before concluding a customer-side misconfig); reason folds the HTTP status into a fixed set: unauthorized, bad_request, forbidden, body_too_large, unsupported_media_type, rate_limited, not_implemented, storage_unavailable, server_error, client_error.
Claude Code usage counters (usage records — not Prometheus)
The usage counters that arrive on /v1/metrics are not re-exported as Prometheus series. Each accepted datapoint becomes a row in the platform's canonical usage store (usage_events, event_type = 'claude_code_metric'), keyed on the session and developer the telemetry asserts and org-tagged from the authenticated license. Stored values are delta-normalized — cumulative exports are converted to increments at ingest — so summing stored values per metric is always correct, regardless of the exporter's temporality.
The accepted metric names are a closed allowlist (anything else is rejected, visible in the export's partial_success response):
| Metric name | What it counts | Stored attributes you will typically slice by |
|---|---|---|
claude_code.token.usage | Tokens consumed | type (input / output / cacheRead / cacheCreation), model |
claude_code.cost.usage | Estimated cost (USD) | model |
claude_code.session.count | CLI sessions started | — |
claude_code.lines_of_code.count | Lines of code changed | type (added / removed) |
claude_code.commit.count | Git commits created | — |
claude_code.pull_request.count | Pull requests created | — |
claude_code.code_edit_tool.decision | Claude Code's local edit-tool permission prompts | decision (accept / reject), tool_name, language, source |
claude_code.active_time.total | Active usage time (seconds) | — |
Attributes pass a strict structural allowlist at ingest — unknown keys (including anything injected via OTEL_RESOURCE_ATTRIBUTES) are dropped before storage. Token deltas of type input / output (and cost) are additionally mirrored into the store's aggregate token/cost columns, so org-level token and cost rollups include Claude Code usage automatically; cache-token usage stays queryable via the type attribute without inflating those rollups.
Building a usage view over these records
Because these are database records, you query them with SQL (or read the surfaces that already do):
- Customer portal — the portal's org-level usage reporting reads the same store, and its token/cost rollups include Claude Code usage automatically (via the mirrored token/cost columns). The portal aggregates by event type, not by developer — for per-developer / per-session breakdowns use the Grafana dashboard or direct SQL below.
- Grafana — Enterprise deployments ship a dedicated Claude Code Usage dashboard (per-developer / per-session tokens, cost, lines of code, tool-permission decisions, active time, plus the export-reject counter) whose panels query the platform database through a PostgreSQL datasource. The metrics it reads land from v9.5.0; the dashboard is provisioned by the Grafana image of the first platform release after v9.5.0 (on v9.5.0 itself, import the JSON manually). See the Grafana dashboard page.
- Direct SQL — for ad-hoc questions, sum the stored deltas. For example, tokens per developer over the last 7 days:
SELECT user_email,
SUM(metric_value) FILTER (WHERE metric_attributes->>'type' IN ('input', 'output')) AS tokens
FROM usage_events
WHERE event_type = 'claude_code_metric'
AND metric_name = 'claude_code.token.usage'
AND metric_time > NOW() - INTERVAL '7 days'
GROUP BY user_email
ORDER BY tokens DESC;
user_email and session_id are attribution labels asserted by the telemetry (see Per-Developer Identity for when a developer email is present at all); the org boundary on every row comes from the authenticated license.
usage_events is row-level-secured. Query it as the table owner or a BYPASSRLS role (the bundled local datasource uses the owner). Any other role gets zero rows back silently — the RLS predicate keys on a per-session org setting that a reporting connection never sets — which is easy to misread as "no usage".
The flip side is a security property, not just a wiring detail: a reporting connection that works at all is bypassing row-level security, so it can read every org's usage records. Anything built on it — the Claude Code Usage dashboard included — shows cross-org data, and an org filter in a dashboard is a display slice, not a tenant-isolation boundary. Treat Grafana and any direct-SQL reporting access as operator-only surfaces; tenants get their own data through the customer portal, which enforces tenant scoping.
Span-derived metrics (OTel Collector overlay)
When you run the observability exporters overlay, the OTel Collector's spanmetrics connector derives RED metrics (calls_total, duration histograms) from axonflow.decision spans, with decision.verdict / decision.stage / decision.origin as dimensions. These are generated by the collector — not emitted by AxonFlow directly — and exist only where that pipeline runs.
A note on completeness
The /prometheus endpoints expose additional operational series beyond this reference (HITL queue activity, circuit-breaker state, plugin-license gauges, and other internals). They are self-describing via their HELP text and are not part of the documented stability surface; the metrics above are the ones the bundled dashboards and this documentation commit to.
Related
- Grafana Dashboard — the bundled dashboards over these metrics
- Token Usage & Cost Tracking — token/cost specifics
- Claude Cowork & Claude Code OTEL Ingest — configuring the OTLP export
- Audit Logging — the per-decision detailed log this page's metrics summarize
- Customer Portal — decisions feed and log explorer for per-decision drill-down
