Skip to content

EMS Group Alert Design

Overview

  • The group alert is a virtual object in EMS. It is not an alert from a real alert source; it groups related alerts together.
  • The group alert follows the operational model of Keep incidents and single alerts. It supports similar actions and workflows, including assignment, acknowledged status, pending status, resolve, audit, workflows, and AI execution.
  • The group alert can be created by correlation rules, manually by users, from AI flows, or from other processors.
  • The group alert supports appending sub-alerts and merging multiple group alerts into one destination group alert.
  • The group alert supports group-alert-specific query, filtering, facets, and CEL filtering. In the mixed alerts feed, group alert visibility follows the alerts_count > 1 rule described below.
  • The group alert appears in the same alerts feed as single alerts when it has more than one sub-alert. It can expand to show the related sub-alerts.
  • The group alert can be enriched with extra fields and can be unenriched.
  • The group alert can trigger AI analysis and workflows, and related workflow and AI rule executions can be queried from the group alert APIs.
  • A sub-alert can belong to more than one group alert.

Group Alert Function Design

Create Group Alert

  • Users can manually create an empty group alert. An empty group alert starts with alerts_count = 0 and status new.
  • Users can manually add alerts to an existing group alert.
  • Users can merge group alerts into another group alert.
  • Correlation rules can create group alerts automatically when one firing alert matches the rule.
  • A group alert with exactly one sub-alert can exist in the database and in the group alert APIs. The alerts feed displays that sub-alert as a single alert until the group has more than one sub-alert.
  • A sub-alert can belong to multiple group alerts. Keep stores membership in the lastalerttogroupalert association table.

Correlation Rule Interval Behavior

Correlation rules use the Group Interval setting to route alerts into group alerts. The interval is measured from last_firing_time, which is the time the most recent firing sub-alert arrived. A new firing alert refreshes the interval window for the group.

When a new alert matches a correlation rule, Keep chooses the target group alert with this order:

  1. If the alert fingerprint already belongs to a group for the same rule and rule fingerprint, use that existing group alert, even if the group interval has expired. This lets repeated alerts reopen the older group.
  2. If the alert fingerprint is new, use the latest active group alert for the same rule and rule fingerprint.
  3. If there is no active group alert and the incoming alert is firing, create a new group alert.

When multiple active group alerts match a new fingerprint, Keep uses the latest created group alert. The selection is based on created_time descending.

Interval-aware alert routing table:

Alert fingerprint Interval expired? Incoming alert status Behavior
Existing in a group for the same rule and rule fingerprint No firing Add the alert to that group. Reopen the group if it is resolved.
Existing in a group for the same rule and rule fingerprint No resolved Add the alert to that group.
Existing in a group for the same rule and rule fingerprint Yes firing Add the alert to the older group. Reopen the group if it is resolved.
Existing in a group for the same rule and rule fingerprint Yes resolved Add the alert to the older group.
New fingerprint No firing Add the alert to the latest active group.
New fingerprint No resolved Add the alert to the latest active group.
New fingerprint Yes firing Create a new group alert.
New fingerprint Yes resolved Do not create a group alert. Keep the alert as a single alert.

Keep does not automatically move a sub-alert from one group alert to another. The same alert fingerprint can be associated with multiple group alerts through the association table.

Merge Group Alerts / Sub-alerts

  • Users can manually select one or more group alerts and merge them into a destination group alert.
  • Merging group alerts moves the source groups' sub-alerts into the destination group alert.
  • After a successful merge, each source group alert is marked as closed, and Keep records merged_into_group_id, merged_at, and merged_by.
  • If all sub-alerts are removed from a group alert, the group alert is set to closed automatically.
  • If the destination group alert is acknowledged or pending, Keep preserves that status when alerts are added. Otherwise, Keep sets the destination group alert to firing when any newly added sub-alert is unresolved.

