Observability

How to run Hippo’s optional OpenTelemetry stack, which dashboards to open, and which provisioned alerts fire when capture paths degrade. Companion to otel/README.md (stack setup and commands) and capture/operator-runbook.md (first-aid when alarms fire).

Telemetry is off by default. Nothing is emitted until you build with OTel support, enable [telemetry] in config, and start the Docker stack.

Quick start

mise run otel:up          # Grafana + Prometheus + collector on localhost
mise run build:otel       # daemon with OTel feature
hippo config edit         # [telemetry] enabled = true
export HIPPO_OTEL_ENABLED=1   # brain + MCP
mise run restart
open http://localhost:3030

Default Grafana login: admin / hippo (anonymous Admin is also enabled for local use).

Architecture

hippo-daemon ──┐
               ├── OTLP ──→ OTel Collector ──→ Tempo (traces)
hippo-brain  ──┤                            ──→ Loki (logs)
hippo-mcp   ──┘                            ──→ Prometheus (metrics)
                                               Grafana (dashboards + alerts)
ServicePortPurpose
Grafana3030Dashboards, Explore, provisioned alert rules
Prometheus9090Metrics storage (30d / 10GB default retention)
OTel Collector4317 (gRPC), 4318 (HTTP)OTLP ingest
Tempo3200Trace storage
Loki3100Log aggregation

Persistent data: ~/.local/share/hippo/otel/. Stack restarts do not wipe state.

Dashboards

All dashboards provision automatically from otel/grafana/dashboards/ into the Hippo folder. No manual import.

DashboardUIDURLWhat it shows
Hippo Overviewhippo-overviewhttp://localhost:3030/d/hippo-overview ↗Health grade, capture lag, probe success/lag, invariant violations, alarm firings, daemon drops
Hippo Daemonhippo-daemonhttp://localhost:3030/d/hippo-daemon ↗Event ingest/drop rates, flush latency, redactions, fallback writes, watcher throughput
Hippo Enrichmenthippo-enrichmenthttp://localhost:3030/d/hippo-enrichment ↗Brain queue depth, LLM latency, enrichment throughput, MCP tool metrics
Hippo Processeshippo-processeshttp://localhost:3030/d/hippo-processes ↗process.* CPU/memory for daemon and brain
Hippo — Knowledge Healthhippo-knowledge-healthhttp://localhost:3030/d/hippo-knowledge-health ↗Recall probe (golden-question /ask round-trips), capture alarms/staleness, corpus size, project graveyard and dead-project contamination, identity fragmentation, redaction canary. Fed by the knowledge-health exporter, not OTel.

Metric names in PromQL use Prometheus exporter suffixes (_total, _milliseconds, etc.). The canonical allow-list and drift tests live in brain/tests/test_otel_dashboards.py.

Dashboards draw on two metric sources: OTel instruments in the daemon and brain (hippo_daemon_*, hippo_brain_*), and the knowledge-health exporter (hippo_kb_*, see below). Both are covered by the same drift tests.

_total is reserved for cumulative counters. A point-in-time reading is a gauge with a bare name — hippo_kb_events, not hippo_kb_events_total — because increase()/rate() over a non-monotonic _total series is silently always zero.

Provisioned alert rules

Alert rules provision from every file in otel/grafana/alerting/ on stack start — hippo-capture-alerts.yml (capture reliability) and hippo-knowledge-alerts.yml (knowledge health). They appear under Alerting → Alert rules in the Hippo folder.

AlertFires whenforSeverity
Daemon events droppedrate(hippo_daemon_events_dropped_total[5m]) > 05mwarning
FS watcher events droppedrate(hippo_watcher_events_dropped_total[5m]) > 05mwarning
Watchdog not runningrate(hippo_watchdog_run_total[5m]) < 0.0015mcritical
Probe failure rate highok=false probe runs > 10% over 15m15mwarning
Capture invariant violationrate(hippo_watchdog_invariant_violation_total[15m]) > 015mcritical

Knowledge health (hippo-knowledge-alerts.yml)

AlertFires whenforSeverity
Recall path downmin_over_time(hippo_kb_recall_up[5m]) < 1 while the exporter scrape target is up5mcritical
Exporter not scrapingup{job="hippo-knowledge-health"} < 1 for 10m10mwarning
Recall path degradedavg_over_time(hippo_kb_recall_latency_milliseconds[15m]) > 15000 (successful probes only)15mwarning
Recall probe failure burstsum(increase(hippo_kb_recall_failures_total[1h])) > 35mwarning
Capture alarm backloghippo_kb_capture_alarms_active above threshold4hwarning
Capture stalemax(hippo_kb_capture_source_last_event_age_milliseconds) above threshold10mwarning
Graveyard contaminationhippo_kb_dead_project_node_ratio above threshold1hwarning
Stranded-hours jumpdelta(hippo_kb_stranded_hours{window="30d"}[7d]) above threshold1hinfo
Secretish env keys presenthippo_kb_env_secretish_keys > 05mwarning
Canary leakmax(hippo_kb_canary_found) > 00mcritical
Collector errorssum(increase(hippo_kb_collector_errors_total[15m])) > 05mwarning

Two further rules (hippo_kb_epitaph_unconfirmed, hippo_kb_push_useful_floor) ship isPaused: true — they target snowball metrics whose backing tables do not exist yet. Unpause them when the feature lands.

All rules use noDataState: OK. Dead-exporter detection moved out of hippo_kb_recall_down (which previously used noDataState: Alerting for that) into the dedicated hippo_kb_exporter_down rule on up{job="hippo-knowledge-health"} — that keeps the OTel-stack-without-exporter deployment ([telemetry] enabled = false) from paging critical, while still surfacing the dead-exporter case as a warning.

All capture rules use noDataState: OK so a stack with telemetry disabled does not page. When OTel is enabled but a rule has no series, treat that as “instrument not emitting” rather than healthy silence.

Notification routing: this repo provisions rules only. Wire contact points and notification policies in Grafana UI (or add provisioning YAML) when you want Slack/PagerDuty delivery.

Knowledge-health exporter

scripts/hippo-metrics-exporter.py is a stdlib-only Python bridge that reads hippo.db read-only (mode=ro + PRAGMA query_only=ON; it can never write or lock capture) and serves Prometheus text on 127.0.0.1:9835. It covers what the OTel capture instruments cannot: what is in the knowledge base and whether it can still be recalled.

Endpoints: /metrics (Prometheus), /metrics.json (same samples as JSON), /healthz.

Enabling telemetry

Daemon (Rust)

# ~/.config/hippo/config.toml
[telemetry]
enabled = true
endpoint = "http://localhost:4317"

Build: mise run build:otel or cargo build --features otel.

Brain / MCP (Python)

export HIPPO_OTEL_ENABLED=1
# optional: export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Commands

mise run otel:up       # start stack
mise run otel:down     # stop stack
mise run otel:status   # container health
mise run otel:logs     # tail compose logs
mise run otel:backup   # snapshot persisted data

See otel/README.md for retention overrides, reset workflow, and process-metric details.

When alerts fire

  1. hippo doctor --explain — isolated checks with CAUSE/FIX per failure
  2. hippo alarms list — unacknowledged capture alarms from SQLite ground truth
  3. capture/operator-runbook.md — version mismatch, enrichment wedge, probe failures

SQLite source_health remains the correctness source during OTel outages; dashboards and alerts are time-series views on the same invariants.