post

đź”§ Designing an Alert Decision Layer (with a Working CLI in Python)

Most teams don’t have an alerting problem. They have a decision problem.

Over time, SRE and DevOps teams add more checks, more monitors, more “smart” tools. The result is often alert fatigue: hundreds of alerts, dozens of dashboards, and still no clear answer to the question “Do we wake someone up for this?”

In this post we’ll do two things:

By the end you’ll have:


The problem: alerts without decisions

Alert fatigue has been written about a lot, but the symptoms are consistent:

What’s missing is a small decision layer that sits between your alert sources and your incident response.

That layer should be:

That’s what we’ll build.


Design principles

Before touching code, let’s describe the design as an architect:

  1. Normalize first, then decide
    Every alert source has its own JSON schema. We’ll normalize into a simple Alert model (id, name, service, severity, environment, fingerprint, timestamps) before any rules.

  2. Service profiles matter
    A critical alert on a tier1, SLO-critical service is not the same as a critical on a best-effort internal tool. We encode this in a ServiceProfile model loaded from services.yml.

  3. History influences action
    A fingerprint fired once is different from a fingerprint that fired 40 times today. We’ll use a small History model to treat flapping/noisy alerts differently.

  4. Rules are code, not magic
    The AlertDecisionEngine is a small, explicit rule engine. The first version has no AI – just clear if/else policies you can review in code.

  5. Decisions must be explainable
    Every decision carries a reason string. This is where AI can plug in later to generate richer explanations, but today it’s hand-written and predictable.


Architecture

Here’s the high-level architecture of the Alert Decision Layer:

  1. Sources
    • Prometheus Alertmanager, PagerDuty, or any system that can export alerts as JSON.
  2. Normalizer (loader.py)
    • Reads raw JSON and maps each item into an Alert dataclass with a consistent shape.
  3. Context loaders
    • services.yml → ServiceProfile per service (tier, SLO criticality, owner).
    • history.json → History per fingerprint (how often we’ve seen this alert recently).
  4. Decision engine (engine.py)
    • For each Alert, looks up the service profile and history and decides one of: page, ticket, aggregate, suppress.
  5. Reporters (reporter.py)
    • Writes a human-friendly decision_report.md and a machine-friendly decision_report.json.
    • Prints a table to the terminal so you can sanity-check the decisions quickly.

This is intentionally simple: a single CLI binary that can be run locally, in CI, or as a cron/systemd job.


Project structure

alertdecider-agent/
  __init__.py
  __main__.py
  cli.py          # CLI entry point
  models.py       # Alert, ServiceProfile, History dataclasses
  loader.py       # Load alerts/services/history from JSON/YAML
  engine.py       # Decision rules (AlertDecisionEngine)
  reporter.py     # Console + Markdown/JSON output
examples/
  alerts.json     # Example alerts
  services.yml    # Service risk profiles
  history.json    # Simple alert history

You can clone the repo and run it against the example data to see how everything works before wiring it into your own tooling.


Setup

git clone https://github.com/AutoShiftOps/alertdecider.git
cd alertdecider
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

The dependencies are minimal:


Modelling alerts, services, and history

We start by defining three small dataclasses in models.py:

This gives us a clean domain model to talk about decisions.


Implementing the decision engine

engine.py contains the AlertDecisionEngine with a handful of clear rules.

Examples of policies encoded:

The whole point is that you can read this engine like a policy document.


Running the CLI with example data

With the repo set up, try the example:

python -m alertdecider-agent   --alerts examples/alerts.json   --services examples/services.yml   --history examples/history.json   --out-dir out

cat out/decision_report.md

You’ll see a table in your terminal and a Markdown report with each alert’s decision and reason.

With the provided examples, you should observe:

From here, you can plug in your own alerts JSON and adjust services.yml and engine.py to match your reality.


Where AI fits in (later)

Right now, alertdecider-agent is deliberately rule-based and transparent.

Once you’re happy with the structure, you can start experimenting with AI in safe, incremental ways:

The important part is: the control plane (what gets paged vs ticketed vs suppressed) remains in code you own.


Next steps for your team

If you try this out, here are some directions to evolve it:

This is the kind of small, opinionated tool that can pay off quickly for SRE/DevOps teams drowning in alerts – and it’s a great foundation for more advanced AI-assisted incident management.