Group Alert Status Change

  • New: Auto: A group alert starts as new when it is created before any sub-alert is attached. Manual: Users cannot set new manually.
  • Firing: Auto: A group alert becomes firing when it has an unresolved sub-alert and the group status is not acknowledged or pending. Manual: Users cannot set firing manually.
  • Acknowledged: Auto: External status synchronization can set a group alert to acknowledged. Manual: Assignment sets a group alert to acknowledged. Users can also use the batch status API.
  • Pending: Auto: External status synchronization can set a group alert to pending. Manual: Users can set pending through the batch status API.
  • Resolved: Auto: A group alert becomes resolved when all sub-alerts are resolved and the group is not acknowledged or pending. Manual: Users can set resolved through the batch status API. Manual resolve requires all sub-alerts to be resolved unless the caller propagates the status to sub-alerts.
  • Closed: Auto: A group alert becomes closed when it is merged into another group or when all sub-alerts are removed. Manual: Users cannot set closed manually.

Manual status changes use POST /group-alerts/status. The endpoint rejects manual changes to new, firing, and closed.

Sub-alert Status Change (Many-to-Many Consideration)

Sub-alert status can change as part of group alert actions. Because a sub-alert can belong to multiple group alerts, callers must account for cross-group effects when they propagate status changes to sub-alerts.

Default Behavior:

Group Action Group Status Change Sub-Alert Status Change
Assign group Group -> acknowledged Propagates to sub-alerts
Pending group Group -> pending No change unless with_alerts = true
Resolve group Group -> resolved No change unless with_alerts = true
Automatic group recalculation Group -> firing or resolved No change

Optional Bulk Action:

  • Batch status changes can propagate to sub-alerts when the request sets with_alerts = true.
  • Automatic group status recalculation does not move sub-alerts between groups.
  • Status propagation affects the underlying sub-alerts, even when those alerts are associated with multiple group alerts.

Trigger AI Analysis

  • Group alerts can participate in AI rules as group alert entities.
  • Group alert fields are available when AI rules execute for a group alert.
  • AI rule execution history is available through GET /group-alerts/{group_alert_id}/airules/executions.
  • AI workflows can read associated sub-alerts through sub_alerts.
sequenceDiagram
    actor User
    participant EMS as EMS Backend
    participant Agent as AI Agent
    participant API as EMS API

    alt Manual trigger
        User->>EMS: Trigger AI analysis on group alert
    else Auto trigger
        EMS->>EMS: Match AI rule for group alert
    end

    EMS->>Agent: Trigger analysis with group alert fields
    Agent->>API: GET group alert sub-alerts
    API-->>Agent: Sub-alert objects
    Agent->>Agent: Analyze sub-alert context
    Agent-->>EMS: Return group-level analysis result
    EMS-->>User: Display AI analysis result

Trigger Workflow

  • Group alerts can participate in workflows as group alert entities.
  • Group alert fields are available when workflows execute for a group alert.
  • Workflow template rendering can read associated sub-alerts through sub_alerts.
  • Workflow execution history is available through GET /group-alerts/{group_alert_id}/workflows.

Trigger Timing

Event Workflow AI
New group alert with sub-alerts Yes Yes
Sub-alert status change No No
Group status auto change Yes No
Group status manual change Yes Yes
New fingerprint added to group Yes Yes

Enrich fields

  • Group alert enrichments are stored separately from alert enrichments and use the group alert id as the enrichment fingerprint.
  • Users can enrich multiple group alerts with POST /group-alerts/enrich.
  • Users can remove enrichment fields from one group alert with POST /group-alerts/unenrich.
  • The enrich endpoint rejects fields that must be changed through dedicated APIs, such as status fields. Use /group-alerts/status for status changes.

Add/Remove Sub-Alerts

  • Users can add or remove sub-alerts from group alerts through the group alert detail and management flows.
  • Adding sub-alerts uses POST /group-alerts/{group_alert_id}/alerts with a JSON array of alert fingerprints.
  • Removing sub-alerts uses DELETE /group-alerts/remove-alerts with one or more remove commands.
  • Group alert membership is many-to-many, so the same sub-alert can remain associated with other group alerts after it is added to or removed from one group alert.

