Why it exists
The web became observable once everyone agreed to log the same fields: every tracing tool understands http.method and http.status_code without being told. Agent authorization has no such vocabulary yet. This proposal adds one, nine OpenTelemetry attributesthat put an agent's identity, trust signals and authorization outcome into standard traces, metrics and logs.
Status: a proposal, emitted today
The attributes areexperimental and filed upstream as OpenTelemetry GenAI SemConv pull request #291 (open, not merged), from issue #180. The AIM reference backend emits these signals today under the earlier names (agent.trust_score and similar); its move to the gen_ai.agent.* names is tracked.The nine attributes
All nine live under gen_ai.agent.*, the namespace the OpenTelemetry GenAI conventions reserve for agents, in two groups: who the agent is, and what is known about it. Each trust signal is paired with a method token so a consumer can tell a changed scoring method from a changed agent.
Identity
- gen_ai.agent.id
- string, stable identifier for the agent instance, e.g. "agent-7f3a9c2e" or a DID.
- gen_ai.agent.public_key.algorithm
- string, the agent's identity key algorithm, e.g. "ed25519", "ml-dsa-65". A verifier that does not recognize the identifier should refuse to verify (fail closed).
- gen_ai.agent.capability
- string, the capability being invoked, in the producer's namespace, e.g. "database.read".
Trust signals (producer-emitted, each with a method token)
- gen_ai.agent.trust.score
- double 0.0-1.0, the trust score at decision time. Producer-defined semantics; not normative.
- gen_ai.agent.trust.method
- string, opaque producer-scoped token for the method and version that produced the trust score, e.g. "trust-model@2.3.1". Compared for equality only.
- gen_ai.agent.drift.score
- double 0.0-1.0, behavioral drift from baseline at decision time.
- gen_ai.agent.drift.method
- string, opaque producer-scoped token for the method and version that produced the drift score, e.g. "embedding-cosine@1.4".
- gen_ai.agent.scan.verdict
- string, latest scan verdict: clean | warnings | findings | critical | unknown.
- gen_ai.agent.scan.method
- string, opaque producer-scoped token for the scanner and version that produced the verdict, e.g. "scanner@1.2.0".
The authorization decision: fga.*, repository-defined
Three more attributes carry the decision path. They are defined in the repository's registry/fga.yaml, not in pull request #291: where they sit (alongside gen_ai.agent.*, under gen_ai.agent.fga.*, or elsewhere) is pending the working-group decision asked for in issue #180, so they are kept unscoped and deferred from the filing.
- fga.step
- string, which authorization step produced this span: capability_check | attribute_check | context_check | chain_check | intent_check.
- fga.outcome
- string, ALLOW | DENY | DENY_INTENT | DENY_CONTEXT | DENY_CHAIN | DENY_ATTRIBUTE | ERROR.
- fga.denied_by
- string, when the outcome is a DENY, which step denied it (one of the fga.step values).
groups:
- id: registry.fga
type: attribute_group
display_name: Fine-Grained Authorization Attributes
brief: >
Decision-path attributes for fine-grained authorization spans. The namespace placement of
these attributes (alongside gen_ai.agent.*, under gen_ai.agent.fga.*, or elsewhere) is
pending the working-group decision asked for in
open-telemetry/semantic-conventions-genai#180, so they are kept unscoped here and deferred
from #291.
attributes:
- id: fga.step
type: string
stability: experimental
brief: The fine-grained authorization step that produced this span.
examples:
- "capability_check"
- "attribute_check"
- "context_check"
- "chain_check"
- "intent_check"
- id: fga.outcome
type: string
stability: experimental
brief: The decision outcome.
examples:
- "ALLOW"
- "DENY"
- "DENY_INTENT"
- "DENY_CONTEXT"
- "DENY_CHAIN"
- "DENY_ATTRIBUTE"
- "ERROR"
- id: fga.denied_by
type: string
stability: experimental
brief: >
Which step denied, when fga.outcome starts with DENY. Matches one of
the fga.step enum values.
examples:
- "capability_check"
- "attribute_check"
- "intent_check"What it looks like in a trace
The attributes map directly onto AIM's 5-step authorization: one parent span, fga.authorize, with a child span per check. A denied request is instantly explainable, the trace shows exactly which step said no.
fga.authorize gen_ai.agent.id=agent-7f3a9c2e
│ gen_ai.agent.capability=database.read
│ gen_ai.agent.trust.score=0.62 gen_ai.agent.trust.method=trust-model@2.3.1
│ gen_ai.agent.scan.verdict=clean gen_ai.agent.scan.method=scanner@1.2.0
│ fga.outcome=DENY_CONTEXT fga.denied_by=context_check
├─ fga.capability_check fga.step=capability_check fga.outcome=ALLOW
├─ fga.attribute_check fga.step=attribute_check fga.outcome=ALLOW
├─ fga.context_check fga.step=context_check fga.outcome=DENY ← here
├─ fga.chain_check (skipped)
└─ fga.intent_check (skipped)What it enables
Without conventions, every team invents its own field names and no dashboard can correlate across vendors. With them, any OpenTelemetry backend, Tempo, Prometheus, Grafana, Loki, can chart denial rates, alert on risinggen_ai.agent.drift.score, and answer “why was this agent denied?” out of the box.If you know HTTP semantic conventions
How it applies to agents
- It turns authorization from a black box into a queryable, chartable signal across any OTel stack.
- The
fga.*attributes are the observability face of AAP's enforcement and AIM's 5-step engine. - Because it's proposed upstream, the goal is that agent telemetry becomes legible to every observability tool, not just OpenA2A's.
