Rule
min_detectors=1 · direction=up · consecutive=3mad{"threshold": 3.0, "window_size": 2016, "half_life": "1d"}Time-series anomaly detection and alerting with a dbt-like project layout. A metric is a SQL query plus a detector in YAML — run it with one command.
DuckDB runs in-process — zero servers, zero credentials. Three small
files, one command, and --report renders the caught anomaly and the alerts
it fires into a self-contained HTML file. Wire a channel (ntfy needs no
token) when you want the notification delivered live.
Runs on a fresh machine with nothing to provision — the full project is in examples/quickstart-duckdb. Swap the profile for ClickHouse, Postgres, MySQL or a hybrid warehouse when you're ready — the four commands don't change. DuckDB guide →
One command sets up Claude Code for your
project folder — a CLAUDE.md, a .claude/rules/detectkit/ reference,
and five skills: dtk-setup-project (configure your database),
dtk-new-metric (scaffold a metric), dtk-tune (dial in a detector
by hand in an interactive browser cockpit — with autotune built in), dtk-autotune
(search for the best detector automatically), and dtk-feedback (file a redacted
bug report or feature request upstream). Now an assistant writes metrics, tunes detectors,
wires up alerts, and reports issues with full knowledge of detectkit. Re-run it after an
upgrade to refresh the context.
dtk mcp is a strictly read-only Model Context Protocol
server over your _dtk_* state. Point Claude Code, Claude Desktop or any MCP
client at it, and your assistant answers questions from the real pipeline — no writes, no
dashboards, no data leaving your infra. MCP guide →
signups down · recoveredmad, 6 intervals; back in range by 13:00.checkout_error_rate up · still openautoreg flagged a broken daily shape — wrong for the curve, not just the level.daily_active_users no-data
A metric is just a query plus a detector in YAML. dtk run handles the
corridor, the quorum and the alert — nothing else to wire up.
The real output of dtk run — a load → detect → alert tree with cyan step
headers and colored status lines. Idempotent: it resumes from the last saved point.
Run the SQL on your warehouse, in batches, from the last checkpoint.
Each detector scores points against its learned corridor of normal.
Quorum met → post to chat with the rule up top, recovery on the way back.
Every detector learns a corridor of normal from recent history, then flags the moment a metric steps outside it. Switch the detector to see the kind of metric it's built for.
Measures the typical distance from the median. A handful of wild spikes barely move it — the most robust default.
Classic mean ± k standard deviations. Fast and simple, but one big outlier inflates the band — keep it for clean data.
Builds the corridor from the middle 50% of values, then extends fences 1.5×IQR out. Comfortable with skewed, long-tailed data.
Predicts each point from the few just before it, then flags what misses the forecast. Catches values that are normal in absolute terms but wrong for the shape of the curve — dips, stalls, broken rhythm.
No statistics at all — you set hard floor and ceiling values. Alerts the instant a metric crosses a known SLA line.
// the statistical corridors are recomputed per window with seasonality grouping &
recency weighting — newer points count more; autoreg refits its forecast per point
// each detector is shown on the metric shape it handles best — robust, bell-shaped,
skewed, pattern-driven or hard-bounded
// opt-in stabilization keeps a sustained incident from widening its own corridor and
masking its own tail
The playground is dtk tune — the same cockpit the tool ships —
running on a synthetic metric you shape in your browser. Turn the detector's real knobs,
watch the corridor of normal and what gets flagged, confirm alerts, mark
incidents, and read the live catch-rate / false-alert metrics — nothing is
sent anywhere.
dtk tune --select <metric> opens a browser cockpit on the metric's real
history. Turn a knob and the corridor recomputes live, with catch-rate and
false-alert metrics pinned over the chart. Four modes: Tune the
band, Review fired alerts (one click — valid or false alarm; a confirmed
alert becomes ground truth), Label incidents (drag a span, lasso the
anomaly cloud), and Autotune — the real search engine, run server-side on
exactly the window you're looking at. Apply validates the config and
writes it back into the metric YAML, archiving the previous version to
metrics/.history/.
// recompute is client-side — the same detector math as the pipeline; no lock, nothing
persists until Apply
// labels save to incidents/<metric>/ — the ground truth dtk autotune optimizes
against, so a labeling round here feeds the next search
dtk ui opens a localhost cockpit over the project: every metric's alert
frequency and freshness at a glance (quality chips once incidents are labeled), the full
HTML report one click away, run / autotune / tune driven as real
dtk subprocesses — and a built-in editor to create, edit and
delete metric YAMLs, validated before any write, previous versions archived to
metrics/.history/.
Direction-aware multi-detector quorum, cooldown, recovery and no-data alerts — posted to
chat with the alert and its rule up top, anomaly evidence below. A slow burn that never
strings N anomalies in a row? An opt-in window-share rule
(anomaly_window + min_anomaly_share) catches it without paging
on every blip.
The same alert, posted by detectkit to each channel — rendered as that channel formats it: one
folding attachment on Slack/Mattermost (its verbose tail collapses behind “Show more”), escaped
HTML on Telegram, a branded card in email, an inline field grid on Discord (embeds don't fold),
an Adaptive Card on Microsoft Teams (posted under the flow's own identity — no detectkit
branding), a Cards v2 card on Google Chat, and a push notification on ntfy. A generic
webhook (with an attachments payload Rocket.Chat accepts as-is, plus
json / alertmanager formats) covers everything else.
Each leads with the project name ([payments]) so several projects can share one
channel while keeping the brand bot identity. The dashboard_url below becomes a
first-class link on every channel.
min_detectors=1 · direction=up · consecutive=3
• Value: 4.2 · Expected: <= 1.1
• Quorum: 1/1 · above
• Severity: 3.40
• Anomaly began: 2026-06-19 11:39:00 (Europe/Moscow) · Latest reading: 2026-06-19 12:04:00 (Europe/Moscow)
• Detector: mad
• Parameters: {"threshold": 3.0, "window_size": 2016, "half_life": "1d"}
Open dashboard · How to read this alert
@oncall_engineermin_detectors=1 · direction=up · consecutive=3
• Value: 1.0 · Expected: <= 1.1
• Anomaly began: 2026-06-19 11:36:00 (Europe/Moscow) · Alert fired: 2026-06-19 11:46:00 (Europe/Moscow) · Recovered: 2026-06-19 12:36:00 (Europe/Moscow)
• Detector: mad
Open dashboard · How to read this alert
@oncall_engineermin_detectors=1 · direction=up · consecutive=3min_detectors=1 · direction=up · consecutive=3alerting: channels: [mattermost_ops] dashboard_url: https://grafana.ops/d/api-errors # one line → a link on every channel
Warehouses punish frequent small writes — Snowflake bills a 60-second minimum every time it
resumes; BigQuery a 10 MiB minimum per query. So detectkit runs a metric's
load SQL on your warehouse, then keeps every
_dtk_* table — detections, alert state, resume cursors — in a cheap local
database. You monitor the warehouse without paying warehouse prices for the bookkeeping.
Hybrid mode →
# metrics/revenue.yml — one line flips a metric into hybrid mode source_profile: snowflake_prod # load runs here; state stays on your --profile
Deliberately narrow: statistical anomaly detection and alerting for business metrics you define in SQL. Here's how that differs from the neighbors — by scope and deployment, the things that actually vary.
Compared on scope + deployment only — never maturity, support or star counts. The OSS corner of this space is thin: most alternatives are either heavy platforms or unmaintained.
SQL + YAML, one command. No agents, no dashboards to babysit.