> ## Documentation Index
> Fetch the complete documentation index at: https://docs.befailproof.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Alertas

> Documentación de Alertas de AgentEye.

Las alertas evalúan una regla según un calendario y te notifican cuando algo supera un umbral. Transforman AgentEye de un panel pasivo en una superficie de avisos para lo que realmente importa: picos en la tasa de errores, regresiones de latencia, caídas en la puntuación de los evaluadores o cualquier evento personalizado que puedas expresar como una consulta ClickHouse.

Esta guía cubre qué es una alerta, los cinco tipos de disparadores, los cuatro canales de notificación, el ciclo de vida de los incidentes y los controles operativos disponibles.

<img src="https://mintcdn.com/exosphere/RgxYS1UZshqb4m7m/agenteye/images/alerts.png?fit=max&auto=format&n=RgxYS1UZshqb4m7m&q=85&s=52dc18ed19b4e78604c20b5608994840" alt="La página de Alertas: una cuadrícula de tarjetas de reglas de alerta, cada una mostrando su tipo de disparador, ventana de evaluación, canales y una insignia de severidad informativa/advertencia/crítica" width="2880" height="1800" data-path="agenteye/images/alerts.png" />

***

## Conceptos

* **Alerta** — la regla. Define *qué* verificar (el disparador), *con qué frecuencia* (el intervalo de evaluación), *cuándo considerarla rota* (lógica compuesta), *qué tan urgente es* (severidad) y *a quién/dónde notificar* (canales).
* **Incidente** — lo que ocurre cuando una alerta se activa. Una alerta tiene como máximo un incidente abierto a la vez. Los incidentes repetidos actualizan la evidencia del mismo incidente. Los incidentes son resueltos por un operador; la resolución automática cuando la regla deja de activarse está planificada pero aún no está habilitada.
* **Canal** — hacia dónde va una notificación: correo electrónico, Slack, webhook genérico o en el panel. Cada alerta puede combinar cualquier combinación de estos.

Las alertas e incidentes pertenecen a una organización y se comparten entre los operadores de esa organización (en un despliegue de inquilino único, esto es simplemente la organización `default` integrada). Los incidentes pueden asignarse a un operador individual para su triaje.

***

## Tipos de disparadores

Cinco tipos, cada uno con su propia especificación JSON. Elige el que mejor describa lo que significa "roto" en tu contexto. El formulario de **nueva alerta** del panel construye la misma especificación, adaptando el editor de condiciones al tipo de disparador que selecciones:

<img src="https://mintcdn.com/exosphere/RgxYS1UZshqb4m7m/agenteye/images/alert-new.png?fit=max&auto=format&n=RgxYS1UZshqb4m7m&q=85&s=ee2fb0c2b98bbdc3602bb568751c5cc4" alt="El formulario de nueva alerta, con los campos básicos (nombre, descripción, habilitado) y el selector de disparador mostrando las opciones: umbral de métrica, SQL personalizado, puntuación de evaluación, fallos de evaluación y por evento" width="2880" height="1800" data-path="agenteye/images/alert-new.png" />

### 1. `metric_threshold`

El más sencillo. Elige una métrica de una lista cerrada, un operador, un umbral y una ventana de tiempo.

| Métrica          | Qué cuenta                                                              |
| ---------------- | ----------------------------------------------------------------------- |
| `event_count`    | Total de eventos en la ventana                                          |
| `error_count`    | `event_type = 'error'` O cualquier evento con `error_type IS NOT NULL`  |
| `error_rate`     | `error_count / event_count` (0..1)                                      |
| `p95_latency_ms` | `quantile(0.95)(duration_ms)` sobre todos los eventos con `duration_ms` |
| `p99_latency_ms` | `quantile(0.99)(duration_ms)`                                           |
| `token_sum`      | `SUM(input_tokens + output_tokens)`                                     |

Operadores: `>`, `>=`, `<`, `<=`, `==` (también aceptados como `gt`, `gte`, `lt`, `lte`, `eq`).

Filtros opcionales: `environment`, `event_type`.

Ejemplo de especificación:

```json theme={null}
{
  "metric": "p95_latency_ms",
  "filter": { "environment": "production" },
  "window_secs": 900,
  "op": ">",
  "value": 5000
}
```

### 2. `custom_sql`

Para cualquier cosa que las métricas predefinidas no cubran. El SQL proporcionado por el operador pasa por el mismo guardia `/queries/run` (solo SELECT/WITH, una única sentencia, límite de 10 000 filas) antes de que el despachador lo ejecute. Dos modos:

