---
name: guardrail-semantico
description: >
  Patrón de validación pre/post ejecución de agentes con "tripwire" para decidir
  si permitir, degradar modelo o bloquear. Cargar cuando se diseñen hooks PreToolUse
  de validación semántica, cuando se implemente control de costos por nivel de
  complejidad, o cuando se quiera observar si un subagente podría ejecutarse con
  un modelo menos costoso. Basado en el patrón InputGuardrail/OutputGuardrail de
  openai-agents-python.
version: "1.0.0"
herramientasPermitidas: [Read, Write]
exclusiones:
  - "No cargar para guardrails de seguridad (detectar operaciones destructivas, escritura en rutas prohibidas, secretos en output) — eso es `reglas/seguridad-agentes.md` y el hook `risk-scoring`; este skill evalúa complejidad semántica para degradación de modelo, no riesgo operacional."
  - "No cargar si el guardrail objetivo va directamente a modo `blocking` sin datos de observación — el propio skill requiere ≥50 activaciones en modo `observational` antes de considerar `blocking`; cargar el skill para diseñar el guardrail, no para saltarse ese requisito."
  - "No cargar para configurar permisos de tools o allowedTools de un agente — eso es `estructura-proyecto-claude` y `seguridad-agentes`; este skill diseña la lógica de tripwire para degradación de modelo."
  - "No cargar para guardrails sobre agentes con `nivelRiesgo: ALTO` — el Criterio 3 es determinista: esos agentes nunca se degradan; diseñar guardrails para ellos no tiene efecto."
tags: [patron-diseño, costos, modelos, guardrails, agentes]
provenance:
  origin: imported
  created_at: "2026-04-19"
  confidence: 0.75
  author: saul.wade@gmail.com
---
# Guardrail Semántico — Validación con tripwire para agentes

## Propósito

Un guardrail semántico es una capa de validación que se ejecuta **antes o después**
de la tarea de un agente para decidir si proceder, degradar el modelo, o bloquear.
El "tripwire" es la condición booleana que activa la respuesta del guardrail.

A diferencia de los guardrails de seguridad (que bloquean operaciones peligrosas),
el guardrail semántico evalúa la **complejidad semántica** del input para decidir
si se justifica el costo del modelo seleccionado.

## Cuándo cargar este skill

- Al diseñar hooks `PreToolUse` que evalúan prompts antes de invocar un subagente.
- Al implementar degradación de modelo basada en complejidad de tarea.
- Al revisar si `guardrail-modelo.js` necesita nuevos criterios de tripwire.
- Al analizar logs de `.planning/evolution/guardrail-observaciones.jsonl`.

---

## Componentes del patrón

### GuardrailFunctionOutput

Estructura de retorno de toda función de guardrail:

```js
// GuardrailFunctionOutput — contrato de retorno
{
  tripwire_triggered: boolean,  // true = aplicar acción (degradar/bloquear)
  output_info: any,             // datos de análisis para diagnóstico
  metadata: {
    razon: string,              // descripción legible del criterio activado
    confidence: number,         // 0.0-1.0 de certeza del tripwire
    modelo_sugerido: string,    // 'haiku' | 'sonnet' | 'opus' | null
  }
}
```

### InputGuardrail (pre-ejecución)

Se ejecuta **antes** de que el agente reciba su tarea. Analiza el prompt del
subagente para decidir si la complejidad justifica el modelo seleccionado.

```js
// Pseudocódigo del patrón InputGuardrail
function inputGuardrail(promptTexto, agenteConfig) {
  const analisis = analizarComplejidad(promptTexto);
  const nivelRiesgo = agenteConfig.nivelRiesgo || 'MEDIO';

  // override: agentes de alto riesgo nunca se degradan
  if (nivelRiesgo === 'ALTO') {
    return { tripwire_triggered: false, output_info: analisis, metadata: { razon: 'override-alto-riesgo' } };
  }

  const tripwire = analisis.esTrivial || analisis.tienePalabrasLigeras;
  return {
    tripwire_triggered: tripwire,
    output_info: analisis,
    metadata: {
      razon: analisis.razon,
      confidence: analisis.confidence,
      modelo_sugerido: tripwire ? 'haiku' : null,
    }
  };
}
```

### OutputGuardrail (post-ejecución)

Se ejecuta **después** de que el agente entregó su resultado. Evalúa la calidad
del output para retroalimentar el sistema de confianza.

```js
// Pseudocódigo del patrón OutputGuardrail
function outputGuardrail(resultado, agenteConfig) {
  const problemas = detectarProblemas(resultado);
  return {
    tripwire_triggered: problemas.length > 0,
    output_info: { problemas },
    metadata: { razon: problemas.join('; '), confidence: 0.8 }
  };
}
```

### Modos de ejecución

| Modo | Comportamiento | Cuándo usar |
|------|---------------|-------------|
| `blocking` | Síncrono. Si tripwire activa, aborta la tarea. | Validaciones de seguridad críticas |
| `run_in_parallel` | No bloquea. Registra el tripwire sin interrumpir. | Observación de patrones, primera versión de guardrails nuevos |
| `observational` | Solo registra. Nunca actúa. | Acumulación de datos pre-activación |

