Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ surface is governed by [`COMPATIBILITY.md`](COMPATIBILITY.md).
blocks and approval denials are now visible in whatever OTLP backend you already
run — a refused call is marked with an error span status so it stands out. See
[`api/v1/otel-genai.md`](api/v1/otel-genai.md) and [`examples/otel`](examples/otel).
- **A ready-made cost & governance dashboard** ([`examples/otel/grafana`](examples/otel/grafana)) —
a provisioned Grafana + Tempo stack that turns RiskKernel's spans into panels:
spend over time and per run, token burn, budget halts by reason, tool-call outcomes,
and p95 latency by model. Built from the spans you already emit (Tempo TraceQL
metrics — no extra instrumentation); `docker compose up`, no import step.

## [0.3.0] - 2026-06-06

Expand Down
6 changes: 5 additions & 1 deletion examples/otel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ backend prefers OTLP/HTTP.

## Building cost/usage dashboards

Because cost and budget live on every span as first-class attributes, you can build
**Want it ready-made?** [`grafana/`](grafana/) ships a provisioned Grafana + Tempo
stack with a RiskKernel dashboard (spend, halts, tool outcomes, latency) — one
`docker compose up`, no import step.

Because cost and budget live on every span as first-class attributes, you can also build
panels directly from spans (e.g. in Grafana over Tempo, or SigNoz):

- **Spend per run** — sum `riskkernel.cost.usd` grouped by `riskkernel.run.id`.
Expand Down
64 changes: 64 additions & 0 deletions examples/otel/grafana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Cost & governance dashboard (Grafana + Tempo)

A ready-made Grafana dashboard for your governed runs — **spend, token burn,
budget halts, tool-call outcomes, and latency by model** — built entirely from the
OpenTelemetry spans RiskKernel already emits. No extra instrumentation, no metrics
pipeline: [Grafana Tempo](https://grafana.com/oss/tempo/) aggregates the spans with
TraceQL metrics, and the dashboard is provisioned so it shows up on first load.

> The [Jaeger quick-look](../docker-compose.yaml) one directory up is for reading
> individual traces. This is the aggregate view — the panels a platform team watches.

![panels: total spend, output tokens, model calls, tool calls refused, spend over
time, spend by run, p95 latency by model, model-call rate, tool calls by outcome,
budget halts by reason]

## Run it

```bash
# 1. Bring up Tempo (OTLP in) + Grafana (dashboard provisioned)
docker compose -f examples/otel/grafana/docker-compose.yaml up -d

# 2. Point RiskKernel at Tempo and start it (your keys, as always)
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export ANTHROPIC_API_KEY=sk-ant-...
riskkernel serve

# 3. Drive some governed traffic — point your app's base URL at the proxy:
export OPENAI_BASE_URL=http://localhost:7070/v1
# (or run any example under ../../ — the loop-killer, the MCP demo, etc.)
```

Open **http://localhost:3000** → **Dashboards** → **RiskKernel — agent runs**.
Spend, halts, and tool outcomes fill in as runs execute (default range: last 1h).

> **No telemetry by default.** RiskKernel exports nothing unless
> `OTEL_EXPORTER_OTLP_ENDPOINT` is set; spans go only to the Tempo you run here. See
> [`SECURITY.md`](../../../SECURITY.md). The Grafana in this stack is login-free for
> convenience — don't expose it as-is.

## What each panel is

| Panel | TraceQL metric |
|---|---|
| Total spend / Output tokens / Model calls | `sum_over_time(span.riskkernel.cost.usd)`, `sum_over_time(span.gen_ai.usage.output_tokens)`, `count_over_time()` |
| **Tool calls refused** | `count_over_time()` over `execute_tool` spans where `riskkernel.tool.status != "approved"` |
| Spend over time / Spend by run | `sum_over_time(span.riskkernel.cost.usd)`, grouped `by (span.riskkernel.run.id)` |
| p95 latency by model | `quantile_over_time(duration, .95) by (span.gen_ai.request.model)` |
| Model-call rate by model | `rate() by (span.gen_ai.request.model)` |
| Tool calls by outcome | `count_over_time() by (span.riskkernel.tool.status)` (approved / blocked / denied / timeout) |
| Budget halts by reason | `count_over_time() by (span.riskkernel.halt.reason)` |

Every attribute these queries touch is pinned in
[`api/v1/otel-genai.md`](../../../api/v1/otel-genai.md).

## Using it against your own Grafana

The dashboard is a plain Grafana JSON
([`dashboards/riskkernel-agent-runs.json`](dashboards/riskkernel-agent-runs.json)).
To use your existing stack instead of this compose: import the JSON and point its
**Tempo** data source at any Tempo with
[TraceQL metrics enabled](https://grafana.com/docs/tempo/latest/metrics-from-traces/)
(the `local-blocks` processor — see [`tempo.yaml`](tempo.yaml)). Needs Grafana
10.4+ and Tempo 2.7+ (the dashboard uses `sum_over_time`).
Loading
Loading