* **modo rows** (sin `op`/`value`): la alerta se activa en cuanto la consulta devuelve al menos una fila.
* **modo value**: la consulta debe tener un alias de columna llamado `metric_value`; el despachador compara el `metric_value` de la primera fila con `value` usando `op`.

```json theme={null}
{
  "sql": "SELECT count() AS metric_value FROM agenteye.events WHERE event_type = 'error' AND ts >= now() - INTERVAL 1 HOUR",
  "op": ">",
  "value": 100
}
```

### 3. `evaluation_score`

Lee `agenteye.evaluations` y compara el promedio de una puntuación con nombre a lo largo de una ventana de tiempo.

```json theme={null}
{
  "score_key": "hallucination",
  "op": ">",
  "value": 0.6,
  "window_secs": 3600,
  "min_count": 5,
  "environment": "production"
}
```

`min_count` protege contra valores atípicos de una sola muestra; el despachador no se activará hasta que existan al menos N evaluaciones en la ventana.

### 4. `eval_compound`

Detecta una regresión de calidad que solo se manifiesta en *varias* puntuaciones de evaluadores a la vez. Mientras que `evaluation_score` monitorea una única puntuación con nombre, `eval_compound` compone múltiples condiciones de puntuación de evaluación en una sola alerta y combina sus resultados con una lógica elegida; así una sola regla puede expresar "activar si la utilidad cae **o** la alucinación sube", "activar solo si la utilidad **y** la eficiencia de herramientas caen ambas", o "activar si **al menos 2 de** estas tres comprobaciones superan el umbral".

Cada condición lee el promedio de una puntuación con nombre de `agenteye.evaluations` sobre la ventana compartida y la evalúa con su propio operador y umbral. Los resultados booleanos se combinan luego mediante el `combinator`:

| Combinador          | Lógica | Se activa cuando                         |
| ------------------- | ------ | ---------------------------------------- |
| `"any"`             | O      | al menos una condición supera el umbral  |
| `"all"`             | Y      | todas las condiciones superan el umbral  |
| `{ "at_least": N }` | M de N | al menos N condiciones superan el umbral |

```json theme={null}
{
  "combinator": { "at_least": 2 },
  "conditions": [
    { "score_key": "hallucination", "op": ">", "value": 0.8 },
    { "score_key": "helpfulness",   "op": "<", "value": 0.6 },
    { "score_key": "tone",          "op": "<", "value": 0.5 }
  ],
  "window_secs": 3600,
  "min_count": 5,
  "environment": "production"
}
```

* `combinator`: `"any"`, `"all"` o `{ "at_least": N }`.
* `conditions[]`: cada una como `{ score_key, op, value }`, usando los mismos operadores que los demás disparadores (`>`, `>=`, `<`, `<=`, `==`).
* `window_secs`: el período de retrospección compartido que se aplica a cada condición (por defecto `3600`).
* `min_count`: número mínimo de evaluaciones por condición antes de que esa condición pueda superar el umbral; una condición con muy pocas muestras en la ventana se cuenta como "no superada" (por defecto `1`).
* `environment`: opcional; restringe cada condición a un entorno.

La evidencia de la notificación registra el promedio observado de cada condición, el umbral contra el que se evaluó, cuántas evaluaciones se vieron y si superó el umbral; así puedes ver exactamente qué comprobaciones se activaron.

### 5. `per_event`

Para alertas del tipo "ha llegado algún evento que coincide con X". Sin agregación; el despachador se activa en cuanto detecta una coincidencia en la ventana de retrospección.

```json theme={null}
{
  "event_type": "error",
  "lookback_secs": 60,
  "environment": "production",
  "tool_name": "bash",
  "agent_id": "caa",
  "error_type": "TimeoutError",
  "message_contains": "vertex-ai timed out"
}
```

Todos los filtros se combinan con AND; cualquier campo que omitas queda sin restricción.

