Anomaly Detection
Anomaly detection changes when a subscription notifies. Instead of its notification trigger, Beacon compares each run’s row count with the row counts of the subscription’s earlier runs and sends the notification only when the run is unusual.
It is statistical (standard deviation, interquartile range or percentage change), part of Beacon.Core, and needs no
LLM.
Turn it on
Section titled “Turn it on”Anomaly detection is set when you create the subscription; a subscription cannot be edited afterwards.
- In Subscriptions (
/beacon/subscriptions), click New subscription. - On the Anomaly step, tick Enable anomaly detection.
- Set the fields below and finish the subscription.
| Field | Default | Dialog accepts | What it does |
|---|---|---|---|
| Detection method | Standard deviation | Standard deviation, IQR, Percentage change | How a run is compared with the baseline. See Methods. |
| Sensitivity | Medium | Low, Medium, High | The threshold of the method. High alerts more often. IQR ignores it. |
| Lookback (days) | 30 | 7–365 | Age of the baseline runs: only runs from the last N days count. |
| Minimum data points | 7 | 3–100 | Baseline runs needed before any run can be an anomaly. |
| Alert on an unusual increase | on | Alert when the row count is unusually high. | |
| Alert on an unusual decrease | on | Alert when the row count is unusually low. The dialog requires at least one of the two. |
The ranges and the either/or rule are the dialog’s. Through the API, the same settings go in the anomalyConfig
object of POST /beacon/api/subscriptions (detectionMethod, sensitivity, lookbackDays, minimumDataPoints,
alertOnIncrease, alertOnDecrease), and the API stores the values it is given.
How a run is evaluated
Section titled “How a run is evaluated”For a subscription with anomaly detection on, each run that executes its query goes through these steps:
- Evaluate. Beacon loads the stored baseline values of the subscription from the last Lookback days. With fewer than Minimum data points values, the run is not an anomaly (“Not enough historical data … Building baseline…”). Otherwise the configured method decides.
- Decide the notification. The usual checks come first: a run that timed out is
Timeout, a run with 0 rows isNoResults, and a run below the subscription’s minimum row count isBelowThreshold. After those, an anomaly sends the notification (NotificationSent) and any other run is silenced (NotificationSilenced). The subscription’s notification trigger is not used while anomaly detection is on. A run whose query returns no rows ends asNoResults; anomaly detection compares runs that return rows. The Control Tower showsNoResultsruns. - Store the baseline. The run’s row count is stored as a new baseline value, with the time of the run. A run is not stored when all of its recipients wait for approval of their destination, so nobody could be told about it.
- Record the event. For an anomaly, Beacon records an anomaly event (current value, baseline mean, standard deviation,
z-score, severity, explanation), linked to the first notification that was delivered, if any. Recipients receive the
evaluation with the notification; the webhook payload carries it as
Anomaly.
Baselines are stored only while anomaly detection is on for the subscription. Runs that did not execute the query (skipped because the owner may no longer run it, or because the query’s change is not approved) store nothing.
Tasks are independent of anomaly detection: they follow the Create a task option of the subscription and open on any run with rows.
Methods
Section titled “Methods”All three methods use the baseline values of the lookback window, oldest to newest, without the current run.
Standard deviation
Section titled “Standard deviation”Beacon computes the mean (μ) and the population standard deviation (σ) of the baseline, then the z-score of the run:
z = (x − μ) / σ. The run is an anomaly when |z| is above the sensitivity’s threshold and its direction (above or
below the mean) has alerting turned on.
| Sensitivity | Threshold |
|---|---|
| High | 1.5σ |
| Medium | 2σ |
| Low | 3σ |
Severity by |z|: Critical from 4, High from 3, Medium from 2, otherwise Low. When every baseline value is the same
(σ = 0), the z-score is 0 and no run is an anomaly.
Baseline: [10, 12, 11, 13, 10, 12, 11] mean 11.29, σ 1.03Run: 18 rows z = (18 − 11.29) / 1.03 = 6.5Medium: 6.5 > 2 → anomaly, severity CriticalSuits steady data.
IQR (interquartile range)
Section titled “IQR (interquartile range)”Beacon sorts the baseline and takes Q1 and Q3 (the values at 25% and 75% of the sorted list), then
IQR = Q3 − Q1. The run is an anomaly when it is below Q1 − 1.5 × IQR (with decrease alerts on) or above
Q3 + 1.5 × IQR (with increase alerts on). The multiplier is fixed at 1.5; Sensitivity does not change it.
Severity by how many IQRs the run lies beyond the bound: Critical from 3, High from 2, Medium from 1, otherwise Low.
Baseline (sorted): [8, 10, 11, 12, 13, 14, 16, 20] Q1 11, Q3 16, IQR 5Bounds: 11 − 7.5 = 3.5 to 16 + 7.5 = 23.5Run: 35 rows → above 23.5 → anomalySuits history with occasional spikes.
Percentage change
Section titled “Percentage change”Beacon compares the run with the mean of the baseline (not with the previous run):
change = |x − mean| / mean. The run is an anomaly when the change is above the sensitivity’s threshold and its
direction has alerting turned on.
| Sensitivity | Threshold |
|---|---|
| High | 15% |
| Medium | 25% |
| Low | 40% |
Severity by change: Critical from 100%, High from 50%, Medium from 25%, otherwise Low. When the baseline mean is 0, any run with rows is an anomaly (severity High).
Baseline mean: 100Run: 140 rows → change 40%Medium: 40% > 25% → anomaly, severity MediumSuits business volumes where a relative change is what matters.
Choosing a method
Section titled “Choosing a method”| Data | Method |
|---|---|
| Steady counts with little variation | Standard deviation |
| Counts with occasional extreme values | IQR |
| Volumes where a relative change matters (orders, sign-ups) | Percentage change |
Tuning
Section titled “Tuning”| Problem | What to change |
|---|---|
| Alerts on normal variation | Lower the sensitivity (Low), try IQR, or lengthen the lookback. |
| Real changes go unnoticed | Raise the sensitivity (High), or use Percentage change for sudden jumps. |
| Detection never starts | The subscription has fewer baseline runs in the lookback window than Minimum data points. Wait for more runs, or create the subscription again with a lower minimum or a longer lookback. |
| Only increases (or only decreases) matter | Turn off the other direction. |
The lookback window counts days, not runs. A subscription that runs hourly with a 7-day lookback has up to 168 baseline values; one that runs daily has 7. Set Minimum data points to what the schedule can deliver within the lookback.
Where you see it
Section titled “Where you see it”- Subscription page, Anomaly detection tab (shown while detection is on). The method, sensitivity, lookback, minimum data points and alert directions; a chart of the last 30 days of row counts with the baseline and the lower and upper thresholds; and the recent detection events.
- Control Tower. An Anomaly badge on subscriptions with detection on, a per-day sparkline of anomaly events and the anomaly count, and the recent anomalies in the row detail. See Control Tower.
The chart route is GET /beacon/api/subscriptions/{id}/anomaly-chart?days=30. It needs read access to the subscription,
and its run points cover only runs the caller may read.
Data model
Section titled “Data model”Three entities in the beacon schema hold anomaly data (PostgreSQL uses snake_case names):
| Entity | Holds |
|---|---|
AnomalyConfig | One per subscription: Enabled, DetectionMethod, Sensitivity, LookbackDays, AlertOnIncrease, AlertOnDecrease, MinimumDataPoints. |
AnomalyBaseline | One per stored run: SubscriptionId, ExecutionTime, MetricValue (the row count). |
AnomalyEvent | One per detected anomaly: SubscriptionId, NotificationId, DetectedTime, CurrentValue, BaselineMean, BaselineStdDev, ZScore, Severity, Explanation, Acknowledged, AcknowledgedBy. |
Service API
Section titled “Service API”IAnomalyDetectionService (in Beacon.Core.Services) is what the subscription run uses. A host can call it in-process:
| Method | What it does |
|---|---|
EvaluateAnomalyAsync(subscriptionId, rowCount) | Evaluates a row count against the stored baseline. |
StoreBaselineAsync(subscriptionId, metricValue, executionTime) | Stores a baseline value. |
GetHistoricalMetricsAsync(subscriptionId, lookbackDays) | Baseline values of the window, oldest first. |
RecordAnomalyEventAsync(subscriptionId, evaluation, notificationId) | Records an anomaly event. |
GetAnomalyConfigAsync(subscriptionId) | The configuration as AnomalyConfigData, or null. |
SaveAnomalyConfigAsync(AnomalyConfigData) | Creates or updates the configuration of a subscription. |
DeleteAnomalyConfigAsync(subscriptionId) | Removes the configuration. |
GetAnomalyChartDataAsync(subscriptionId, lookbackDays, runScope) | Chart data, limited to the runs runScope may read. |
AnomalyEvaluationResult has IsAnomaly, CurrentValue, BaselineMean, BaselineStdDev, ZScore, Explanation,
Severity and HistoricalDataPoints. BaselineStdDev and ZScore are set by the standard deviation method only.
await anomalyDetectionService.SaveAnomalyConfigAsync(new AnomalyConfigData{ SubscriptionId = subscriptionId, Enabled = true, DetectionMethod = AnomalyDetectionMethod.PercentageChange, Sensitivity = AnomalySensitivity.Medium, LookbackDays = 30, MinimumDataPoints = 14, AlertOnIncrease = true, AlertOnDecrease = true}, cancellationToken);SaveAnomalyConfigAsync does not check who calls it; a host that exposes it decides who may change a subscription’s
configuration.
Related
Section titled “Related”- Subscriptions: where anomaly detection is configured
- Control Tower: anomaly counts and sparklines across subscriptions
- Notifications: what recipients receive
- Tasks: work items opened by runs with rows