Timeline View

  • Group alerts record audit activity for important lifecycle changes.
  • The audit view can show events such as creation, assignment, status changes, comments, sub-alert additions, sub-alert removals, enrichment changes, and merge activity.
  • Use GET /group-alerts/{group_alert_id}/audit to fetch group alert activity.

Group Alert Object Design

Group Alert:

Field Name Type Constraints / Default Description
id uuid Generated Unique group alert identifier. In mixed alert results, this value is used as the group alert fingerprint.
tenant_id varchar Required Tenant identifier.
description text null Group alert description.
name varchar Required Group alert name.
start_time timestamp null Earliest timestamp among associated sub-alerts.
end_time timestamp null Time when the group alert was resolved or closed.
assignee varchar null User assigned to the group alert.
affected_services json [] Services aggregated from sub-alerts.
alerts_count int4 0 Number of sub-alert associations.
last_seen_time timestamp null Latest observed sub-alert time. Some status refresh paths also update this value during group status recalculation.
last_firing_time timestamp null Latest firing time among associated sub-alerts.
last_updated_time timestamp Current time Last group alert update time.
last_updated_by varchar null User or system actor that last updated the group alert.
created_by text null User or system actor that created the group alert.
created_time timestamp Current time Time when the group alert was created.
rule_id uuid null Correlation rule identifier.
rule_fingerprint text "" Rule grouping fingerprint.
severity int4 5 Severity order.
status varchar new Group alert status.
merged_into_group_id uuid null Destination group alert when this group is merged.
merged_at timestamp null Merge time.
merged_by varchar null User who performed the merge.
group_alert_type varchar manual Group alert source type: manual, ai, rule, or topology.

Use id as the unique group alert identifier. Use rule_fingerprint to group alerts created by the same correlation rule and grouping key.

LastAlert to Group Alert(many-to-many):

Field Name Type Constraints / Default Description
group_alert_id uuid Required Parent group alert.
tenant_id varchar Required Tenant identifier.
timestamp timestamp Current time Time when the association was created.
alert_fingerprint varchar Required Sub-alert fingerprint.
group_by varchar Required User or system actor that added the alert to the group.

The primary key is the combination of group_alert_id, tenant_id, and alert_fingerprint.

Query Strategy for Mixed Results

The alerts feed uses the v2 alerts query to return both single alerts and group alerts. Each result has a type field with either alert or group_alert.

Keep applies two visibility rules in the mixed feed:

  • Group alert rows are returned only when GroupAlert.alerts_count > 1.
  • Single alert rows are hidden only when they belong to a group alert with alerts_count > 1.

This means a group alert with one sub-alert is not shown as a grouped row in the alerts feed. The sub-alert remains visible as a normal single alert.

Group alerts still appear in group alert management APIs regardless of whether they have zero, one, or multiple sub-alerts.

Response Structure:

{
  "results": [
    {
      "type": "alert",
      "data": {
        "fingerprint": "alert-fingerprint-1",
        "name": "Database connection failed",
        "status": "firing"
      }
    },
    {
      "type": "group_alert",
      "data": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "Database connectivity group",
        "status": "firing",
        "alerts_count": 2
      },
      "sub_alerts": [
        {
          "fingerprint": "alert-fingerprint-2",
          "name": "Database connection failed",
          "status": "firing"
        }
      ]
    }
  ],
  "count": 2,
  "limit": 25,
  "offset": 0
}

Consistency Maintenance

When alerts are added to or removed from group alerts, Keep updates the group alert metadata and the many-to-many association table.

  1. Adding Alert to Group:

    • Insert a record into lastalerttogroupalert.
    • Keep existing associations for the same alert fingerprint in other group alerts.
    • Increment groupalert.alerts_count.
    • Refresh start_time, last_seen_time, last_firing_time, affected_services, and severity-related metadata.
    • Set the group status to firing when a newly added sub-alert is unresolved and the group is not acknowledged or pending.
  2. Removing Alert from Group:

    • Delete the record from lastalerttogroupalert.
    • Decrement groupalert.alerts_count.
    • Recalculate service, severity, and time metadata from remaining sub-alerts.
    • Set the group status to closed when no sub-alerts remain.
    • Set the group status to resolved when remaining sub-alerts are all resolved and the group is not acknowledged or pending.

