Monitoring & alerting¶
observability.md covers the setup — the three signals
(Prometheus /metrics, structured logs, OTel spans) and how to wire them.
This page is the interpretation: what to alert on, the expression to
alert with, what a firing alert means, and what to do about it. It assumes
the /metrics endpoint is scraped (since 0.5.0) and the JSON request logs
are aggregated.
Counters on
/metricsare per-process and reset on restart — always wrap them inrate(), andsum()across instances in a multi-process deployment. The metric surface is the table in observability.md § Prometheus/metrics.
Alert catalogue¶
Each row is a page-worthy signal. PromQL is illustrative — tune the thresholds and windows to your traffic and SLOs.
Queue backlog / backpressure¶
# Backlog growing and not draining (tune 50 / 10m to your throughput)
kneo_runs_queued > 50 and deriv(kneo_runs_queued[10m]) > 0
Means: runs are arriving faster than the worker pool drains them, or
workers are wedged. Do: check kneo_worker_count is non-zero and
kneo_runs_running is moving; scale KNEO_SERV_WORKER_CONCURRENCY or add
instances; if a deployment runs with KNEO_SERV_MAX_QUEUE_DEPTH set,
sustained backlog at the cap means clients are getting 503 load-shed
(those runs are terminalized failed{queue_full}, not silently dropped) — alert
on rate(kneo_runs_rejected_total[5m]) > 0, the direct load-shed counter.
Worker starvation¶
Means: queued work with no live worker to claim it. Workers poll
persistently until shutdown (0.10.0), so a zero count with a backlog
points at a crashed pool or a process that never called start_worker.
Do: check the process is up and /readyz is green; restart drains the
queue (leases are reclaimed).
Failure & dead-letter rate¶
# Failures as a fraction of completions over 5m
rate(kneo_runs_failed_total[5m])
/ clamp_min(rate(kneo_runs_completed_total[5m]), 1) > 0.1
# Any dead-lettering is worth attention
rate(kneo_runs_dead_lettered_total[5m]) > 0
Means: a rising failed-ratio is a provider outage, a bad spec, or
store pressure; dead-lettering means a run exceeded
KNEO_SERV_QUEUE_MAX_ATTEMPTS re-claims (it repeatedly crashed its
worker). Do: read the failing runs' traces (GET /v1/runs/{id}/trace)
and the JSON logs for the exception; a dead-letter usually means a
poison run — fix the spec/tool, don't just raise the cap.
Latency regression¶
/metrics exports run counters and queue gauges, not request-latency
histograms — read p95 from the reverse-proxy access logs or the OTel
spans, or from the duration_ms field on the JSON request logs.
Means: provider slowness, queue wait, or DB pressure. Do:
correlate with kneo_runs_queued (queue wait) and provider latency in the
spans.
Token spend¶
Means: consumption above your cost envelope. Do: the per-run hard
ceiling is TokenBudgetMiddleware (a run that hits it ends
failed{token_budget_exceeded}); this alert catches aggregate spend
trending over budget across runs.
Readiness / dependency health¶
# From the proxy/LB health checks, not /metrics:
/readyz returns 503 for more than one probe interval
Means: a dependency probe (store, queue, configured provider secret)
is failing — /readyz reports per-check detail. Do: read the
checks block in the /readyz body; see
incident_response.md.
Signals that aren't on /metrics¶
Two state-growth signals matter operationally but are read from the API / store, not the scrape endpoint (see checkpoint_and_state_lifecycle.md):
- Continuation backlog —
GET /v1/human-tasks(or aCOUNTon the continuation store). A count that only grows means human tasks are neither answered nor expired. Give human tasks anon_timeoutand runprune_expired_human_tasks. - Checkpoint / store growth — track store size or a checkpoint
COUNT. Rising size with a flat run rate means the retention sweep (prune_retention) isn't running. Note the 0.10.0 liveness guard intentionally retains live (blocked/running) runs' checkpoints regardless of age, so a large blocked-run population is expected store.
Wiring¶
The service does not push its own alerts — it exposes signals. Point your
Prometheus/Alertmanager at /metrics, your log pipeline at the JSON
records, and your proxy's health checks at /readyz. Keep /metrics
unauthenticated but not publicly reachable (it carries operational
counts, no run content); bind it behind the reverse proxy per
tls_and_proxy.md.