Metrics
The control plane serves GET /metrics on the same listener as the REST API and dashboard. When an API token or RBAC is configured, /metrics needs the token too, so give the scraper a bearer credential. The job_name must contain runeward for the RunewardTargetDown alert to match:
scrape_configs:
- job_name: runeward
metrics_path: /metrics
static_configs:
- targets: ["localhost:8080"]
| Metric | Type | Labels | Meaning |
|---|---|---|---|
runeward_actions_total |
counter | tool, verdict |
Governed actions by tool and verdict |
runeward_actions_duration_seconds |
histogram | tool |
Duration of executed governed actions |
runeward_sandboxes_created_total |
counter | none | Sandboxes created since start |
runeward_usage_tokens_total |
counter | profile |
Model tokens reported via the usage API |
runeward_usage_cost_usd_total |
counter | profile |
Spend (USD) reported via the usage API |
runeward_build_info |
gauge | version |
Always 1; carries the version label |
verdictisallow,deny,require-approvalorerror.- A label series appears after its first event. A fresh process shows only
runeward_build_infoandruneward_sandboxes_created_total. - The histogram also exposes
_bucket(withle),_sumand_countseries.
Grafana dashboard
Import deploy/grafana/runeward-dashboard.json via Dashboards → New → Import, and pick your Prometheus datasource for the DS_PROMETHEUS input. Panels: deny ratio, require-approval rate, action rate by verdict, top tools, p95 duration per tool, sandbox creation and build info.
Alerts
Add deploy/prometheus/runeward-alerts.yaml to prometheus.yml, then validate and reload:
rule_files:
- /etc/prometheus/rules/runeward-alerts.yaml
promtool check rules deploy/prometheus/runeward-alerts.yaml
curl -X POST http://localhost:9090/-/reload
| Alert | Severity | Fires when | Indicates |
|---|---|---|---|
RunewardDenySpike |
warning | deny ratio > 50% for 10m | Bad actor or too-strict policy |
RunewardHighActionLatency |
warning | p95 duration for a tool > 30s for 10m | Backend, sandbox or downstream degraded |
RunewardNoActivity |
info | action rate == 0 for 30m | Idle, or agents cannot reach the control plane |
RunewardTargetDown |
critical | up for a .*runeward.* job == 0 for 5m |
Control plane down or unreachable |
Structured logs
Both services log to stderr via Go's log/slog.
| Variable | Values | Default |
|---|---|---|
RUNEWARD_LOG_FORMAT |
text, json |
text |
RUNEWARD_LOG_LEVEL |
debug, info, warn, error |
info |
RUNEWARD_LOG_FORMAT=json RUNEWARD_LOG_LEVEL=info runeward --config-dir examples serve
{"time":"2026-07-04T11:28:37Z","level":"INFO","msg":"request","method":"POST","path":"/v1/citadels","status":200,"duration_ms":142}
/metrics and /healthz are excluded from the access log.
Telemetry
Telemetry is off by default. It runs only when both variables are set, pointing at a collector you control:
export RUNEWARD_TELEMETRY=1
export RUNEWARD_TELEMETRY_ENDPOINT=https://your-collector.example/ingest
- A truthy
DO_NOT_TRACK(convention) always keeps it off. - Each service start sends one JSON event with
version,os,archand non-identifying properties, such as whether the dashboard is enabled. No hostnames, paths, profile contents, IDs or device identifier. - Sends are best-effort with a 2-second timeout and never block or fail the process.
- Every
serveandcontrollerstartup logs the telemetry state.
Written from docs/observability.md at commit 1ee29ff4ab36. Rune Box is open source under Apache-2.0.