Performance Considerations

  • groupalert(last_seen_time, tenant_id) supports time-windowed group alert queries.
  • groupalert(severity, tenant_id) supports severity filtering and sorting.
  • groupalert(assignee, tenant_id) supports assignee filtering.
  • lastalerttogroupalert(alert_fingerprint, tenant_id) supports lookup by sub-alert fingerprint.
  • Membership queries use lastalerttogroupalert to resolve the relationship between sub-alerts and group alerts.

Group Alert UI Design

  • The group alert shows up in the same alerts feed as single alerts when the backend v2 query returns type = "group_alert".
  • The backend v2 query returns group alert rows only when alerts_count > 1.
  • A group alert with one sub-alert exists in the group alert APIs, but the alerts feed displays its sub-alert as a single alert.
  • Group alert rows can expand to show sub-alerts.
  • Group alert rows use the same table field selection as regular alert rows where values are available.
  • Group alert rows show the sub-alert count from alerts_count.
  • Group alert detail pages show group alert fields and related activity.
  • Group alert management flows support create, edit, assign, merge, enrich, unenrich, add sub-alerts, remove sub-alerts, comments, workflows, and AI rule execution history.
  • Grouped mode is controlled by preset/query state through group_alert_only.
  • The create and edit flows use name, description, assignee, severity, and status.

API

The Group Alerts API provides functionality for managing group alerts throughout their lifecycle, including creation, status updates, alert association, merge, enrichment, audit activity, workflow history, and AI rule execution history.

1. Create Group Alert

Method: POST Path: /group-alerts Description: Creates a new group alert with the provided details.

Request Body

{
  "name": "Database connectivity group",
  "description": "Related database connectivity alerts",
  "severity": "high",
  "assignee": "engineer@example.com",
  "status": "new"
}

Response (201 Created)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Database connectivity group",
  "description": "Related database connectivity alerts",
  "severity": "high",
  "status": "new",
  "assignee": "engineer@example.com",
  "created_by": "creator@example.com",
  "created_time": "2026-06-17T10:30:00Z",
  "start_time": null,
  "end_time": null,
  "last_seen_time": null,
  "last_firing_time": null,
  "alerts_count": 0,
  "affected_services": [],
  "rule_fingerprint": "",
  "group_alert_type": "manual"
}

Mock Call

curl -X POST "https://{{api_endpoint}}/group-alerts" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Database connectivity group",
    "description": "Related database connectivity alerts",
    "severity": "high",
    "assignee": "engineer@example.com",
    "status": "new"
  }'

2. Get All Group Alerts

Method: GET Path: /group-alerts Description: Retrieves a list of group alerts with optional sorting, pagination, and CEL filtering.

Parameters

Parameter Type Description
limit integer Maximum number of group alerts to return. Default: 100.
offset integer Number of group alerts to skip. Default: 0.
sorting string Sort field, for example -created_time, created_time, -last_seen_time, or last_seen_time.
cel string Optional CEL filter.

Response (200 OK)

{
  "limit": 25,
  "offset": 0,
  "count": 1,
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Database connectivity group",
      "description": "Related database connectivity alerts",
      "severity": "high",
      "status": "firing",
      "assignee": "engineer@example.com",
      "created_by": "creator@example.com",
      "created_time": "2026-06-17T10:30:00Z",
      "start_time": "2026-06-17T10:25:00Z",
      "last_seen_time": "2026-06-17T10:35:00Z",
      "last_firing_time": "2026-06-17T10:35:00Z",
      "alerts_count": 2,
      "affected_services": ["database"],
      "rule_id": "660f9511-f3ac-52e5-b827-557766551001",
      "rule_fingerprint": "database-service",
      "group_alert_type": "rule"
    }
  ]
}

Mock Call

curl -X GET "https://{{api_endpoint}}/group-alerts?limit=25&offset=0&sorting=-created_time"

3. Get Group Alert

