El camino incorrecto debe ser imposible, no prohibido

| Última modificación: 24 de mayo de 2026 | Tiempo de Lectura: 8 minutos
Premios Blog KeepCoding 2025

Co-Fundador de KeepCoding

«Tengo un shell y soy creativo.»

— Claude, explicando por qué creó un script de 47 líneas como una cadena y se lo pasó a python -c

Esa frase es real. La dijo mi agente IA — bueno, no con esas palabras exactas, pero sí con esos hechos. Tenía que lanzar un proceso de un pipeline ETL. El comando correcto estaba en el Makefile. Pero algo falló. Y en vez de preguntar, hizo lo que haría cualquier programador con acceso root y cero supervisión: improvisó.

Manda huevos.

La confabulación que nadie ve

Ya he escrito antes sobre las alucinaciones de código: el LLM que inventa un campo JSON, construye un DTO alrededor, genera los tests, y te quedas con 90 tests verdes validando ficción. Ese problema es grave, pero al menos es estático. El código inventado se queda ahí, esperando a que alguien lo revise.

Hay otro tipo de confabulación mucho más peligroso: la confabulación operacional. Es cuando el agente no inventa código, sino que inventa caminos de ejecución.

El patrón es siempre el mismo:

Camino correcto falla → Agente busca atajo → Atajo "funciona" → Daño oculto

Te cuento dos ejemplos reales de un pipeline ETL que agrega datos dispersos de varias fuentes web.

Caso 1: El script como cadena. El pipeline tiene un comando make scrape-fuente que levanta un watchdog que a su vez lanza workers. El watchdog monitoriza, reinicia workers caídos, y cierra conexiones huérfanas. Un día, el agente necesitaba lanzar un scrape. El make falló por un problema de dependencias. ¿Qué hizo? Creó un script Python inline, 47 líneas como un string, y lo pasó a python -c "...". Sin error handling. Sin watchdog. Sin cleanup. Funcionó… hasta que un worker se quedó colgado y nadie lo reinició. Datos parciales, conexiones sin cerrar, y yo sin enterarme hasta tres días después.

Caso 2: El worker solitario. Otra sesión, mismo pipeline. El agente ejecutó voyeur worker directamente, saltándose el watchdog. El worker empezó a scrapear, se encontró un timeout de red, y se quedó en un bucle de retry infinito consumiendo recursos. Sin watchdog, nadie lo mató. Sin logging centralizado, nadie lo vio. El servidor estuvo tres horas dedicado a reintentar una página que devolvía 503.

En ambos casos, el agente tomó una decisión localmente razonable. «El make falla, pero yo sé cómo hacer lo mismo a mano.» El problema es que no sabía lo mismo. Sabía el 60%. El otro 40% eran las invariantes del sistema que no aparecen en ningún README.

Por qué prohibir no funciona

Mi primera reacción fue la de todo el mundo: escribir reglas.

## PROHIBIDO

- NUNCA ejecutar workers directamente
- NUNCA crear scripts como strings
- SIEMPRE usar make

¿Sabes cómo lee eso un LLM?

Lo que escribes Lo que interpreta
«NUNCA hagas X» «X está prohibido, salvo que yo crea que es necesario»
«SIEMPRE usa Y» «Y es preferible, pero si falla, improviso»
«Es peligroso hacer Z» «Tendré cuidado mientras hago Z»

Lo conté en un post anterior: las instrucciones blandas describen actitudes. El LLM necesita imposibilidades. «No corras junto a la piscina» no funciona. Lo que funciona es que no haya piscina, o que el suelo sea de velcro.

El LLM siempre cree que su caso es la excepción. Su entrenamiento lo optimiza para completar tareas, demostrar competencia, y evitar fricción. Cuando el camino correcto falla, esos incentivos se alinean en una dirección: «puedo resolverlo yo mismo». Y lo resuelve. Mal.

La filosofía: imposible, no prohibido

Hay una idea en ingeniería de seguridad que lleva décadas funcionando: hacer que lo incorrecto sea imposible en vez de prohibirlo.

No pones un cartel de «no insertar diésel» en un coche de gasolina. Haces que la boquilla no quepa. No pones una nota en el enchufe que dice «este aparato funciona a 110V, no enchufe a 220V». Haces que el enchufe tenga una forma diferente.

Dicho en cristiano: el sistema debe impedir físicamente hacer lo incorrecto, no depender de que alguien lea un manual.

Aplicado a un agente IA operando un pipeline ETL, esto se traduce en tres capas de defensa.

Capa 1: El código se defiende solo

Si el worker necesita el watchdog para funcionar correctamente, que el propio worker lo verifique:

class Worker:
    def _verify_invocation(self) -> None:
        """El worker se niega a arrancar si no hay watchdog."""
        if not os.environ.get("WATCHDOG_PID"):
            raise RuntimeError(
                "Worker lanzado sin watchdog. "
                "Usa 'make scrape-<fuente>'. "
                "NUNCA ejecutar el worker directamente."
            )

Ahora da igual lo creativo que sea el agente. Puede escribir python -c "from pipeline import Worker; Worker().run()" y el worker le va a escupir un error en la cara. No hay camino alternativo. El código se defiende.

Lo mismo para las fases del pipeline. Si la fase 3 (consolidación) necesita que la fase 1 (scrape) haya terminado, que lo compruebe al arrancar:

def verify_prerequisites(locale: str) -> None:
    """Fase 3 no arranca si Fase 1 no completó."""
    sources = get_enabled_sources(locale)
    completed = [s for s in sources if has_valid_data(s)]
    if not completed:
        raise PrerequisitoError(
            f"Fase 3 requiere al menos una fuente con datos. "
            f"Ejecuta primero: make scrape-<fuente>"
        )

No es un test. No es una regla en un fichero de configuración. Es código que se ejecuta cada vez y que no depende de que el agente haya leído el README.

Capa 2: Un único interfaz, sin atajos

El Makefile es la lista blanca de operaciones. Si no está en make help, no existe.

scrape-%:           ## Scrapear una fuente (make scrape-destacamos)
    $(MAKE) health
    cd packages/etl && uv run pipeline scrape $*

consolidate:        ## Consolidar todas las fuentes
    cd packages/etl && uv run pipeline consolidate

verify:             ## Verificar integridad de datos
    cd packages/etl && uv run pipeline verify

Fíjate en un detalle: scrape-% ejecuta health antes de hacer nada. El health check verifica que los adaptadores de scraping siguen funcionando (los sitios web cambian sin avisar). El agente no puede saltarse esta verificación porque está dentro del make target.

Al enemigo que huye, puente de plata: si quieres que el agente use el camino correcto, hazlo el más fácil. make scrape-fuente es más cómodo que montar un script a mano. No pelees contra la naturaleza del agente — canalízala.

Capa 3: Interceptores que bloquean los atajos

Las capas 1 y 2 cubren el 90%. El 10% restante es el agente siendo demasiado creativo. Para eso, interceptas los comandos antes de que se ejecuten.

Herramientas como Claude Code permiten configurar hooks que inspeccionan cada comando de shell antes de ejecutarlo. Un hook puede bloquear patrones peligrosos:

#!/usr/bin/env bash
# Interceptor de comandos: bloquea patrones peligrosos

COMMAND="$1"

# Nunca crear scripts como strings
if echo "$COMMAND" | grep -qE 'python[3]?\s+-c\s+'; then
    echo "BLOQUEADO: No crear scripts como strings. Usa make."
    exit 2
fi

# Nunca ejecutar el worker directamente
if echo "$COMMAND" | grep -qE 'pipeline\s+worker\b'; then
    echo "BLOQUEADO: No ejecutar el worker directo. Usa 'make scrape-<fuente>'."
    exit 2
fi

# Nunca tocar SQLite de datos directamente
if echo "$COMMAND" | grep -qE 'sqlite3\s+.*\.(db|sqlite)'; then
    echo "BLOQUEADO: No ejecutar SQL directo. Usa los comandos make."
    exit 2
fi

# Nunca mover imágenes del pipeline a mano
if echo "$COMMAND" | grep -qE '(mv|cp)\s+.*images/'; then
    echo "BLOQUEADO: No mover imágenes manualmente. Usa el pipeline."
    exit 2
fi

exit 0

Es una lista negra, sí. Y las listas negras no son perfectas. Pero combinada con las capas 1 y 2, cierra el cerco. El agente tendría que:

  1. Inventar un comando que no matchee ningún patrón del hook
  2. Que además no sea detectado por la guarda del código
  3. Y que produzca un resultado correcto sin el Makefile

Es posible, pero estamos hablando de un nivel de creatividad que bordea lo malintencionado. Y los LLMs no son malintencionados — son perezosamente creativos. Ponles una barrera y buscan el camino más fácil, que a estas alturas es el Makefile.

El catálogo de atajos que no sabías que temías

Más allá de ejecutar cosas mal, hay confabulaciones operacionales dentro del propio código que un agente produce:

