Observability
The AIM server emits OpenTelemetry traces, metrics, and logs over OTLP. Every authorization decision is traced, so you can see which agent took which action, which Fine-Grained Authorization step allowed or denied it, and how long each step took.
Demo stack
A hermetic demo stack lives in the repository: an OpenTelemetry Collector, Tempo, Prometheus, Loki, and Grafana. Bring it up and run the smoke test to confirm traces, metrics, and logs land end to end.
cd apps/backend/deployments/otel-demo
docker compose up -d
./smoke-backend.shAuthorization spans
Every fga.authorize decision lands as a parent span with five child spans, one per Fine-Grained Authorization step. The parent span carries a fixed set of semantic-convention attributes so traces are queryable by agent, capability, and outcome.
| Attribute | Meaning |
|---|---|
| agent.id | The AIM agent UUID |
| agent.public_key.algorithm | Signing algorithm for the agent identity |
| agent.trust_score | Trust score at decision time |
| agent.drift_score | Behavioral drift signal |
| agent.scan_verdict | Latest scan verdict for the agent |
| agent.capability | The capability being authorized |
| fga.step | The Fine-Grained Authorization step |
| fga.outcome | Allowed or denied |
| fga.denied_by | The step that denied, when denied |
These attribute names are proposed to the OpenTelemetry Semantic Conventions working group, so AIM traces use stable, vendor-neutral names rather than custom keys.
Metrics and logs
Authorization decisions are also emitted as counters and as structured logs that share the same attribute keys, so a Prometheus query, a Tempo trace, and a Loki log line for the same decision line up on agent.id and fga.outcome.
Full design notes, the complete attribute reference, and exporter configuration are in apps/backend/docs/OBSERVABILITY.md in the repository.
Query examples
Because the signals share attribute keys, the same decision is reachable from traces, metrics, and logs. A few starting queries:
Tempo (TraceQL): denied authorizations for one agent
{name="fga.authorize" && .agent.id="<uuid>" && .fga.outcome != "ALLOW"}Prometheus (PromQL): decision rate by outcome, and p99 latency
sum by (fga_outcome) (rate(fga_decisions_total[1m]))
histogram_quantile(0.99, sum by (le) (rate(fga_latency_ms_bucket[5m])))Loki (LogQL): denial log lines, click through to the trace
{service_name="aim-backend"} |= "fga.decision" | json | fga_outcome != "ALLOW"