Method: GET Path: /group-alerts/{group_alert_id} Description: Retrieves detailed information about a specific group alert.

Parameters

Parameter Type Description
group_alert_id string UUID of the group alert.

Response (200 OK)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Database connectivity group",
  "description": "Related database connectivity alerts",
  "severity": "high",
  "status": "firing",
  "assignee": "engineer@example.com",
  "created_by": "creator@example.com",
  "created_time": "2026-06-17T10:30:00Z",
  "start_time": "2026-06-17T10:25:00Z",
  "last_seen_time": "2026-06-17T10:35:00Z",
  "last_firing_time": "2026-06-17T10:35:00Z",
  "alerts_count": 2,
  "affected_services": ["database"],
  "rule_fingerprint": "database-service",
  "group_alert_type": "rule"
}

Mock Call

curl -X GET "https://{{api_endpoint}}/group-alerts/550e8400-e29b-41d4-a716-446655440000"

4. Update Group Alert

Method: PUT Path: /group-alerts/{group_alert_id} Description: Updates an existing group alert with new information.

Parameters

Parameter Type Description
group_alert_id string UUID of the group alert.

Request Body

{
  "name": "Database connectivity group",
  "description": "Related database connectivity alerts",
  "severity": "high",
  "assignee": "engineer@example.com",
  "status": "resolved"
}

Response (200 OK)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Database connectivity group",
  "description": "Related database connectivity alerts",
  "severity": "high",
  "status": "resolved",
  "assignee": "engineer@example.com",
  "last_updated_time": "2026-06-17T11:30:00Z"
}

Mock Call

curl -X PUT "https://{{api_endpoint}}/group-alerts/550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Database connectivity group",
    "status": "resolved",
    "description": "Related database connectivity alerts"
  }'

5. Delete Group Alert

Method: DELETE Path: /group-alerts/{group_alert_id} Description: Deletes a group alert.

Parameters

Parameter Type Description
group_alert_id string UUID of the group alert.

Response (200 OK)

{
  "message": "Group alert deleted successfully"
}

Mock Call

curl -X DELETE "https://{{api_endpoint}}/group-alerts/550e8400-e29b-41d4-a716-446655440000"

6. Change Group Alert Status

Method: POST Path: /group-alerts/status Description: Changes the status of one or more group alerts.

Parameters

No path parameters. The request body contains the target group alert IDs.

Request Body

{
  "group_alert_ids": ["550e8400-e29b-41d4-a716-446655440000"],
  "status": "resolved",
  "comment": "All sub-alerts are resolved.",
  "with_alerts": false,
  "operator": null
}

Response (200 OK)

{
  "status": "ok"
}

Mock Call

curl -X POST "https://{{api_endpoint}}/group-alerts/status" \
  -H "Content-Type: application/json" \
  -d '{
    "group_alert_ids": ["550e8400-e29b-41d4-a716-446655440000"],
    "status": "resolved",
    "with_alerts": false
  }'

7. Acknowledge Group Alert

Method: POST Path: /group-alerts/assign Description: Assigns one or more group alerts. Assignment sets group alert status to acknowledged.

Parameters

No path parameters. The request body contains the target group alert IDs.

Response (200 OK)

{
  "status": "ok"
}

Mock Call

curl -X POST "https://{{api_endpoint}}/group-alerts/assign" \
  -H "Content-Type: application/json" \
  -d '{
    "group_alert_ids": ["550e8400-e29b-41d4-a716-446655440000"],
    "assignee": "engineer@example.com"
  }'

8. Get Group Alert Sub-Alerts

Method: GET Path: /group-alerts/{group_alert_id}/alerts Description: Retrieves sub-alerts associated with a group alert.

Parameters

Parameter Type Description
group_alert_id string UUID of the group alert.
limit integer Maximum number of sub-alerts to return. Default: 25.
offset integer Number of sub-alerts to skip. Default: 0.

Response (200 OK)