| Campo              | Propósito                                                                                                                                                                                                                                                                                             |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_id`         | Restringe a los errores de un agente específico (el que aparece en la fila `/errors`).                                                                                                                                                                                                                |
| `error_type`       | Restringe a una clase de error específica (p. ej. `TimeoutError`) en lugar de todos los errores.                                                                                                                                                                                                      |
| `message_contains` | Coincidencia de subcadena sin distinción entre mayúsculas y minúsculas contra `payload.message`. Útil para capturar un modo de fallo específico (p. ej. `prompt is too long`) sin alertar por cada error del mismo agente. Limitado a 200 caracteres; se compara como cadena literal, no como patrón. |

Consejo: establece `lookback_secs` de forma aproximada al `eval_interval_secs` de la alerta para evitar notificaciones duplicadas del mismo evento.

**Atajo desde `/errors`:** el botón **+ alert** en la fila representativa de cada grupo de errores en la vista de errores (y en el panel de detalle de evento de sesión para un evento de error) abre `/alerts/new` con el disparador `per_event` ya completado con el `event_type` y el entorno de esa fila, incluyendo un nombre derivado del `error_type` del payload cuando está disponible. Aún debes seleccionar los canales y confirmar la retrospección, pero el comparador ya viene rellenado. Los operadores necesitan `alerts:write` para que aparezca el botón.

***

## Lógica compuesta (M de N)

Cada alerta tiene dos controles enteros adicionales al disparador:

* `eval_window`: cuántas evaluaciones recientes considerar (por defecto 1)
* `min_breaches`: cuántas de esas deben superar el umbral antes de que la alerta se active (por defecto 1)

`1 de 1` (el valor por defecto) significa "activar en el primer incumplimiento". `3 de 5` significa "activar cuando la regla haya superado el umbral en 3 de las últimas 5 evaluaciones", útil para señales inestables donde una sola medición incorrecta es ruido. El despachador mantiene un búfer circular por alerta; no tienes que gestionar el estado.

***

## Intervalo de evaluación

`eval_interval_secs` controla con qué frecuencia el despachador ejecuta tu regla. Limitado al rango `[30, 86400]`. Valores predefinidos en el panel: 1m / 5m / 15m / 1h. Elige un intervalo acorde a la velocidad con que cambia la señal subyacente: una alerta de tasa de error de 5 minutos evaluada cada 15 segundos desperdicia CPU; una alerta por evento necesita una retrospección corta o perderá eventos silenciosamente entre ticks.

***

## Canales

Cada alerta puede combinar cualquiera de estos cuatro. Las credenciales por canal (URL del webhook de Slack, URL del webhook genérico + secreto de firma, destinatarios de correo predeterminados) se configuran una sola vez en **`/settings`** y se referencian por clave desde cada alerta. De esta forma, un canal de Slack puede servir a muchas alertas sin que cada una almacene su propia copia de la URL del webhook.

Los tres tipos de canales externos (email, Slack, webhook) también están controlados por un interruptor global a nivel de organización, `alerts.enabled_channels`. Cuando una alerta activada incluye un tipo de canal que no está en este conjunto, el despachador lo omite y registra una fila en `alert_notifications` con estado `skipped_disabled` y destino `<channel_disabled>` (lo que permite pausar globalmente, por ejemplo, todas las entregas de Slack sin editar cada regla). El canal en el panel siempre está habilitado. Consulta [Configuración](#configuration).

### Correo electrónico

Reutiliza el mismo transporte SMTP que envía los correos de inicio de sesión OTP. Los destinatarios se resuelven en este orden:

1. Anulación `recipients[]` por canal (cuando no está vacío).
2. La configuración `alerts.email_default_recipients` (un array de cadenas de correo).

Si SMTP no está configurado, el canal no hace nada; el despachador igualmente registra una fila en `alert_notifications` con destino `<smtp_unconfigured>` para que el registro de auditoría haga visible la mala configuración.

### Slack

Envía un mensaje Block Kit a una [URL de webhook entrante](https://api.slack.com/messaging/webhooks).

* URL predeterminada: `alerts.slack_default_webhook` (configurada en `/settings`).
* Anulación por alerta: establece el `webhook_setting_key` del canal a cualquier otra clave de configuración de tipo URL, p. ej. `alerts.slack_oncall`, `alerts.slack_costs`.

El encabezado incluye un emoji de severidad (`:rotating_light:` / `:warning:` / `:bell:`), y el mensaje incluye un botón con enlace directo a la página del incidente.

### Webhook genérico

Una integración JSON-POST para PagerDuty, Opsgenie o tu propio endpoint de ingesta. Estructura del cuerpo:

```json theme={null}
{
  "schema_version": 1,
  "event": "alert.firing",
  "incident": { "id": "uuid", "url": "https://…/incidents/uuid", "opened_at": "2026-…" },
  "alert":    { "id": "uuid", "name": "errors > 50/hr", "severity": "critical" },
  "breach":   { "value": 73.0, "summary": "ErrorCount > 50 (observed 73.000)", "evidence": { … } }
}
```

Cuando `alerts.webhook_signing_secret` está configurado, la solicitud incluye un encabezado `X-AgentEye-Signature: sha256=<hex>`, el HMAC-SHA256 del cuerpo usando el secreto. Verifícalo en el lado receptor antes de confiar en el payload.

El encabezado `X-AgentEye-Event` contiene `alert.firing` / `alert.test`. (`alert.resolved` está reservado para la función de resolución automática planificada y actualmente no se emite.)

### En el panel

Sin entrega externa: la alerta simplemente escribe una fila en `alert_notifications` que la página de incidentes del panel muestra. Útil mientras ajustas una regla y no quieres saturar un sistema externo, o para alertas de baja urgencia que los operadores revisan durante el triaje habitual.

***

## Ciclo de vida del incidente

```
firing  ──ack──▶  acknowledged
   │                    │
   └────────────────────┴───── operator resolve ───▶  resolved
