Observability¶
ag-server exports metrics (Prometheus), traces (OpenTelemetry, OTLP/HTTP) and structured logs.
The files for all three are in deploy/observability/; the Compose stack
wires them up under --profile observability.
Metrics¶
GET /metrics (Prometheus text format), served on the API port and protected by a bearer token when
AG_METRICS_TOKEN is set (always set it in production). Disable with [metrics] enabled = false.
| Metric | Type | Labels | Meaning |
|---|---|---|---|
agwp_requests_total |
counter | transport, operation, code |
requests by result. code is ok or the AGWP error code |
agwp_request_duration_seconds |
histogram | transport, operation |
latency, 1 ms to 60 s |
agwp_inflight_requests |
gauge | requests being handled | |
agwp_auth_failures_total |
counter | reason |
failed authentications (missing, invalid, throttled: the client address failed too often, see limits.auth_failures_per_second; and run_ended: a credential bound to a run that is no longer running, see ADR-0010) |
agwp_rate_limited_total |
counter | requests rejected by the rate limiter | |
agwp_shed_total |
counter | requests rejected because the server was saturated | |
agwp_integrations_total |
counter | outcome |
integrated, blocked, conflict, denied, invalid |
agwp_object_bytes_received_total, agwp_object_bytes_sent_total |
counter | object traffic | |
agwp_inflight_object_bytes |
gauge | object bytes held in memory by uploads in progress, against limits.max_inflight_object_bytes |
|
agwp_object_budget_refused_total |
counter | uploads refused (unavailable, with a retry hint) because that budget was used up |
|
agwp_derive_pending_events, agwp_derive_failing_repositories |
gauge | outbox events still pending after commits, and repositories whose last post-commit derive pass was incomplete (nothing is lost: the data is committed, derived data is behind; the next write retries, ag reindex rebuilds). Refreshed by the health scan |
|
agwp_dangling_refs |
gauge | kind |
pins (dangling_pin), open proposals (dangling_proposal_head) and workspaces (dangling_workspace_ref) that point at a changeset the object store cannot produce. Garbage collection refuses to run while one exists; ag verify lists them. Refreshed by the health scan |
agwp_health_scan_unreadable_repositories, agwp_health_scan_timestamp_seconds |
gauge | repositories the last scan could not open, and when it finished (0: none yet) | |
agwp_open_repositories, agwp_event_subscriptions |
gauge | ||
agwp_job_events_total |
counter | event |
execution plane since start: enqueued, claimed, succeeded, retried, failed (retried and failed include the jobs taken back when a worker died holding them: a runner that crashes on its last attempt shows up here, not only a runner that reports the failure) |
agwp_jobs_queued |
gauge | queued validation jobs in repositories known to have work (counted, not listed, at scrape time; a repository that cannot be read at that moment is logged and left out of the sum) | |
agwp_tree_cache_bytes / agwp_tree_cache_entries |
gauge | decoded trees held in memory (estimated bytes, against repos.tree_cache_bytes; entries) |
|
agwp_tree_cache_hits_total / agwp_tree_cache_misses_total / agwp_tree_cache_evictions_total |
counter | tree lookups answered from memory, not found there, and trees dropped to stay inside the budget | |
agwp_agent_runs_reaped_total |
counter | agent runs ended as abandoned by a reap (an operator's ReapRuns, ag-server admin reap-runs, or the periodic reaper of [agents] reap_interval_secs) |
|
agwp_trace_spans_exported_total, agwp_trace_spans_dropped_total, agwp_trace_export_failures_total |
counter | trace exporter health | |
agwp_uptime_seconds |
gauge |
Label vectors only appear once they have been used (a fresh server has no agwp_integrations_total series).
Security-relevant detail: operation names and counts are visible to whoever can read /metrics, which is why
it takes a token.
Alerts (alerts.yml): server down, server-side error ratio,
p99 latency, load shedding, rate limiting, authentication-failure spikes, job backlog, permanently failing
jobs, trace export failing. Thresholds are starting points. For the Prometheus Operator the same rules are
generated into a PrometheusRule (scripts/gen_prometheusrule.py).
Dashboard: grafana/dashboards/agentgit-overview.json (request rate by result, error ratio, latency
percentiles and slowest operations, saturation, authentication failures, integrations by outcome, jobs,
object traffic, trace export). It is provisioned automatically in the Compose stack and can be imported
anywhere (datasource uid agentgit-prometheus).
Traces¶
Set the collector and the server records one SERVER span per handled operation, named after the
operation (proposals.create, integration.integrate, …):
[telemetry]
otlp_endpoint = "http://otel-collector:4318" # OTLP/HTTP, JSON encoding; empty = off
service_name = "agentgit-server"
otlp_sample_ratio = 1.0 # fraction of NEW traces recorded
otlp_headers_env = "OTEL_EXPORTER_OTLP_HEADERS" # name=value,name2=value2 — a secret, never logged
- Propagation. An incoming W3C
traceparentis continued: same trace id, the caller's span as parent. If the caller'ssampledflag is off, nothing is recorded. Without atraceparenta new trace starts andotlp_sample_ratioapplies. - Logs. Every log line written while a request is served carries, in its
request{...}span,request_id,transport,remote(the peer address of the connection - the proxy's, behind one) andtrace_id(the exported trace's id, or the caller's own if nothing is exported for it, including asampled=0trace), so a log line can be found from a trace and the other way round. A failed authentication logs theremotetoo. - Attributes.
agwp.operation,agwp.transport,agwp.result(okor the error code),agwp.request_idand, if the client set one,agwp.deadline_ms. Credentials, request bodies and identities are never recorded. A span is marked as an error only for server-side faults (internal,unavailable,deadline_exceeded,unimplemented), not for a caller'snot_foundorpermission_denied. - Never in the way. Spans go through a bounded queue (4096) to one background thread that batches them
(256 spans or 2 s) and POSTs
…/v1/traces. If the queue is full or the collector is down, spans are dropped and counted; requests are unaffected. The queue is flushed on graceful shutdown (SIGTERM). - Limits. The exporter is hand-written and small: JSON over HTTP only (no gRPC, no protobuf, no retries, no compression, no metrics or logs signals), and spans cover the request as the server sees it — there are no child spans for storage or engine calls yet. Use a collector in front for fan-out, retries and authentication to hosted backends. It is tested against a fake collector, not against a specific backend (Tempo, Jaeger, a vendor).
ag-server's own log lines carry the same request_id as the span and the X-Request-Id response header,
which is the quickest way from a trace to a log line and back.
Logs¶
JSON lines on stdout with log_format = "json" (the container configs set it), text otherwise;
log_filter is a tracing directive (info,ag_server=debug), or set RUST_LOG. Failed requests are logged at
debug with the operation, error code and request id; the audit log (ag audit, GET …/events) is the
durable record of what changed and who changed it.
Health¶
| Endpoint | Meaning | Use for |
|---|---|---|
GET /healthz |
the process is up | liveness |
GET /readyz |
the control database answers | readiness / start-up |
ag-server healthcheck [--url …] |
the same probe as an exit code | container HEALTHCHECK (distroless has no curl) |