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 |
Policy-health metrics (Community)
These answer a different question from "how many requests were blocked": is the policy set actually enforcing what its rows say? A policy that silently never fires, or fires with a weaker action than its row records, is invisible in the volume metrics above. Each of these is available from the next platform release.
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_agent_policy_stored_action_displaced_total | counter | category, stored, resolved | Matched detection policies whose stored action the posture lever resolved downward (for example a stored block that resolved to redact). Nonzero means the action column on those rows is not what runs. Tightening is not counted. See what the Action column means at runtime. |
axonflow_policy_condition_unevaluable_total | counter | reason, plane | Tenant-policy conditions that could not be evaluated. reason is a closed set: unknown_operator, non_numeric_operand, non_string_pattern, conditions_unmarshal_failed, field_unresolved, and empty_conditions. plane names the call site (memory, database, mcp, policy_test). |
axonflow_active_policy_count_errors_total | counter | - | Free-tier active-policy quota checks that failed and fell back to fail-open. The quota fails open deliberately so a transient database blip cannot lock out a legitimate user, but a sustained nonzero rate means the quota is not being enforced. |
Two of these are worth an alert rather than a dashboard panel:
axonflow_policy_condition_unevaluable_total{reason="empty_conditions"}above zero after an upgrade means you are carrying stored policies with an explicitly empty conditions list. They are excluded from evaluation on every plane; see a policy with no conditions for how to remediate them.axonflow_agent_policy_stored_action_displaced_totalabove zero means at least one policy row readsblockwhile the deployment's posture resolves it to something weaker. That is the designed authority order, not a fault, but it should be a deliberate choice rather than a surprise during a compliance review.
Client version-distribution metrics (Enterprise, v9.7.0+)
AxonFlow clients identify themselves with the X-Axonflow-Client: <client>/<version> header (see the auth and header matrix). From platform v9.7.0, Enterprise deployments record the validated pair as a Prometheus series, so "which plugin / proxy / SDK versions are actually calling us?" is answerable from Grafana. In Community these metrics are not registered - their absence means "not measured", not "no clients".
| Metric | Type | Labels | What it measures |
|---|---|---|---|
axonflow_client_version_requests_total | counter | plane, client, client_version | Requests carrying a valid X-Axonflow-Client header, by client id and version. plane is decision (POST /api/v1/decide, counted per attempt - a later policy deny still counts) or mcp (the check-output plane, counted per authenticated request). Because the two planes count at different points, their series don't reconcile 1:1. |
axonflow_client_version_dropped_total | counter | reason | Header values that were not recorded: absent (no header), invalid (failed shape validation), overflow (series cap reached) |
The capture is telemetry-only and fail-open - it is outside the authentication path, and an absent or malformed header can never affect a verdict or produce an auth failure. Cardinality is strictly bounded:
- Raw header values never become label values. The client id must be a lowercase slug (max 64 chars) and the version a semver-like token (max 32 chars); anything else counts as
invalid. A header with no/separator records the version labelunversioned. - At most 512 distinct
(plane, client, client_version)series are minted per process; new pairs past the cap count asoverflowwhile already-seen pairs keep counting.
This complements - not replaces - the origin label on the Decision Mode metrics: origin buckets callers into six fixed integration values and deliberately discards versions; this metric keeps the validated version, under the bounds above. Clients that send the header include the AxonFlow plugins, the SDKs, and - in releases after v0.3.0 - the Claude Desktop governance proxy (mcp-proxy/<version>).
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 from platform v9.6.0 onward (on a v9.5.0 deployment, import the JSON manually). See the Grafana dashboard page.
- Session-summary API -
GET /api/v1/audit/session-summary(Enterprise, platform v9.6.0+) returns tenant-scoped per-session / per-user-day buckets over the governed audit trail, additively enriched with these usage counters - the right surface when a tenant (rather than an operator) needs a per-session usage report over the API. See the session-summary API 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
