How your baseline is learned
There is no global "too much CPU" number. Anomalous learns what is normal for your Mac using robust statistics, so a flag means "unusual for you," not "above an arbitrary line."
Normal is personal
Anomalous has no global threshold that says "this many wakeups is bad" or "that much CPU is a problem." What counts as normal for your Mac is learned from your Mac. Every program earns its own baseline, built from how that program has actually behaved on your machine. A video editor that lives at 90% CPU and a text editor that idles at nothing are held to entirely different standards, because "normal for my Mac" is measured per-process, not against a one-size-fits-all rule.
Roughly every 90 seconds the app takes a reading of each running process across a handful of dimensions: CPU, memory, how often the process wakes the processor, disk throughput, and on capable Macs GPU share and network throughput. Those readings quietly accumulate into a picture of typical behavior, and a process is only ever compared to its own history.
Robust baselines (median & MAD)
The baseline is built from two numbers: the median (the typical middle value) and the MAD, the median absolute deviation, which is the typical wobble around that middle. In plain terms, the median answers "what does this program usually do?" and the MAD answers "how much does it usually vary?" A flag needs a reading that is far outside both.
The reason Anomalous uses median and MAD instead of a plain average and standard deviation matters. An average is fooled by the very spike you are hunting: one big outlier drags both the average and the standard deviation upward, so the anomaly ends up hiding inside its own distortion and looks almost normal. The median and MAD shrug outliers off, so the baseline stays honest even while something is misbehaving. This keeps the false positive rate low without letting real runaways slip through.
Under the hood
For each process and metric the sensor keeps the last 60 exact observations (about 90 minutes) in a reservoir and recomputes the median and MAD from them, rather than approximate streaming quantiles, because the MAD is the number the threshold keys on (BaselineStore.swift:41-59). Deviation is expressed in MADs scaled by the 1.4826 consistency constant, so a threshold like "8 MADs" reads on the familiar z-score scale; a flat history with MAD 0 returns infinity rather than dividing by zero (JudgmentCore.swift:32-63).
Seasonal patterns
Some heavy activity is normal only at certain times. A nightly cloud backup that hammers the disk at 2am is expected at 2am and alarming at 2pm. Anomalous learns this by time of day, so recurring, legitimate load in its usual window is not treated as an anomaly.
It keeps twelve seasonal buckets: six four-hour slots across the day, split by weekday versus weekend. A process is judged against the matching slot's history once that slot has collected enough data; until then it falls back to its all-time baseline.
Under the hood
Buckets are keyed by hour/4 and a weekday/weekend flag, giving 6 × 2 = 12 (JudgmentCore.swift:116-125). A seasonal bucket is only consulted once it holds at least minimumSeasonalCount (default 5) observations; otherwise the global baseline is used, or no judgment is made when there is no history at all (JudgmentCore.swift:100-125).
Confidence through agreement
A deviation does not become an alert on its own. Every rule runs on every process each tick, not just the first that matches, because agreement between independent signals is itself a form of confidence. Anomalous then grades each candidate with a score from 0 to 1 rather than a blunt yes or no.
Confidence rises when more than one rule fires on the same process at the same time, and when a spike is dramatically far above baseline. It falls for memory flags when the whole machine is under memory pressure, because then the process may be a victim rather than the cause. Only high-confidence findings become a card or notification; medium and low findings are kept quietly in a transparency panel, never surfaced.
Under the hood
Base score is 0.8 for a self-qualifying heritage rule or 0.5 for a statistical rate rule. Add +0.3 per other rule agreeing this tick (capped at +0.4, so two rules agreeing reaches the 0.8 bar), plus up to +0.35 for magnitude (0.35 × min((MADs − 8)/16, 1), letting one spectacular signal surface alone), minus 0.4 for memory rules under memory-pressure level ≥ 2. Only a score ≥ 0.8 is appended to what you see (JudgmentCore.swift:141-202, AppState.swift:744-775). Related processes flagged together, and multiple dimensions on one process, collapse into a single card so you never get five notifications for one event (JudgmentCore.swift:236-289).
False-positive gates
Being far above a near-zero baseline is statistically loud but often humanly meaningless. To stop that noise, each rate-based rule must clear a real-world floor before it can fire: a genuine workload, not a rounding error over a tiny baseline. This is the moat that keeps Anomalous from crying wolf.
The clearest example is wakeups. A rule about waking the processor also requires the process to be using at least 3% CPU, because an idle text editor can show an elevated but completely harmless wake rate. Without that check, the app might claim an idle program "drains your battery," which would simply be false.
Under the hood
Absolute floors: wakeups ≥ 150/s and ≥ 3% CPU, disk ≥ 40 MB/s, GPU ≥ 40%, network ≥ 25 MB/s. After clearing warm-up and the floor, the window average must also sit at least 8 consistency-scaled MADs above the selected median. 8 MADs is a deliberately conservative "far outside this process's normal" bar; the design ships conservative and loosens with data (DetectionRules.swift:126-167, 439-474). The older heritage rules use conservative absolute thresholds and stand on their own: sustained 80% CPU for 25 minutes, a lifetime CPU-time ratio over 0.5 across 6h uptime, a 16 GB RSS ceiling, a memory leak, and a frozen "Not Responding" app (DetectionRules.swift:98-120, 231-400).
The warm-up period
Anomalous will not judge a program it has barely met. A newly installed or first-seen process has no real "normal" to compare against yet, so no matter how loud it looks in its first minutes, a statistical rule stays silent until there is genuine history. This warm-up gate is why a fresh install does not immediately generate flags.
Under the hood
A rate-based rule cannot fire until the process lineage has at least warmUpObservations (default 3) recorded baseline observations; a first-seen process records nothing and judges nothing on that tick (DetectionRules.swift:168-171, 444; AppState.swift:704-719). The baseline to judge against is also selected before the current tick is folded in, so a reading never contaminates the baseline it is being measured against (BaselineStore.swift:296-341).
Defending the baseline from poisoning
A runaway process that burns for two days must never quietly teach Anomalous that burning is normal. If the app simply kept learning from a misbehaving program, the anomaly would slowly become the new baseline and the flag would fade, which is exactly the failure mode a monitor cannot have. Anomalous defends against this baseline poisoning in two ways.
First, once a process is flagged, it stops teaching its baseline: its runaway readings are no longer folded into the median and seasonal history. Second, even for normal learning, each seasonal update is clamped so a single outlier can nudge the baseline but never yank it. The one thing that can move the envelope is your explicit "this is normal for me" acknowledgment.
Under the hood
A flagged process is fed with feedBaselines:false so its readings do not update the robust or seasonal baseline (AppState.swift:694-719). The seasonal estimator clamps each update to 3 scales, Huber-style, with the scale taken from the learned baseline rather than the incoming value, so an outlier moves the median only a little (BaselineStore.swift:100-115). Flags persist up to 7 days across relaunches so a runaway is not silently forgotten, while stale lineages decay after 30 days and are capped at 2000 (BaselineStore.swift:170-221). A recovered process still heals: the cumulative CPU-time card additionally checks live CPU (≥ 20%), so a once-hot, now-idle process clears instead of staying flagged forever (DetectionRules.swift:108-114; AppState.swift:1220-1227).
All of this detection math runs on-device, every tick, with nothing gated behind a network call. If you want to see the exact statistics, the sensor is open source at anomalous-mac, and the public data lives in the anomalous-corpus repository — PRs are welcome.