Gateway Guardian Observability
Gateway Guardian provides two complementary observability layers:
- governed request records for audit, investigations, and replay-oriented troubleshooting
- runtime telemetry for traces, logs, and metrics
In the current R1 implementation, these capabilities are split across the platform:
- the gateway request path verifies requests, captures audit exchanges, records agent state CID history, emits OpenTelemetry traces, logs, and metrics, and exposes Prometheus metrics
- the control plane exposes operator APIs that let users and the console inspect audit exchanges and state history
At a glance
| Capability | Produced by | Stored or exported to | Primary access path | Primary use |
|---|---|---|---|---|
| Audit exchanges | llm-gateway | Gateway observability data store | control-plane API and console | Governance, forensics, request-level debugging |
| Agent state CID history | llm-gateway when X-Viper-State-CID is present | Gateway state history and blob storage | control-plane API and console | Reconstructing agent state at request time |
| OpenTelemetry traces, logs, and metrics | llm-gateway | OTLP-compatible backend via Collector or direct OTLP | OTLP-capable observability backend | Lifecycle tracing, diagnostics, plugin health |
| Prometheus metrics | llm-gateway | /metrics scrape endpoint | Prometheus and Grafana | SLIs, dashboards, and alerting |
1. Audit exchanges
The gateway attempts to create an append-only audit exchange for each handled request after verification and routing logic runs.
What an audit exchange captures
Each record can include:
correlation_id,received_at,completed_at, andduration_msroute_type:proxiedlocal_denyupstream_error
decision_codeanderror_message- request metadata:
- HTTP method, scheme, host, path, and query
- full request headers
- request body bytes, body encoding, and byte size
- response metadata:
- response status
- full response headers
- response body bytes, body encoding, and byte size
- verified identity metadata:
agent_idkey_id- signature
profile
- LLM routing metadata:
provideroperationrequest_modelresponse_modelstream_requestedstream_observedendpoint_supportedupstream_urlupstream_host
request_previewandrequest_preview_rolewhen a preview can be extracted from the request body
For chat-style requests, the preview logic walks backward through the messages array and extracts the last meaningful text segment it can find, truncating the preview to 150 characters. This is intended to provide quick operator context without requiring the full body to be opened first.
Durability and retention
Audit persistence is designed to be durable and retention-governed. In practice, that means:
- audit capture continues independently of the synchronous request path
- records are retained for a defined period and then aged out according to platform retention settings
- audit availability is intended for both operational troubleshooting and governance workflows
Operator access
Most user-facing audit access is provided by the control-plane service:
GET /v1/agents/{agentId}/audit-exchangesGET /v1/agents/{agentId}/audit-exchanges/timeseriesGET /v1/agents/{agentId}/audit-exchanges/{exchangeId}GET /v1/agents/{agentId}/audit-exchanges/{exchangeId}/request-bodyGET /v1/agents/{agentId}/audit-exchanges/{exchangeId}/response-body
These APIs support:
- paginated audit exchange listing with
limitandcursor - optional
startandendfilters using RFC3339 timestamps - timeseries buckets at
1m,5m,15m,1h,6h, or1d - full body retrieval for request and response payloads
The console UI uses the same control-plane APIs for agent activity views and exchange inspection.
Access control and redaction
In authenticated deployments, read access to agent audit data requires either:
- platform admin access, or
- an active binding to the target agent
When audit exchange detail is returned through the control-plane, sensitive header values are redacted for:
AuthorizationProxy-AuthorizationX-Api-KeyCookie
Body retrieval endpoints return the stored bytes as either UTF-8 text or base64. Those bodies should be treated as sensitive operational data.
2. Agent state CID history
Gateway Guardian also records agent state attestation references so operators can answer a different question: not just what request was sent, but what state the agent was in when it sent it.
How it works
Viper attaches state attestation metadata to outbound requests with headers such as:
X-Viper-State-CIDX-Viper-NonceX-Viper-TimestampX-Viper-App-Type
When verification succeeds and a state CID is present, the gateway stores:
- the current observed state CID for the agent
- a historical sequence of observed state CIDs
- the observation timestamp
- the correlation ID
- the app type captured at request time
The underlying attestation blobs are also made available to the platform so they can be retrieved later during an investigation or review.
This state data is separate from the audit exchange row itself, but the two are linked operationally through agent identity and correlation metadata.
What the state blob represents
The underlying StateAttestationData blob is a content-addressed snapshot of agent state at request time. It can include:
- host information
- Viper version and binary CID
- session details and git state
- app-specific metadata for supported agent apps
- derived configuration references such as skills CIDs
- derived request metadata such as provider, model, tools, and system prompt CID
This makes the state history useful for provenance, incident review, and change attribution.
Operator access
The control-plane exposes state history through:
GET /v1/agents/{agentId}/state-cidsGET /v1/agents/{agentId}/state-cids/{stateCID}/blob
The first endpoint returns the current snapshot plus recent history. The second resolves the underlying blob and returns it as UTF-8 or base64, depending on content.
3. OpenTelemetry integrations
OpenTelemetry in the current platform is implemented in the llm-gateway process. The control-plane serves stored observability data, but this R1 document refers to the gateway’s OTEL behavior across request lifecycle instrumentation and plugin runtime telemetry.
Signals and export model
The gateway initializes:
- tracing
- OTEL log emission
- OTEL metric emission
It supports:
- OTLP over
http/protobuf - OTLP over
httpas an alias forhttp/protobuf - OTLP over
grpc
http/protobuf is the default when no protocol is explicitly set.
The default service identity is:
service.name=guardian-llm-gateway
service.version is resolved from build information when available.
Common exporter configuration is provided through standard OpenTelemetry environment variables, including:
OTEL_TRACES_EXPORTEROTEL_LOGS_EXPORTEROTEL_METRICS_EXPORTEROTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_PROTOCOLOTEL_EXPORTER_OTLP_HEADERSOTEL_RESOURCE_ATTRIBUTES
Per-signal endpoint, protocol, and header overrides may also be used when traces, logs, and metrics need to route differently.
OTEL_RESOURCE_ATTRIBUTES can add backend-specific resource dimensions such as deployment environment. The gateway always controls service.name and service.version.
Runtime behavior
The gateway’s OTEL bootstrap is fail-open:
- if no exporter configuration is present for a signal, it uses a no-op provider for that signal
- if
OTEL_TRACES_EXPORTER=none,OTEL_LOGS_EXPORTER=none, orOTEL_METRICS_EXPORTER=none, it uses no-op providers for that signal - if exporter initialization fails, it degrades to no-op providers instead of failing the process
This means observability misconfiguration should not take the request path down.
At startup, the gateway writes process logs with OTEL bootstrap status and non-fatal warnings when exporters are missing, disabled, unsupported, or unable to initialize.
W3C propagation contract
Gateway trace propagation is backend-agnostic and uses the W3C model:
- inbound
traceparent,tracestate, andbaggageare accepted when valid - server spans continue valid inbound lineage
- malformed inbound trace context does not fail the request
- outbound proxy egress always injects gateway-owned W3C trace context
- stale outbound trace headers are removed before new context is injected
- malformed baggage is dropped fail-open while request and trace propagation continue
When configured, async request.end plugin work also carries W3C trace propagation so post-response plugin telemetry remains connected to the originating request trace.
Gateway lifecycle traces
The primary request instrumentation name is:
guardian.gateway.lifecycle
Each proxied request produces a request-centric trace:
gateway.request gateway.request.prepare gateway.plugin.invocation # request.start plugins, when matching plugins run gateway.verification gateway.proxy gateway.plugin.invocation # proxy.pre plugins, when matching plugins run gateway.upstream gateway.plugin.invocation # proxy.post plugins, when matching plugins runThe root gateway.request span represents the full gateway request. It includes viper.state_cid when the inbound request provides X-Viper-State-CID.
Child spans represent:
- request preparation and route/model extraction
- signature verification and authorization decisions
- plugin invocation
- proxying to the configured upstream provider
- upstream response handling
Lifecycle log events and attributes
The gateway emits structured OTEL lifecycle logs that mirror request progress. Every lifecycle log carries correlation_id and event.name. Logs emitted on the synchronous request path also carry event.sequence so backends can reconstruct the order of events for a request.
Stable lifecycle events include:
gateway.request.startgateway.request.prepare.startgateway.request.prepare.endgateway.verification.startgateway.verification.endgateway.proxy.startgateway.plugin.invocation.startgateway.plugin.invocation.endgateway.plugin.invocation.queuedgateway.upstream.startgateway.upstream.endgateway.proxy.endgateway.request.end
Error-oriented proxy log events are also emitted from guardian.gateway.proxy for exceptional conditions such as:
gateway.proxy.request.read_errorgateway.proxy.provider_route.errorgateway.proxy.policy.errorgateway.proxy.policy.shadow_mismatchgateway.proxy.upstream.errorgateway.proxy.agent_state.errorgateway.proxy.audit.encode.errorgateway.proxy.audit.enqueue.error
Common trace and log attributes include:
correlation_idevent.sequenceviper.state_cidagent.idsigner.key_idpolicy.decision_codegateway.route_typegateway.route_supportedllm.providerllm.operationllm.request_modelllm.response_modelllm.stream_requestedllm.stream_observedhttp.methodhttp.status_codegateway.duration_msgateway.uriupstream.uriverification.outcomeverification.decision_codeerror
Completion severities are outcome-aware:
INFOfor successful proxy responsesWARNfor local denials and other4xxoutcomesERRORfor upstream failures and5xxoutcomes
Plugin and hook observability
When gateway plugins are enabled, plugin work is represented in the same request lifecycle. Plugin invocations emit gateway.plugin.invocation spans and matching lifecycle log events.
Supported hook stages are:
request.startproxy.preproxy.postrequest.end
request.end hooks run asynchronously after the response path. Their queued work preserves trace propagation and continues lifecycle sequencing from the originating request. Worker invocations are emitted as gateway.plugin.invocation spans with hook.name=request.end.
Plugin observability attributes include:
hook.nameplugin.idplugin.modeplugin.runtimeplugin.order_indexplugin.outcomeplugin.decision_codeplugin.status_codeplugin.error_codeplugin.latency_mssnapshot.version
Plugin outcomes include:
allowdenydeny_ignorederror
Plugin denials are policy outcomes, not OTEL span errors. Plugin spans are marked as errors only for execution or context failures, such as plugin runtime errors, timeouts, or invalid context writes.
OTEL metrics
When gateway plugins are enabled, the gateway emits plugin runtime metrics through the guardian.llm-gateway.hooks instrumentation scope when OTEL metrics export is configured.
Plugin runtime metrics include:
| Metric | Type | Description |
|---|---|---|
guardian.plugin.request_end.enqueued_total | Counter | Async request.end work items enqueued |
guardian.plugin.request_end.executed_total | Counter | Async request.end work items executed |
guardian.plugin.request_end.retried_total | Counter | Async request.end work items retried |
guardian.plugin.request_end.dropped_total | Counter | Async request.end work items dropped |
guardian.plugin.request_end.dlq_total | Counter | Async request.end work items moved to the DLQ |
guardian.plugin.request_end.ack_failures_total | Counter | Async queue acknowledgement failures |
guardian.plugin.snapshot_load_failures_total | Counter | Plugin snapshot load failures |
guardian.plugin.snapshot_cache_fallbacks_total | Counter | Plugin snapshot cache fallback events |
guardian.plugin.invocations_total | Counter | Plugin invocations by hook, plugin, mode, result |
guardian.plugin.latency_ms | Histogram | Plugin invocation latency in milliseconds |
guardian.plugin.request_end.queue_records | Gauge | Current queued request.end records |
guardian.plugin.request_end.queue_bytes | Gauge | Current queued request.end bytes |
guardian.plugin.active_snapshot_version | Gauge | Active plugin snapshot version |
Invocation metrics are labeled by hook, plugin ID, plugin mode, and outcome.
Payload policy
Lifecycle OTEL logs do not capture request bodies, response bodies, or raw headers. Payload inspection remains in the audit subsystem and plugin previews, where retention and redaction are controlled separately.
4. Prometheus metrics
Prometheus scraping is separate from OTEL export. The gateway exposes a dedicated metrics endpoint on a separate listener.
Current gateway-specific metrics are:
| Metric | Type | Labels | Description |
|---|---|---|---|
gateway_http_requests_total | Counter | agent_id, route | Total inbound gateway requests |
gateway_http_request_errors_total | Counter | status_code, agent_id | Total 4xx and 5xx gateway responses |
Standard Go runtime and process collectors are also registered.
OTLP metric export and Prometheus scraping are independent. Operators may bridge them in their observability backend, but the gateway exposes them through separate runtime paths.
Metrics labeling behavior
To keep label cardinality stable:
- route labels use templates rather than raw request paths
- the metrics endpoint does not count itself
agent_idis populated after successful identity verification- requests without a resolved agent are labeled as
unknown
Examples of route labels include:
/health/v1/agents/registrations/v1/blobs/{cid}/v1/agents/{agentId}/state-cids/v1/anthropic_api/*/v1/openai_api/*/v1/chatgpt_backend/*
5. Recommended usage model
The observability stack is designed to be used in layers:
- use audit exchanges when exact request and response evidence matters
- use state CID history when operator review needs to include agent runtime state, not just payloads
- use OTEL traces and logs for live debugging, request lifecycle reconstruction, correlation, and backend-specific search
- use OTEL metrics for plugin runtime health, async queue visibility, and plugin latency
- use Prometheus metrics for dashboards, rates, error ratios, and alerting
In practice:
- audit is the governed source of truth for what the gateway handled
- OTEL is the live transport for distributed telemetry
- Prometheus is the lowest-friction source for gateway request health metrics
Current scope notes
- OTEL in R1 covers traces, logs, and plugin runtime metrics from
llm-gateway - user-facing audit and state inspection flows are primarily surfaced through
control-planeand the console - audit bodies and state blobs should be handled as sensitive data and governed with the same care as agent traffic itself