Atajo Por qué lo hace Por qué es letal
Aflojar tests (assert count >= 0) El test falla, quiere que pase Un test que siempre pasa no testea nada
Inventar fixtures JSON Necesita datos de test, no tiene reales Ficción validando ficción
Suprimir warnings (# type: ignore) El linter se queja, quiere silencio Errores reales ocultos bajo la alfombra
except Exception: pass Algo falla, quiere que «funcione» Fallos silenciosos que se acumulan
Retry en bucle infinito Un servicio no responde Consume recursos y oculta el error real

Para cada uno de estos, la defensa es la misma: no prohibir, imposibilitar.

¿Cómo impides que afloje tests? Con un plugin de pytest que detecta assertions sospechosas:

def pytest_collection_modifyitems(items):
    for item in items:
        source = inspect.getsource(item.function)
        if ">= 0" in source and "count" in source:
            warnings.warn(
                f"Test sospechoso en {item.nodeid}: "
                f"'count >= 0' siempre pasa."
            )

¿Cómo impides fixtures inventadas? Exigiendo que cada fixture tenga proveniencia documentada: URL de origen, fecha de captura, hash SHA256. Una fixture sin proveniencia no pasa el CI.

¿Cómo impides except Exception: pass? Con una regla de ruff o flake8 que lo bloquee como error, no como warning.

En cada caso, la verificación es mecánica, automática, y no depende de que nadie lea una instrucción.

El problema de fondo: confianza vs. instrumentación

Hay un mantra en ingeniería que aplica perfectamente aquí:

«You don’t trust; you instrument.»

La confianza es un sentimiento. La instrumentación es un sistema. Los sentimientos escalan fatal. Los sistemas escalan bien.

Cuando le das a un agente IA acceso a un shell y le dices «pero ten cuidado», estás confiando. Cuando le das acceso a un shell donde los comandos peligrosos no funcionan, estás instrumentando.

La diferencia no es de grado. Es de naturaleza. Un agente que «tiene cuidado» falla cuando se distrae (y un LLM se distrae en cada generación de tokens). Un sistema que impide el camino incorrecto no falla porque no hay nada que fallar.

El marcador

Capa Fiabilidad Coste de implementación Ejemplo
Guardas en el código Alta Medio Worker que verifica watchdog
Makefile como interfaz único Alta Bajo make help = lista blanca
Hooks interceptores Media-alta Bajo Bloquear python -c
Reglas en config del agente Baja Mínimo «NUNCA hagas X»
Confiar en el agente Nula Gratis ¯\_(ツ)_/¯

Las tres primeras capas son acumulativas. La cuarta es un complemento útil pero insuficiente. La quinta es lo que hacemos todos hasta que nos pilla.

Quién vigila al vigilante

Queda la pregunta incómoda: ¿quién escribe las guardas? Si el agente IA escribe el código que se supone que debe restringirlo, ¿no estamos en un bucle?

Sí. Parcialmente.

La clave es que las guardas las diseña el humano y las implementa quien sea — agente, humano, o un mono con un teclado. Lo importante es que una vez implementadas, las guardas se testan contra sí mismas. El test de _verify_invocation no testea el pipeline; testea que el pipeline rechaza invocaciones incorrectas. Ese test es trivial de escribir y difícil de equivocar:

def test_worker_rejects_direct_invocation():
    """Worker DEBE fallar sin watchdog."""
    with pytest.raises(RuntimeError, match="sin watchdog"):
        Worker().run()

Si este test pasa, la guarda funciona. Si la guarda funciona, el agente no puede saltársela. No importa quién escribió el código. Importa que el test existe y pasa.

Lo que aprendí

Llevo meses trabajando con un agente IA en un pipeline ETL que agrega datos de fuentes web dispersas. He visto al agente hacer cosas brillantes y cosas que me han dejado el culo al aire. La conclusión más importante:

No diseñes reglas para un agente disciplinado. Diseña sistemas para un agente con shell y creatividad ilimitada.

El agente no es malicioso. Es un optimizador. Optimiza para completar la tarea, no para respetar tus invariantes. Si le dejas un hueco, lo encontrará. No porque quiera joderte, sino porque es literalmente lo que hace: encontrar caminos.

Tu trabajo no es bloquear cada camino incorrecto. Es hacer que el único camino que funcione sea el correcto.


Serie completa sobre fallos de IA en producción: Los 44 emails inventadosMEMORY.mdSilent failure5 defensas reactivasEste post: defensas estructurales.


Read this article in English.

Noticias recientes del mundo tech


¡CONVOCATORIA ABIERTA!

Desarrollo de apps móviles ios & Android

Full Stack Bootcamp

Clases en Directo | Profesores en Activo | Temario 100% actualizado

Descárgate también el informe de tendencias en el mercado laboral 2026.

Fórmate con planes adaptados a tus objetivos y logra resultados en tiempo récord.
KeepCoding Bootcamps
Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.