**Regla de introducción**: todo guardrail nuevo DEBE iniciar en modo `observational`
o `run_in_parallel` durante al menos 50 activaciones antes de activar el modo
`blocking`. Esto previene falsos positivos en producción sin datos.

---

## Criterios propuestos de tripwire para degradación de modelo

### Criterio 1 — Tarea trivial por longitud

**Señal**: prompt < 200 caracteres Y sin palabras clave de decisión crítica.
**Acción**: sugerir degradación Opus → Sonnet, Sonnet → Haiku.
**Confianza**: 0.6 (longitud no garantiza trivialidad).

```js
const esTrivialPorLongitud = (texto) =>
  texto.length < 200 &&
  !/arquitectura|seguridad|diseño|decisión|riesgo|critico|irreversible/i.test(texto);
```

### Criterio 2 — Palabras clave de tarea ligera

**Señal**: el prompt contiene verbos de operación determinista.
**Acción**: forzar degradación a Haiku si el agente no es ALTO riesgo.
**Confianza**: 0.75.

```js
const PALABRAS_TAREA_LIGERA = /\b(lista|formatea|muestra|busca|cuenta|consulta|renombra|copia|mueve)\b/i;
const esTareaLigera = (texto) => PALABRAS_TAREA_LIGERA.test(texto);
```

### Criterio 3 — Override por nivelRiesgo ALTO

**Señal**: el frontmatter del agente invocado declara `nivelRiesgo: ALTO`.
**Acción**: NUNCA degradar. Ignorar criterios 1 y 2.
**Confianza**: 1.0 (regla determinista).

Este criterio protege a: `orquestador-swl`, `arquitecto-swl`, `revisor-seguridad-swl`,
`producto-prd-swl` y cualquier agente con decisiones irreversibles de arquitectura.

### Criterio 4 — Agente ya en modelo mínimo

**Señal**: el agente invocado ya usa `model: haiku` o `model: claude-haiku-*`.
**Acción**: no aplicar guardrail (no hay degradación posible).
**Confianza**: 1.0.

---

## Casos donde NO aplicar

- Agentes con `model: claude-opus-4-7` explícito en frontmatter y `nivelRiesgo: ALTO`.
- Input que ya pasó por otro guardrail en la misma cadena de delegación.
- Sesiones de research abierto (`/swl:discutir-fase`, `/swl:brainstorm`) donde
  la amplitud del análisis es el objetivo.
- Tareas con keywords mixtas donde criterio 1 y criterio 2 se contradicen:
  en caso de conflicto, NO degradar (conservador).

---

## Anti-patrones

### Degradación silenciosa

El guardrail que baja el modelo sin notificar al usuario es una violación del
principio de "no fallback silencioso" (`reglas/seguridad-agentes.md`). Siempre
emitir un nudge visible antes o durante la degradación.

```
MAL:  modificar silenciosamente el modelo en el payload sin registrar
BIEN: emitir nudge + registrar en guardrail-observaciones.jsonl + retornar exit 0
```

### Tripwire que degrada agentes `model: inherit`

Los agentes con `model: inherit` reciben el modelo del padre. Degradarlos en el
guardrail rompería la cadena de herencia intencional del orquestador. El hook
`guardrail-modelo.js` DEBE verificar esto antes de aplicar cualquier criterio.

### Activar blocking sin datos de observación

Pasar de modo `observational` a `blocking` sin revisar las observaciones acumuladas
es una fuente garantizada de falsos positivos. El ciclo correcto:

1. Deploy en modo `observational` (exit 0 siempre).
2. Revisar `.planning/evolution/guardrail-observaciones.jsonl` tras ≥50 activaciones.
3. Calcular tasa de falsos positivos. Si < 5%, considerar `run_in_parallel`.
4. Solo con tasa < 2% y aprobación manual, activar `blocking`.

### Lógica de negocio en el guardrail

El guardrail evalúa complejidad semántica, no lógica de negocio. No debe:
- Validar permisos del usuario.
- Verificar que el agente existe en el sistema.
- Modificar el payload del agente invocado (solo observar y reportar).

---

## Ejemplo de uso concreto

Escenario: el orquestador invoca al `notificador-swl` con un prompt corto.
El hook `guardrail-modelo.js` intercepta la invocación PreToolUse:

```js
// Fragmento del hook guardrail-modelo.js
const prompt = payload?.tool_input?.prompt || '';
const agenteNombre = payload?.tool_input?.agent || '';

const tripwire = esTrivialPorLongitud(prompt) || esTareaLigera(prompt);

if (tripwire) {
  // Registrar observación
  const evento = {
    timestamp: new Date().toISOString(),
    agente: agenteNombre,
    modelo_actual: 'sonnet',
    modelo_sugerido: 'haiku',
    razon_tripwire: 'prompt-corto-sin-keywords-criticas',
    prompt_length: prompt.length,
  };
  fs.appendFileSync(GUARDRAIL_LOG, JSON.stringify(evento) + '\n');

  // Nudge al usuario — visible, no bloquea
  process.stderr.write(
    `[guardrail-modelo] El agente '${agenteNombre}' podría ejecutarse con 'haiku' ` +
    `(menos costoso). Razón: prompt corto sin keywords críticas (${prompt.length} chars). ` +
    `Ver .planning/evolution/guardrail-observaciones.jsonl\n`
  );
}
// exit 0 siempre — modo observacional
process.exit(0);
```

**Evento JSONL resultante** en `.planning/evolution/guardrail-observaciones.jsonl`:

```jsonl
{"timestamp":"2026-04-19T14:23:01.000Z","agente":"notificador-swl","modelo_actual":"sonnet","modelo_sugerido":"haiku","razon_tripwire":"prompt-corto-sin-keywords-criticas","prompt_length":87}
```

---

## Checklist para implementar un guardrail nuevo

- [ ] El guardrail tiene un nombre descriptivo y un criterio explícito de tripwire.
- [ ] Inicia en modo `observational` (exit 0 siempre).
- [ ] El override `nivelRiesgo: ALTO` está implementado.
- [ ] El caso `model: inherit` está excluido.
- [ ] El caso "agente ya en modelo mínimo" está excluido.
- [ ] Se registra en JSONL append-only (no sobreescribir).
- [ ] Emite nudge legible con prefijo `[guardrail-*]`.
- [ ] Está protegido con opt-in de variable de entorno.
- [ ] El criterio de promoción a `run_in_parallel`/`blocking` está documentado.

## Cuándo NO cargar

- Se diseñan guardrails para detectar operaciones destructivas, escritura en rutas prohibidas o secretos en output — eso es `reglas/seguridad-agentes.md` y `risk-scoring`; este skill evalúa complejidad semántica para degradación de costo de modelo, no riesgo operacional.
- El agente objetivo tiene `nivelRiesgo: ALTO` en frontmatter — el Criterio 3 es determinista: los agentes de alto riesgo nunca se degradan, por lo que diseñar un guardrail para ellos no tiene efecto.
- Se necesita configurar `allowedTools` o permisos de tools — eso es `estructura-proyecto-claude` y `seguridad-agentes`; este skill diseña la lógica de tripwire semántico.
- Se quiere activar un guardrail en modo `blocking` sin haber acumulado datos de observación — el skill requiere ≥50 activaciones en modo `observational` como precondición; usar este skill para diseñar el guardrail respetando ese ciclo, no para saltárselo.

## Gotchas / Errores comunes no obvios

- **Tripwire por longitud (< 200 chars) degrada un prompt corto pero arquitectónicamente crítico**: un prompt de 150 caracteres como "Diseñar la estructura de datos para el módulo de facturación" activa el Criterio 1 y sugiere degradar a Haiku. Causa: la longitud es una señal débil de trivialidad (confianza 0.6 según el skill). Solución: combinar siempre Criterio 1 con la ausencia de keywords críticas — el regex de palabras clave debe incluir "diseñar", "estructura", "arquitectura", "módulo" además de las ya listadas; prompts cortos con decisiones de diseño deben resistir la degradación.
- **`model: inherit` no verificado antes de aplicar degradación**: el guardrail baja el modelo de un sub-agente que hereda del orquestador, rompiendo la cadena de herencia intencional. Causa: el hook no verifica el campo `model` del frontmatter del agente invocado antes de aplicar los criterios. Solución: el primer check del guardrail debe ser `if (agenteConfig.model === 'inherit') return { tripwire_triggered: false }` — la herencia de modelo es una decisión del orquestador que el guardrail no debe anular.
- **JSONL de observaciones escrito con `atomicWriteJSON` en lugar de `appendFileSync`**: el archivo `guardrail-observaciones.jsonl` pierde eventos anteriores porque se sobreescribe en cada activación. Causa: confundir archivos de estado (usan `atomicWriteJSON`) con archivos de eventos de alta frecuencia (usan `appendFileSync`). Solución: `guardrail-observaciones.jsonl` es un log de eventos — siempre `fs.appendFileSync(GUARDRAIL_LOG, JSON.stringify(evento) + '\n')`; el ejemplo de código en el skill ya lo muestra correctamente.
- **Criterios 1 y 2 en conflicto sin conservar el modelo**: un prompt de 150 chars (Criterio 1 → degradar) contiene la palabra "lista" (Criterio 2 → degradar) pero también "arquitectura" (keyword crítica → no degradar). El guardrail actúa sobre los primeros dos criterios e ignora el tercero. Causa: no implementar la regla de resolución de conflictos del skill ("en caso de conflicto, NO degradar"). Solución: el flujo de decisión debe verificar todos los criterios antes de emitir el tripwire — si algún criterio sugiere no degradar (keyword crítica presente), el resultado es `tripwire_triggered: false` independientemente de los demás.
