Fábrica AI · Ingeniería con agentes · ~7 min

Las 4 capas de una fábrica de IA

Un agente ignora los consejos y obedece lo determinista. Estas son las cuatro capas —con mis configs reales— con las que evito que rompa producción, ordenadas de la más ignorable a la que no se puede saltar. Con un paso a paso y un bloque para autoconfigurarlo en Claude Code.

Por Juan Urrea2026-07~7 min
Las 4 capas de defensa de una fábrica de IA

Cuando construyes casi todo el día con agentes que escriben código, aprendes algo rápido: el default de un agente es el mínimo esfuerzo. No por pereza — porque optimiza lo que se ve completo ahora, no lo que aguanta producción a las 3 a.m. Confiar en que "se porte bien" no es una estrategia.

La respuesta no es un consejo mejor. Es un sistema de defensas por capas, ordenadas por una sola pregunta: ¿el agente puede ignorar esto? Arriba, lo que es una guía (ignorable). Abajo, lo que es una barrera (determinista, no negociable). Estas son mis cuatro, con la configuración real que uso.

L1 · CLAUDE.md — la guía

La primera capa es un archivo de reglas que el agente lee al arrancar. Ahí viven los principios no-negociables del proyecto: cada dato citando su fuente, cada operación al log de auditoría, la salida de IA que no reentra como hecho.

# CLAUDE.md — reglas no-negociables (extracto)
- P1/P5: todo claim cita su raw_signal;
  toda operación va a audit_log.
- P7: salida de IA no reentra como hecho.
- C(n) no abre sin que C(n-1) pase su eval ≥ 0.90.

Es imprescindible para guiar el criterio. Pero seamos honestos sobre lo que es: un archivo de texto. El agente puede leerlo y, en el momento equivocado, no seguirlo. Es una guía, no una garantía. Si tu única defensa vive aquí, no tienes una defensa.

Cómo montarla

  1. Crea CLAUDE.md en la raíz del repo.
  2. Escribe reglas verificables, no deseos: "toda función pública tiene docstring", no "escribe buen código".
  3. Mantenlo corto: lo que no cabe en la cabeza del agente, no lo va a respetar.

L2 · Un reviewer que solo puede leer

La segunda capa separa a quien construye de quien revisa. Es un subagente independiente, en sesión aparte, con un permiso clave: solo lectura. No puede tocar el código; solo puede aprobar o rechazar contra la spec.

# .claude/agents/code-reviewer.md
name: code-reviewer
model: sonnet
allowedTools: [Read, Glob, Grep]   # solo-lectura

# Reglas: NO aprobar si falta un item del NFR Checklist.
# Verificar audit_log (P5) y llm_generated (P7) en cada operación.

Un revisor con permiso de escribir termina "arreglando" lo que debería marcar. Quitarle la escritura lo obliga a hacer su trabajo real: encontrar problemas, no aprobar rápido.

Cómo montarla

  1. Crea .claude/agents/code-reviewer.md con allowedTools: [Read, Glob, Grep].
  2. Dale un mandato paranoico: revisar contra la spec y el checklist, y no aprobar con dudas.
  3. Invócalo en una sesión distinta a la que escribió el código.

L3 · permissions.deny + hooks — la barrera

Aquí empieza lo que el agente no puede hacer. permissions.deny es una lista determinista de acciones bloqueadas — el agente no puede tocarlas aunque quiera. La uso para volver la constitución del proyecto de solo-lectura: ni el agente ni yo la editamos sin intención.

// .claude/settings.json
"permissions": { "deny": [
  "Edit(producto/constitucion.md)",
  "Write(fabrica/constitucion-fabrica.md)",
  "Read(.env)", "Read(**/.env)"      // secretos
]},
"hooks": {
  "PostToolUse": [{ "matcher": "Write|Edit",
    "command": "uv run ruff check --fix; uv run ruff format" }],
  "Stop": [{ "command":
    "git add -A && git commit -m checkpoint" }]
}

Los hooks añaden reflejos automáticos: cada edición se formatea con ruff, y al terminar cada sesión el trabajo se auto-commitea. No dependo de que el agente "se acuerde": pasa siempre. Esto no se ignora.

Cómo montarla

  1. En .claude/settings.json, agrega a permissions.deny los archivos críticos (constitución, .env, migraciones).
  2. Configura un hook PostToolUse que formatee/lintee en cada edición.
  3. Configura un hook Stop que haga git commit de checkpoint al cerrar la sesión.

L4 · CI con eval-gate — el candado final

La última capa vive fuera del agente, en CI. Nada mergea sin pasar tres puertas: lint+type, tests, y un gate de evaluación que exige un score mínimo contra un golden dataset. El umbral no es un consejo: es un assert.

# .github/workflows/ci.yml (extracto)
lint-type:  ruff check · ruff format --check · mypy src/
test:       uv run pytest -m "not evaluator"
eval-gate:  uv run pytest fabrica/evals -m evaluator
            # el eval hace: assert score >= 0.90  → o no entra