{
  "limit": 25,
  "offset": 0,
  "count": 1,
  "items": [
    {
      "fingerprint": "alert-fingerprint-1",
      "name": "Database connection failed",
      "status": "firing",
      "severity": "critical",
      "source": ["prometheus"],
      "service": "database",
      "lastReceived": "2026-06-17T10:35:00Z"
    }
  ]
}

Mock Call

curl -X GET "https://{{api_endpoint}}/group-alerts/550e8400-e29b-41d4-a716-446655440000/alerts?limit=25&offset=0"

9. Associate Alerts to Group Alert

Method: POST Path: /group-alerts/{group_alert_id}/alerts Description: Associates one or more alerts with a group alert.

Parameters

Parameter Type Description
group_alert_id string UUID of the group alert.

Request Body

[
  "alert-fingerprint-1",
  "alert-fingerprint-2"
]

Response (202 Accepted)

No response body.

Mock Call

curl -X POST "https://{{api_endpoint}}/group-alerts/550e8400-e29b-41d4-a716-446655440000/alerts" \
  -H "Content-Type: application/json" \
  -d '["alert-fingerprint-1", "alert-fingerprint-2"]'

10. Remove Alerts from Group Alert

Method: DELETE Path: /group-alerts/remove-alerts Description: Removes alerts from one or more group alerts.

Parameters

No path parameters. The request body contains the source group alert IDs and alert fingerprints.

Request Body

[
  {
    "source_group_alert_id": "550e8400-e29b-41d4-a716-446655440000",
    "alert_fingerprints": ["alert-fingerprint-1"]
  }
]

Response (202 Accepted)

No response body.

Mock Call

curl -X DELETE "https://{{api_endpoint}}/group-alerts/remove-alerts" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "source_group_alert_id": "550e8400-e29b-41d4-a716-446655440000",
      "alert_fingerprints": ["alert-fingerprint-1"]
    }
  ]'

11. Get Group Alert Workflows

Method: GET Path: /group-alerts/{group_alert_id}/workflows Description: Retrieves workflow executions related to a group alert and its associated alerts.

Parameters

Parameter Type Description
group_alert_id string UUID of the group alert.
limit integer Maximum number of workflow executions to return. Default: 25.
offset integer Number of workflow executions to skip. Default: 0.

Response (200 OK)

{
  "limit": 25,
  "offset": 0,
  "count": 1,
  "items": [
    {
      "id": "workflow-execution-id",
      "workflow_id": "workflow-id",
      "status": "success"
    }
  ],
  "alerts": {
    "550e8400-e29b-41d4-a716-446655440000": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Database connectivity group",
      "status": "firing"
    }
  }
}

Mock Call

curl -X GET "https://{{api_endpoint}}/group-alerts/550e8400-e29b-41d4-a716-446655440000/workflows?limit=25&offset=0"

12. Merge Group Alerts

Method: POST Path: /group-alerts/merge Description: Merges source group alerts into a destination group alert.

Request Body

{
  "source_group_alert_ids": [
    "660f9511-f3ac-52e5-b827-557766551001"
  ],
  "destination_group_alert_id": "550e8400-e29b-41d4-a716-446655440000"
}

Response (200 OK)

{
  "merged_group_alert_ids": [
    "660f9511-f3ac-52e5-b827-557766551001"
  ],
  "failed_group_alert_ids": [],
  "destination_group_alert_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "1 group alert merged successfully"
}

Mock Call

curl -X POST "https://{{api_endpoint}}/group-alerts/merge" \
  -H "Content-Type: application/json" \
  -d '{
    "source_group_alert_ids": [
      "660f9511-f3ac-52e5-b827-557766551001"
    ],
    "destination_group_alert_id": "550e8400-e29b-41d4-a716-446655440000"
  }'

Common Error Responses

400 Bad Request

{
  "detail": "No alert fingerprints provided"
}

404 Not Found

{
  "detail": "Group alert not found"
}

409 Conflict

{
  "detail": "Cannot resolve the group alert because it contains unresolved alerts."
}

422 Unprocessable Entity

{
  "detail": "Validation error"
}

500 Internal Server Error

{
  "detail": "Internal server error occurred"
}