```

* **firing** — el despachador acaba de abrir el incidente o detectó otro incumplimiento. La notificación de activación se envía exactamente una vez (controlada por la marca de tiempo `notified_firing_at` del incidente).
* **acknowledged** — un operador presionó *ack* en `/incidents/:id`. El incidente sigue considerándose abierto; los incumplimientos posteriores actualizarán su evidencia sin volver a notificar.
* **resolved** — un operador presionó *resolve*. La resolución automática cuando la regla deja de incumplir está planificada pero aún no está habilitada, por lo que un incidente abierto permanece abierto hasta que un operador lo resuelva.

Un nuevo incidente puede reabrirse en la misma alerta en cualquier momento después de que el anterior haya sido resuelto.

**Línea de tiempo de actividad.** Cada acción sobre un incidente — apertura, reconocimiento, resolución — se registra en un registro de actividad de solo adición y se muestra en la línea de tiempo de *actividad* del incidente, con cada entrada atribuida al operador que la realizó (por correo electrónico) o a **automated** para las acciones que el despachador tomó por su cuenta (apertura automática ante un incumplimiento). El reconocimiento es compartido: varios operadores pueden reconocer el mismo incidente y cada uno aparece como una entrada separada y atribuida.

La bandeja de entrada de **Incidents** agrupa los incidentes abiertos por estado y permite filtrar por severidad y responsable asignado:

<img src="https://mintcdn.com/exosphere/RgxYS1UZshqb4m7m/agenteye/images/incidents.png?fit=max&auto=format&n=RgxYS1UZshqb4m7m&q=85&s=2ec7841f0329c15974b52a761b6dcdcc" alt="La bandeja de entrada de Incidents mostrando tarjetas de incidentes vinculados a alertas y ad-hoc con insignias de severidad y responsables asignados" width="2880" height="1800" data-path="agenteye/images/incidents.png" />

Al abrir un incidente se muestra la evidencia del incumplimiento, los responsables asignados y suscriptores, la línea de tiempo de actividad atribuida y un hilo de comentarios:

<img src="https://mintcdn.com/exosphere/RgxYS1UZshqb4m7m/agenteye/images/incident-detail.png?fit=max&auto=format&n=RgxYS1UZshqb4m7m&q=85&s=d49c0814049fa568eca794743ceea7e3" alt="Vista de detalle de un incidente: la alerta padre, resumen del incumplimiento, responsables asignados, suscriptores, registro de actividad atribuido y conversación" width="2880" height="1800" data-path="agenteye/images/incident-detail.png" />

***

## Permisos requeridos

La creación de *reglas* de alerta y el triaje de *incidentes* son responsabilidades separadas con permisos distintos, de modo que puedes dar a una rotación de guardia acceso a incidentes sin también otorgarle la capacidad de reescribir las reglas.

* `alerts:read`: ver reglas de alerta.
* `alerts:write`: crear, editar, eliminar reglas de alerta y disparar una notificación de prueba.
* `incidents:read`: ver incidentes.
* `incidents:write`: abrir incidentes manuales (ad-hoc) que no están vinculados a una alerta.
* `incidents:ack`: reconocer, asignar, comentar y resolver incidentes.

> **`alerts:ack` heredado.** Las claves y operadores a los que se otorgó el token anterior `alerts:ack` siguen funcionando: se trata como `incidents:ack` (e implica `incidents:read`), por lo que los operadores de guardia existentes mantienen su acceso sin necesidad de reemitir credenciales. Emite nuevos permisos con la familia `incidents:*`.

Otorga permisos en claves de API (`POST /keys`) y operadores (`PUT /users/:id`). El `PermGate` del panel bloquea los botones relevantes cuando falta un permiso; verás `// 403` junto a la acción.

