---
name: drift-detection
version: 1.0.0
herramientasPermitidas: [Read]
description: "Detección de drift de métricas de agentes/skills via ventana deslizante (7d) vs baseline (4 semanas). Integrado al operador Reflect del ciclo AGP."
exclusiones:
  - "No cargar para detectar drift en código de aplicación (regressions de negocio); este skill detecta drift en métricas de uso de agentes/skills del sistema SWL."
  - "No cargar si hay menos de 7 días de trazas en `.planning/traces/`; el baseline de 4 semanas no existirá y los resultados serán estadísticamente inválidos."
  - "No cargar para observabilidad de la aplicación en producción; usar `monitoring-alertas` para eso."
  - "No cargar manualmente cuando el ciclo AGP ya ejecuta `ejecutarDriftAutomatico()` en cada SubagentStop — invocarlo dos veces dobla el trabajo sin beneficio."
author: swl-ses
evolvable: true
evolvable_scope: [content, examples]
domain: ingeniería-de-software
tags: [agp, observabilidad, drift, métricas, ciclo-agp]
---
# drift-detection — Detección de drift para el ciclo AGP

Algoritmo de ventana deslizante que compara métricas recientes (últimos 7 días)
contra un baseline histórico (4 semanas previas) para detectar degradación de
skills y agentes antes de que el health score lo refleje.

Adaptado de `runDriftCheck`/`getDriftTimeline` en mission-control-main (MIT).

## Módulo principal

`scripts/lib/drift-detector.js` — zero-deps, Node stdlib únicamente.

## Cuándo se activa

- Manual: desde cualquier script o agente que necesite análisis de degradación.
- Automático (vía `hooks/lib/etapa-auto-evolucion.js`): el hook ejecuta `ejecutarDriftAutomatico()`
  en cada `SubagentStop` con un throttle de 24 h (configurable con `SWL_DRIFT_THROTTLE_H`).
  Se evalúan los últimos 5 archivos JSONL de trazas. Opt-out total con `SWL_DRIFT_DISABLED=1`.
- Automático (vía ciclo AGP): `hooks/lib/reflect-classifier.js` expone
  `ejecutarDriftReflect(rutaJsonl, nombreAgente)` como punto de extensión alternativo.
- Las trazas de agentes viven en `.planning/traces/YYYY-MM-DD.jsonl`.

## Exports del módulo

```js
const { detectarDrift, generarLineaTemporal } = require('./scripts/lib/drift-detector');
```

### `detectarDrift(opts)`

```js
detectarDrift({
  rutaJsonl:           '/ruta/a/traces.jsonl',
  ventanaDias:         7,       // default
  baselineSemanasBase: 4,       // default
  umbralPct:           10,      // warn si |drift| > 10%
  metricas:            ['tokens_promedio_por_sesion', 'tasa_exito_tool', 'tasa_finalizacion_tarea'],
  agente:              'backend-python-swl',  // para nudges
})
```

**Retorna:**

```json
{
  "drifts": [
    {
      "metrica": "tokens_promedio_por_sesion",
      "baseline": 1000,
      "reciente": 2100,
      "driftPct": 110.0,
      "estado": "critico"
    },
    {
      "metrica": "tasa_exito_tool",
      "baseline": 0.9,
      "reciente": 0.85,
      "driftPct": -5.56,
      "estado": "ok"
    }
  ],
  "timestamp": "2026-04-19T12:00:00.000Z",
  "estado_global": "critico"
}
```

**Estados por métrica:**

| Condición | Estado |
|-----------|--------|
| `abs(driftPct) <= umbralPct` | `ok` |
| `abs(driftPct) > umbralPct` | `warn` |
| `abs(driftPct) > umbralPct * 2` | `critico` |

**Estado global:** el peor estado entre todas las métricas.

### `generarLineaTemporal(opts)`

```js
generarLineaTemporal({
  rutaJsonl: '/ruta/a/traces.jsonl',
  dias:      30,   // default
})
// → [{ fecha: '2026-04-19', tokens: 3200, exitos: 12, fallos: 1 }, ...]
```

Retorna un array de longitud `dias`, uno por día, para visualización en
el dashboard o en reportes de salud.

## Estructura de eventos esperada en el JSONL

El módulo es permisivo: extrae métricas de cualquier campo que coincida.

| Métrica | Campos aceptados en el evento |
|---------|-------------------------------|
| Tokens | `atributos.tokens_totales`, `tokens`, `total_tokens` |
| Éxito de tool | `success`, `exito`, `atributos.success` |
| Finalización de tarea | `estado`, `status`, `atributos.estado` (valores: `OK`, `done`, `completado`) |
| Timestamp | `timestamp`, `ts`, `inicio`, `created_at` |

