Integrations
Prometheus
Connect Alertmanager as the alert source and Prometheus for live metric context.
Two separate pieces, both read-only toward your stack:
| Piece | Role | Gives you |
|---|---|---|
| Alertmanager | Alert source | A webhook copy of every alert, firing and resolved |
| Prometheus connector | Evidence source | Live metric values in triage, verification checks, and PromQL tools over MCP |
The connector only queries. It never writes metrics, creates rules, or changes Prometheus.
Alertmanager: the alert source#
Add a receiver and route to it (full example: examples/alertmanager.yml):
route:
receiver: alertint-agent
group_by: [alertname, cluster, namespace, service]
group_wait: 10s
group_interval: 30s
repeat_interval: 4h
receivers:
- name: alertint-agent
webhook_configs:
- url: "http://<agent-host>:9911/webhook/alertmanager"
send_resolved: true
http_config:
authorization:
credentials_file: /etc/alertmanager/alertint_token
- Token: must match the variable named by
alertmanager.webhook_token_env(defaultALERTINT_WEBHOOK_TOKEN). Usecredentials_fileso it isn't stored inalertmanager.yml. send_resolved: trueis required. Resolutions are what move a Situation to Confirming recovery and Recovered.- Keep your paging: add
alertint-agentas a child route withcontinue: trueinstead of replacing your top-level receiver.
Reusable expected schedules#
To mark a recurring Alertmanager alert as expected on a schedule, map it to its exact Prometheus rule:
alertmanager:
instance_id: prod-alertmanager-eu
rules:
- alert_name: ReconciliationLoad
group: batch-jobs
rule: ReconciliationLoad
scope_labels: [service]
prometheus:
instance_id: prod-prometheus-eu
- Needs the connector (
prometheus.base_url) to prove the rule is active. - Unmapped alerts go through normal triage.
- Change identities in a quiet window: a new Alertmanager
instance_idstarts a new delivery and episode namespace; a new Prometheusinstance_idstarts a new rule scope. pendingorfiringin Prometheus both count as "the rule is active". The Alertmanager webhook is still what fires or updates a Situation.
Not available when the mapped rule is missing or ambiguous, its definition
can't be read, it depends on a recording rule, or Prometheus has active
alert_relabel_configs. AlertINT never infers a rule from generatorURL,
externalURL, alert name, or fingerprint, and never claims which rule version
produced an older delivery.
Prometheus connector: live metrics#
Configure#
prometheus:
base_url: http://localhost:9090 # setting this turns the connector on
# enabled: false # force off despite base_url
bearer_token_env: PROMETHEUS_BEARER_TOKEN # optional; see Authentication
# org_id: tenant-1 # multi-tenant Mimir/Cortex only
timeout_seconds: 10 # default
default_range_minutes: 60 # default
| Field | Default | Does |
|---|---|---|
enabled |
auto | On when base_url is set; false forces off |
base_url |
— | Prometheus API root, e.g. http://localhost:9090 |
bearer_token_env |
— | Only if something in front of Prometheus checks a token |
org_id |
— | Tenant sent as X-Scope-OrgID (Mimir, Cortex) |
timeout_seconds |
10 |
Query timeout |
default_range_minutes |
60 |
Default range-query look-back |
Compatible backends: anything serving /api/v1/query and
/api/v1/query_range.
- Thanos: point
base_urlat the Querier (default port 10902), not a Sidecar or Store Gateway. You get the full retention across all connected Prometheus instances.
Authentication#
Prometheus has no token auth of its own; a token exists only if something in front of it checks one. Test first:
curl "http://<prometheus-host>:9090/api/v1/query?query=up"
| Result | Your setup | Token |
|---|---|---|
| Answers | Plain Prometheus (e.g. kube-prometheus-stack in-cluster) | None: omit bearer_token_env |
| Rejected | Your own reverse proxy (nginx, Traefik, oauth2-proxy) | One you invent, as below |
| Rejected | Managed stack (OpenShift monitoring, kube-rbac-proxy) | A ServiceAccount token, as below |
Your own proxy:
- Generate a long random secret.
- Make the proxy require
Authorization: Bearer <secret>. - Set
bearer_token_env: PROMETHEUS_BEARER_TOKENand exportPROMETHEUS_BEARER_TOKEN=<secret>.
Managed stack:
- Create a ServiceAccount with metrics-read permission (on OpenShift,
cluster-monitoring-view). - Mint a long-lived token:
kubectl create token <sa> -n <namespace> --duration=8760h. The default one hour is too short: AlertINT reads the token once at startup. - Point
base_urlat the authenticated endpoint (on OpenShift, Thanos Querier on port 9091) and wire the token throughbearer_token_env.
Verify before starting the agent:
curl -H "Authorization: Bearer $PROMETHEUS_BEARER_TOKEN" \
"<base_url>/api/v1/query?query=up"
After rotating the token, update the variable and restart the agent.
Multi-tenant Mimir and Cortex#
Every query needs an X-Scope-OrgID header, or it's rejected even with valid
credentials.
- Set
org_idto your tenant ID. It's not a secret, so it goes inline. - Independent of the token: use either, both, or neither.
curl -H "X-Scope-OrgID: <tenant>" \
-H "Authorization: Bearer $PROMETHEUS_BEARER_TOKEN" \
"<base_url>/api/v1/query?query=up"
Situation evidence and coverage limits#
For Alertmanager alerts, Situation preparation reads the configured Prometheus
/api/v1/rules endpoint and looks for one uniquely named current alerting rule.
When its expression yields exact metric names without recording-rule dependencies,
those metrics are collected before broader diagnostics. Both reads preserve the
same scope. The alert name remains available after a resolved delivery. If no
firing member matches the scope, current-rule selection is explicitly marked as
an advisory hint; it does not prove the historical rule, establish lifecycle
state or grant reusable schedule authority.
- Small background samples: optional diagnostics and Alertmanager scope fallback collect at most 32 series plus one overflow sentinel, preserving the original time resolution. Primary rule metrics keep the plan's series limit. Samples can omit whole metric families; a returned sentinel marks coverage incomplete. Large labels or primary queries can still reach the byte limits.
- At most three source calls: rule lookup, primary metrics, then diagnostics. A two-call budget allows lookup and primary metrics; a one-call budget uses bounded scope collection.
- Explicit fallback: ambiguous rules, unsupported expressions and lookup failures use bounded scope collection and record the reason.
- Specific warnings: ntfy and Slack identify series-count, 16 KiB summary, and 1 MiB decoded-response limits with known counts and an observation reference. Overflow counts are lower bounds; rejected responses have unknown counts.
- Earlier evidence survives a later diagnostic failure. Inspect details with
alertint_list_observation_runs, using the Situation'sidorhandle. Older notifications say when the specific cause is unavailable.
How evidence queries are built#
When an Incident is ready for triage, AlertINT queries metrics at its start time:
- Selector from the labels all member alerts share, filtered to an allowlist:
namespace,service,job,pod,container,instance. This is what gives Kubernetes alerts (often withoutinstance) real metrics. - Per-instance supplement: any alert with
instancealso gets its own per-instance query. - Zero-match retry: if the selector matches nothing, retry once with only
namespace,pod,container,instance. - Top 10 non-system series per query, ranked by how many labels they share with the alerts, go into the prompt as Live metrics. Real numbers outrank annotation text.
Multi-cluster: triage.extra_selector_labels#
If one Prometheus or Mimir serves several clusters, cluster isn't in the
allowlist, so queries mix clusters. Add topology labels:
triage:
extra_selector_labels: [cluster]
For an Incident sharing cluster=eu-west, namespace=payments, service=checkout:
| Query | Default | With [cluster] |
|---|---|---|
| Metrics | {namespace="payments",service="checkout"} |
{cluster="eu-west",namespace="payments",service="checkout"} |
| Zero-match retry | {namespace="payments"} |
{cluster="eu-west",namespace="payments"} |
| Per-instance supplement | {instance="10.0.4.7:9100"} |
{cluster="eu-west",instance="10.0.4.7:9100"} |
Logs (before label_map) |
{namespace="payments",service="checkout"} |
{cluster="eu-west",namespace="payments",service="checkout"} |
| Verification floor | {namespace="payments",service="checkout"} |
{cluster="eu-west",namespace="payments",service="checkout"} |
- Extras are never dropped by the retry. If your series lack the label, you get an empty result with the exact query in the log, never a silent widening to the wrong cluster.
- Only shared keys are used. A configured label missing from any member alert is left out.
- Metrics but not logs? Drop it for Loki with
logs.loki.label_map: {cluster: ""}(see Loki). - Topology, not identity: use
cluster,region,datacenter. A pod-like label would shrink the verification floor to the Incident's own targets. - Checked at startup: invalid names (
cluster-1) and built-in duplicates (namespace) are rejected. - Debug:
--log-level=debugshows which alert labels the allowlist dropped.
Evidence line#
Every Finding carries a per-source evidence summary: how many metrics, log
lines, changes, and Sentry issues fed the triage, e.g.
Prometheus 21 metrics · Loki 0 lines · Changes 2 · Sentry unreachable.
unreachable is distinct from a genuine 0, so a down connector is visible
instead of silently lowering confidence.
MCP tools#
| Tool | Parameters |
|---|---|
prometheus_query |
expr (required), time (optional, ISO 8601) |
prometheus_query_range |
expr, start, end (ISO 8601), step (optional; auto by default) |
- Passthrough: your agent's PromQL goes to Prometheus exactly as sent, and Prometheus's own error comes back verbatim. No local check, no repair.
- Backend extensions work: e.g. VictoriaMetrics MetricsQL functions like
median_over_time. - Automated checks are different: PromQL the verification round runs is validated locally first; see Verification round. The round never calls these two tools.
Query CPU usage for instance api-1 right now.
Show me the error rate for the last 30 minutes.
What was the latency trend during this incident?
Or pass PromQL directly:
cpu_usage_percent{instance="api-1"}
rate(http_errors_total[5m])