> **Selector de destinatarios de correo.** El selector de destinatarios del editor de alertas lista los miembros de tu organización para que puedas seleccionarlos por nombre. Se carga para cualquier operador que tenga `alerts:read` o `alerts:write`; ver el directorio de tu equipo para este fin **no** requiere `users:read`, y el selector devuelve únicamente las direcciones de correo de los miembros, nunca registros de usuario completos.

***

## Configuración

Variables de entorno consumidas por el despachador:

| Variable                   | Por defecto                  | Propósito                                                                                                         |
| -------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `ALERT_WORKERS`            | `1`                          | Tareas de worker por instancia de servidor. La mayoría de los despliegues solo necesitan una.                     |
| `ALERT_CLAIM_BATCH`        | `16`                         | Máximo de alertas que procesa un único tick de worker.                                                            |
| `ALERT_POLL_IDLE_SECS`     | `5`                          | Tiempo de espera del worker cuando la cola está vacía.                                                            |
| `ALERT_REQUEST_TIMEOUT_MS` | `15000`                      | Tiempo límite de evaluación por disparador.                                                                       |
| `DASHBOARD_URL`            | `https://app.befailproof.ai` | Origen usado para construir el enlace mágico al incidente en las notificaciones. Establécelo al host de tu panel. |

Tras un fallo de evaluación transitorio (ClickHouse inaccesible, tiempo límite de consulta), el despachador reintenta la regla con retroceso exponencial. Una vez que una regla acumula **5** fallos transitorios consecutivos, se reprograma a su cadencia normal en lugar de continuar el retroceso, de modo que una regla que falla persistentemente sigue siendo reevaluada. Este límite es fijo y no es configurable por el operador.

Configuraciones de canal (administradas desde `/settings`, no como variables de entorno):

* `alerts.email_default_recipients` (`email_list`): array JSON de cadenas de correo — los destinatarios predeterminados para los canales de correo.
* `alerts.slack_default_webhook` (`url`): URL de webhook entrante de Slack por defecto.
* `alerts.webhook_default_url` (`url`): URL de webhook genérico por defecto.
* `alerts.webhook_signing_secret` (`secret`): clave HMAC-SHA256. Siempre se devuelve como `""` en la respuesta GET; escribe un nuevo valor para rotarla.
* `alerts.enabled_channels` (`channel_set`): el conjunto global de tipos de canales externos que se despachan cuando se activa una alerta; por defecto incluye los tres (`email`, `slack`, `webhook`). Eliminar un tipo aquí suprime globalmente ese canal para cada alerta sin editar cada regla. El canal en el panel siempre se entrega y no se ve afectado por esta configuración.

***

## Verificar una nueva alerta

Antes de confiar en una nueva alerta:

1. Guárdala como habilitada con al menos un canal de notificación.
2. Selecciona **test** en la página de detalle de la alerta y confirma que cada destino configurado recibe la notificación sintética.
3. Tras el primer incumplimiento real, confirma que el incidente aparece en **Incidents** y que el valor medido coincide con la consulta correspondiente en el panel.

Los incidentes no se resuelven automáticamente cuando una condición se normaliza. Un operador debe resolverlos desde la página de detalle del incidente.

***

## Solución de problemas

| Síntoma                              | Causa probable                                                                                                                                                                                                                                                                                                                                    |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| La alerta nunca se activa            | `enabled = false`, o no hay canales adjuntos, o la consulta CH subyacente devuelve 0 filas. Usa *test* para confirmar los canales; usa `/queries/run` para confirmar la métrica.                                                                                                                                                                  |
| Falta la notificación de Slack       | `alerts.slack_default_webhook` (o la clave de anulación por alerta) no está configurada: verifica en `alert_notifications.target` si hay filas con `<unset:…>`; o el tipo `slack` está deshabilitado globalmente en `alerts.enabled_channels`: busca filas en `alert_notifications` con estado `skipped_disabled` y destino `<channel_disabled>`. |
| Webhook genérico con 401             | El receptor está requiriendo una firma pero `alerts.webhook_signing_secret` no está configurado. Verifica en el receptor que el HMAC coincida con `hmac_sha256(secret, body)`.                                                                                                                                                                    |
| Correo con "from all sends failed"   | Las credenciales SMTP son incorrectas o tu relay rechaza la dirección `from`. Es la misma superficie que envía los correos OTP: si esos funcionan, el transporte SMTP está bien.                                                                                                                                                                  |
| El incidente se reabre repetidamente | Los controles compuestos son demasiado agresivos: intenta aumentar `min_breaches` o `eval_window` para que los picos transitorios no vuelvan a abrir incidentes que ya resolviste.                                                                                                                                                                |
