0041. Operator-managed reliability alarm suppression¶
- Status: accepted
- Date: 2026-07-29
Context and Problem Statement¶
Issue #166: on a monitored Windows 11 host, reliability reports overall=crit because
one single, well-known-benign event pattern dominates the window —
Microsoft-Windows-CAPI2 / 4176 ("PFX operation failed as AuthSafes count doesn't lie in
expected range"), emitted continuously by CryptSvc, a documented Windows quirk with no known
fix (Microsoft support forums, 2022–2026, no root cause ever identified). 3439 of 3743
events in 7 days are this one pattern; the one event that actually matters in the same
window — a single Kernel-Power/41 unclean shutdown — is lost in the noise. Over the
dashboard or agent_health, "3439 identical harmless lines" is indistinguishable from
"3439 individually relevant errors" without a manual diag_eventlog lookup.
We need a way for the operator to exclude a specific (source, event_id) pattern from
health scoring — without hiding the raw event counts the issue explicitly wants
preserved (the reporter is also concerned about the pattern's volume rotating the Windows
Application log within ~8 days; that concern is addressed separately, see Consequences).
A key finding while designing this: reliability health is evaluated at ~8 call sites
(the dashboard's two annotated read paths, but also alerting.py's push-notification loop,
digest.py's weekly digest, the fleet list, the trend sparkline, and the copilot chat), and
only the two dashboard paths run the ADR-0026 LLM categorization that carries a severity
field. The rest take health_rules._rule_reliability_by_volume, where 3743 ≥ the crit
threshold (50) regardless of any severity annotation. A suppression mechanism that only
reached the annotated scoring path would fix the dashboard modal and leave the fleet tile
and the push alerts still screaming crit.
Considered Options¶
- Teach the LLM classifier about this pattern (extend the ADR-0026 prompt, or a hand-maintained known-benign table). Rejected: ADR-0026 exists precisely because the space of Windows event sources is open-ended and unmaintainable by hand, and whether a pattern should be muted is a per-fleet operator decision, not a global classification fact — the same message can be a genuine problem on a different operator's fleet.
- Raise the volume thresholds, or cap how much one pattern can contribute to the total. Rejected: this hides real diversity and cannot distinguish "3439 identical harmless lines" from "3439 individually relevant errors", which is the issue's core complaint.
- Let the operator override the LLM
severitytobenign. Rejected: this conflates two different claims ("the model classified this as harmless" vs. "the operator decided to ignore this") behind one field, is indistinguishable in the UI from a model verdict, and loses the audit trail of who decided and why. - A new setting in the ADR-0032
Settingscatalog. Rejected: settings are scalar key/value knobs; this needs a scoped, growable list of rules with per-row provenance (who, when, why), not a single value. - Stamp suppression at telemetry-insert time, like
web_activity.flagged(ADR-0024). Rejected: a rule added or removed by the operator would not take effect until the agent's next telemetry push, so the dashboard's suppress/unsuppress toggle would appear broken for minutes at a time. - Chosen: a new operator-authored suppression-rule table, mirrored in-memory, stamped
onto reliability event groups at the
TelemetryStoreread-path boundary.
Decision Outcome¶
Chosen option: a new reliability_suppressions SQLite table
(agent_id, source, event_id, note, created_at, created_by — empty-string
sentinels, not NULL, for "fleet-wide" / "any source", since SQLite treats NULLs as
pairwise-distinct in a uniqueness check) plus ReliabilitySuppressionStore (mirrors
PolicyStore's connect/list/add/remove idiom). A rule's precedence is most-specific-wins:
(host, exact source) › (host, wildcard) › (fleet, exact source) › (fleet, wildcard).
reliability_suppression.SuppressionList is the in-memory mirror (the same
single-process/single-event-loop argument ADR-0032 makes for Settings): loaded at
startup, replaced on every write, matched with a synchronous, lock-free dict lookup —
(source, event_id) matching needs no LLM and no API key, so unlike the ADR-0026
categorization it is cheap enough to run on every health read, not just the two
annotated dashboard paths.
The key mechanism: TelemetryStore gained an optional annotate: Callable[[agent_id,
snapshot], None] | None hook, called from _row_to_record/daily_latest — i.e. every
latest(), history(), and daily_latest() call. main.py wires
store.annotate = suppression.mark. Because alerting.py, digest.py, the fleet list,
the chat tool-use loop, and every MCP tool all read snapshots through these same
TelemetryStore accessors, suppression reaches all of them automatically — no per-call-site
threading of an agent_id parameter through health_rules.evaluate_snapshot/
evaluate_section, and no risk of a missed call site silently staying unsuppressed. This
was chosen over threading an explicit agent_id through every health-evaluation call
(rejected: ~8 signature changes, easy to miss one — the exact bug class this ADR fixes) and
over routing alerting/digest through a single tools.build_health choke point
(rejected: a real import cycle — tools.py already dodges tools -> chat -> ... -> tools
with a deferred import; routing the alert loop through tools would drag the chat/Anthropic
path into it).
mark() stamps suppressed: true and a suppressed_by descriptor (rule id, scope, source,
event id, note) onto each matched event group without touching category/severity/
suspected_cause — the LLM's verdict and the operator's suppression are different claims
and must stay visually and semantically distinct (never folded into a benign severity).
health_rules.py stays pure (no store, no I/O): both _rule_reliability_by_severity and
the volume fallback _rule_reliability_by_volume read the suppressed field already
present on the payload and exclude those groups from every scoring list — but a suppressed
Windows-level: critical group also skips the automatic "critical -> serious" escalation,
because explicit operator intent overrides the automatic one (otherwise a suppressed
Kernel-Power/41 could never actually be muted). Raw counts (recent_crashes, per-event
count, the fleet heatmap's cell count) are never altered — only the scoring total is
reduced by suppressed volume. The Windows Reliability Index overlay
(_RELIABILITY_SI_CRIT/_WARN) is deliberately not suppressible: it is an independent,
agent-computed signal that a (source, event_id) rule carries no information about, and
letting suppression silence it would let one muted pattern hide a genuinely unstable
machine.
The dashboard's reliability section detail gained: a suppressed badge per event row
(distinct from the benign severity pill), a per-row suppress/unsuppress button (always
fleet-wide with the exact source — the decided default, no scope dialog on a one-click
affordance), and a rule-management panel with a manual-add form (event id required,
source optional = wildcard, plus a fleet/host scope selector and a free-text note). New
/api/reliability/suppressions routes (GET any role, POST/DELETE operator+) and three MCP
tools (reliability_suppression_list/add/remove, the two mutators behind the ADR-0009
confirm-gate) give Claude the same read/write access as the dashboard. agent_snapshot
picks up the suppressed marker automatically through the same TelemetryStore hook, with
no LLM call and no extra latency — closing exactly the gap the issue's repro steps describe
(comparing agent_snapshot's raw breakdown against known-noisy patterns).
No wire-contract change: suppressed/suppressed_by are server-internal read-path
annotations, exactly like category/severity/suspected_cause (ADR-0026) and
web_activity.flagged (ADR-0024) — the agent never sends or sees them, and
kenny-agent/ needs no changes.
Consequences¶
- Good, because a fleet-wide, well-known-benign Windows quirk (or any single dominant pattern) can be muted once and stops distorting severity scoring everywhere — the dashboard, push alerts, the weekly digest, and the fleet list alike — while its raw volume stays fully visible for anyone who wants to audit it.
- Good, because the
TelemetryStore.annotateseam is a single, well-tested integration point rather than ~8 scattered call-site edits, and it composes cleanly with the existing ADR-0026 LLM annotation (different fields, no conflict, independently degradable). - Good, because the Windows Reliability Index overlay and the "an
unknownseverity is never silently benign" property from ADR-0026 are both preserved unconditionally — suppression can only ever narrow pattern-based scoring, never the independent signals. - Bad / accepted, because an operator can now suppress a pattern that later turns out to be
genuinely serious. Mitigated by: the pattern stays fully visible with its raw count and
its LLM severity (never hidden), the audit trail (
created_by, timestamp), and the health reason explicitly naming how many patterns are suppressed rather than silently going quiet. - Bad / accepted, because suppression rules are yet another small piece of durable operator
state (alongside
operator_policy_rulesandwebfilter_domains) that must be considered whenever a host is removed from inventory — handled by purging only that host's own rules on removal, while fleet-wide rules (which mute a Windows quirk, not a specific PC) survive. - Explicitly out of scope: the issue's second concern — that ~490 events/day from one
source rotates the 20 MB Windows Application log within ~8 days, overwriting genuine
diagnostic history — is not addressed here. Suppression is a server-side scoring and
presentation change; it does not reduce what the agent collects, what it pushes, or how
Windows manages its own event log. That is a log-retention problem on the monitored host,
not a severity-scoring problem, and deserves its own design (e.g. a read-only "log
rotation pressure" signal, or a guarded, confirm-gated
MaximumSize/retention change viapowershell_exec). Tracked as a follow-up issue rather than folded in here. - Also out of scope: suppression matches on
(source, event_id)only, not on message content — it cannot distinguish "this specific AuthSafes value is harmless" from "a different value in the same event id would be a real problem". Nor does it affectdiag_eventlog, a raw, deliberately unfiltered diagnostic tool.
More Information¶
- ADR-0026 — the
category/severity/suspected_causeread-path annotation this decision sits alongside. - ADR-0024 — the closest prior art
for an operator-editable, per-host rule list with a dashboard editor and a store/mirror
pair (
WebFilterStore/WebFilterService). - ADR-0020 — the
operator-append-only rule table + in-memory mirror pattern this reuses
(
PolicyStore/PolicyEngine). - ADR-0032 — the "single process, one event loop, so a synchronous in-memory mirror is safe and cheap" argument this decision borrows.
- Issue #166.