Un test en verde prueba correctitud, no calidad. Por eso el gate mide comportamiento contra datos reales: verde en todo y eval ≥ 0.90, o no hay merge. La calidad se enforcea, no se pide.

Cómo montarla

  1. Un workflow con jobs lint-typetesteval-gate.
  2. El eval-gate corre tus evals y hace assert score >= umbral (empieza en 0.90).
  3. Protege la rama main: sin CI verde, el botón de merge no está.

Consejo vs. barrera: dónde poner lo crítico

La lección detrás de las cuatro capas es una sola. No todas protegen igual:

Pon lo crítico donde el agente no pueda ignorarlo — no en un archivo de consejos.

La mayoría pone toda su confianza en la capa que se puede ignorar, y se sorprende cuando el agente la ignora. La disciplina no es confiar mejor: es diseñar defensas que no se puedan saltar.

Autoconfigúralo con Claude Code

¿Usas Claude Code? No descargues nada: copia el bloque de abajo, pégalo en tu sesión y creará las cuatro capas en tu repo. Al terminar, te pedirá los ajustes de tu stack.

Configura mi repositorio con las "4 capas de defensa" para construir con agentes (patrón ver4). Crea EXACTAMENTE estos cuatro archivos con este contenido. No cambies nada más. Al final, dime qué debo ajustar a mi stack.

===== 1) CLAUDE.md (raíz del repo) — L1: la guía =====
# CLAUDE.md — reglas no-negociables
## Antes de cada tarea
1. ¿Qué capacidad habilita esto? Si no hay respuesta, no construir.
2. Lee la spec y su eval. No se abre C(n) sin que C(n-1) pase su eval >= 0.90.
3. Plan primero: plan citando la spec -> revisión -> implementación.
## Reglas no-negociables (adáptalas a tu producto)
- Todo dato/claim cita su fuente. Toda operación va a un audit_log.
- La salida de IA no reentra como hecho (márcala: llm_generated=true).
- Muestra incertidumbre; no afirmes más de lo que la evidencia respalda.
- La complejidad se gana: no añadas piezas sin un eval que lo justifique.
## Cómo se trabaja
- Spec -> tests/eval -> implementación. PRs < 400 líneas.
- El que revisa nunca es el que construye. Memoria en git: STATUS.md + lessons.md.

===== 2) .claude/settings.json — L3: la barrera (permissions.deny + hooks) =====
{
  "permissions": {
    "deny": [
      "Read(.env)", "Read(**/.env)", "Edit(.env)", "Edit(**/.env)",
      "Bash(cat .env*)", "Bash(printenv:*)", "Bash(env:*)",
      "Edit(producto/constitucion.md)", "Write(producto/constitucion.md)"
    ]
  },
  "hooks": {
    "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command",
      "command": "cd \"$CLAUDE_PROJECT_DIR\" && uv run ruff check --fix .; uv run ruff format .; true" }] }],
    "Stop": [{ "hooks": [{ "type": "command",
      "command": "cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git diff-index --quiet HEAD || git commit -m checkpoint 2>/dev/null || true" }] }]
  }
}

===== 3) .claude/agents/code-reviewer.md — L2: reviewer read-only =====
---
name: code-reviewer
description: Revisor read-only y paranoico. Revisa cambios contra la spec antes de mergear.
model: sonnet
allowedTools: [Read, Glob, Grep]
---
Eres un staff engineer revisando código de otro agente. Encuentra problemas, no apruebes rápido.
- NO aprobar si falta cualquier item del checklist de la spec.
- NO aprobar código fuera de la spec sin justificación.
- Verifica logging/validación/manejo de errores y que los tests validen lo que dicen.

===== 4) .github/workflows/ci.yml — L4: el candado (eval-gate) =====
name: ci
on: { push: { branches: [main] }, pull_request: {} }
jobs:
  lint-type:
    runs-on: ubuntu-latest
    steps: [{ uses: actions/checkout@v4 }, { uses: astral-sh/setup-uv@v5 },
            { run: uv sync }, { run: uv run ruff check . }, { run: uv run ruff format --check . }, { run: uv run mypy src/ }]
  test:
    runs-on: ubuntu-latest
    steps: [{ uses: actions/checkout@v4 }, { uses: astral-sh/setup-uv@v5 },
            { run: uv sync }, { run: uv run pytest -m "not evaluator" }]
  eval-gate:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps: [{ uses: actions/checkout@v4 }, { uses: astral-sh/setup-uv@v5 },
            { run: uv sync }, { run: uv run pytest evals -m evaluator }]
    # el eval hace: assert score >= 0.90

Al terminar: (a) confírmame los 4 archivos creados; (b) ajusta el linter/formatter del hook a mi stack; (c) ajusta la ruta de evals y el umbral en ci.yml (empieza en 0.90); (d) recuérdame proteger la rama main para exigir CI verde.

¿Prefieres hacerlo a mano? Descarga los archivos sueltos: CLAUDE.md · settings.json · code-reviewer.md · ci.yml.

Construimos Centro de Verdad con la misma disciplina que vendemos: separar lo que parece correcto de lo que está verificado — antes del merge, y antes de la decisión.ver-4.com
← Todos los posts