Formato nativo de `.planning/traces/YYYY-MM-DD.jsonl`:

```json
{"traceId":"...","nombre":"agent:backend-python-swl","inicio":"2026-04-19T10:00:00.000Z","estado":"OK","atributos":{"tokens_totales":1500}}
```

## Integración con el ciclo AGP

El operador Reflect (`hooks/lib/reflect-classifier.js`) expone:

```js
const { ejecutarDriftReflect } = require('./hooks/lib/reflect-classifier');

const resultado = ejecutarDriftReflect(
  '.planning/traces/2026-04-19.jsonl',
  'backend-python-swl'
);
// resultado: { drifts, timestamp, estado_global } o null si módulo no disponible
```

Cuando `estado_global === 'critico'`, el módulo emite automáticamente un nudge
a `.planning/evolution/nudges.jsonl`:

```json
{
  "timestamp": "2026-04-19T12:00:00.000Z",
  "tipo": "drift-detectado",
  "agente_o_skill": "backend-python-swl",
  "metrica": "tokens_promedio_por_sesion",
  "drift_pct": 110.0,
  "estado": "critico"
}
```

El agente `auto-evolucion-swl` consume estos nudges para proponer mejoras.

## Cómo interpretar los resultados

- **`ok`**: métricas estables. Sin acción requerida.
- **`warn`**: degradación moderada. Revisar en el próximo ciclo de evolución.
- **`critico`**: degradación severa. Invocar `/swl:evolucionar` con el agente
  identificado. El nudge ya fue emitido — aparecerá en `/swl:status salud`.

## Cuándo NO cargar

- Se busca detectar regresiones en el código de la aplicación (tests fallando, performance degradada); para eso usar `monitoring-alertas` o el revisor de código correspondiente.
- El proyecto tiene menos de 7 días de trazas — el baseline de 4 semanas no puede calcularse y `detectarDrift` retornará resultados sin significado estadístico.
- El ciclo AGP ya tiene configurado el throttle automático de 24 h; invocar el drift manualmente en el mismo intervalo produce el mismo nudge dos veces sin nuevo valor.

## Gotchas / Errores comunes no obvios

- **Timestamps inválidos en eventos JSONL provocan líneas ignoradas silenciosamente**: el módulo es permisivo con los campos pero si ningún campo de timestamp (`timestamp`, `ts`, `inicio`, `created_at`) tiene una fecha ISO válida, el evento se descarta del cálculo. Causa: eventos generados con timestamps de formato local (ej. `"19/04/2026"`) no pasan la validación. Solución: verificar que todos los eventos JSONL del trace tengan al menos un campo de timestamp en formato ISO 8601 — si el baseline aparece como 0, es el primer síntoma de eventos descartados.
- **Estado `critico` emitido por baseline con muy pocos eventos**: si el baseline de 4 semanas tiene solo 3 eventos y la ventana de 7 días tiene 8, el `driftPct` de tokens se dispara al 166% cuando en realidad el agente está más activo, no degradado. Causa: el módulo no valida que el baseline tenga suficientes eventos para ser estadísticamente válido. Solución: antes de interpretar un estado `critico`, verificar que el baseline tiene al menos 20 eventos — si no, marcar el resultado como `insufficient-data` en lugar de accionar.
- **Nudge duplicado en `.planning/evolution/nudges.jsonl` por falta de deduplicación**: el hook se ejecuta dos veces en el mismo `SubagentStop` (por bug de throttle) y emite el mismo nudge dos veces. Causa: el throttle `SWL_DRIFT_THROTTLE_H` no validó correctamente el timestamp del último run. Solución: el archivo `nudges.jsonl` es append-only — antes de emitir un nudge, verificar si el último evento del mismo `agente_o_skill` y `metrica` tiene un timestamp dentro de la ventana de throttle.
- **`atomicWriteJSON` usado para escribir en nudges.jsonl**: escribir el archivo completo en lugar de hacer append corrompe el historial de nudges previos. Causa: confusión entre archivos de estado mutable (usan `atomicWriteJSON`) y archivos de eventos de alta frecuencia (usan `fs.appendFileSync`). Solución: `nudges.jsonl` es un JSONL de alta frecuencia — siempre usar `fs.appendFileSync(ruta, JSON.stringify(nudge) + '\n')`, nunca reescribir el archivo completo.

## Referencia cruzada

- Módulo: `scripts/lib/drift-detector.js`
- Tests: `tests/lib/drift-detector.test.js`
- Operador Reflect: `hooks/lib/reflect-classifier.js`
- Ciclo AGP: `.planning/evolution/nudges.jsonl`
- Origen (adaptado de): `temp/mission-control-main/src/lib/agent-evals.ts` — MIT
