Lotes de logs a las tres de la mañana. Un agente de IA intentaba resolver un problema menor con el formateo de una fecha en una tarea de sincronización. De repente, ante una inconsistencia menor en la zona horaria del contenedor, el modelo decidió que la mejor opción era forzar un reboot de la máquina. No había malicia en su decisión. Tampoco una voluntad de sabotaje. Solo estadística aplicada: en el espacio latente del modelo, la secuencia de caracteres que representaba ese comando de reinicio era la respuesta probabilísticamente más adecuada para limpiar el estado del entorno y volver a empezar.

Para nuestro servidor, sin embargo, era un disparo directo al pie.

Ahí es donde entendés que la libertad absoluta que le damos a un agente autónomo de desarrollo no es autonomía real; es simplemente negligencia diferida. Si le das a un sistema de IA acceso sin restricciones bajo la vaga premisa de que "es inteligente y sabrá qué hacer", el desastre en producción no es una posibilidad remota: es una fecha en el calendario que estás esperando que llegue.

Cuando empezamos a estructurar el entorno de ejecución para el agente en el hub central, la discusión en el equipo de infraestructura se polarizó de inmediato. Por un lado, estaba la inercia de la omnipotencia operativa: la idea de que si el agente no tiene permisos para instalar dependencias globales del sistema, modificar configuraciones del sistema operativo o levantar puertos arbitrarios, sus capacidades se verían tan reducidas que el sistema dejaría de ser útil. Por el otro, aparecía el pánico defensivo de seguridad: limitarlo a un modo de solo lectura donde el agente sugiriera cambios pero nunca los ejecutara.

Pero un agente que no puede ejecutar el código que escribe no es un worker; es solo un analista estático que te traslada el trabajo de validación a vos. Si el valor real está en la ejecución autónoma, tenés que dejar que el código corra. Y hacer correr código que no fue escrito por un humano, sino generado de forma probabilística por un LLM en un servidor conectado a tu infraestructura, requiere trazar una línea muy clara en el suelo.

La decisión básica fue aislar el entorno. Creamos un servicio independiente, agora-worker, estructurado en Node.js, que corre dentro de un contenedor Docker en un VPS dedicado de Hostinger (il-server). El worker expone un directorio /workspace que se sincroniza contra un bucket de MinIO en la nube. De este modo, si el agente borra todo lo que tiene a su alcance, lo que destruye es una réplica temporal de los datos de trabajo, no el almacenamiento persistente del sistema principal.

Pero el aislamiento a nivel de infraestructura —el contenedor en sí— es solo la primera frontera física. Es la jaula exterior. El verdadero desafío no es evitar que el agente rompa el hardware o acceda a la red local del host; el problema es evitar que use las herramientas legítimas del propio entorno para evadir la lógica de control. Una IA que alucina un comando peligroso no necesita privilegios de root para comprometer el flujo del sistema si la validación del worker es frágil.

Es en este punto donde la validación del input deja de ser un problema puramente de desarrollo y se convierte en una decisión de arquitectura. Escribimos la lógica de control en un archivo llamado agent-command-policy.mjs.

La primera reacción del equipo fue la obvia: armar una blacklist. Una lista con los comandos prohibidos. Bloqueas sudo, bloqueas chmod, bloqueas reboot. Es una solución rápida, cómoda de escribir, y profundamente inútil.

El problema de las listas de exclusión es que asumen que sos capaz de anticipar todas las variantes de bypass que un motor de lenguaje natural puede generar cuando busca resolver un problema por aproximación. Un agente no necesita invocar sudo si puede estructurar un redireccionamiento de salida (>) que apunte a un script de configuración del entorno que el contenedor lee al arrancar, o si utiliza expansiones de shell como $(...) o sustitución de procesos para forzar la evaluación de cadenas de texto complejas descargadas en segundo plano. Las combinaciones de tuberías, caracteres de escape y sub-shells en Linux son virtualmente infinitas. Intentar bloquearlas una a una es jugar a perseguir tu propia sombra.

Por eso decidimos invertir el enfoque. Diseñamos una whitelist estricta.

El worker solo permite la ejecución de un grupo reducido de unos cuarenta binarios esenciales: comandos básicos como ls, grep, git, node o herramientas de lectura estática. Si el comando que el agente intenta ejecutar no hace referencia directa a un binario en esa lista, la ejecución se aborta en el acto. No existe la posibilidad de invocar herramientas de red no declaradas ni utilidades del sistema fuera del set mínimo necesario para inspeccionar y editar código.

Sin embargo, filtrar los ejecutables no basta si dejas la shell expuesta a la manipulación sintáctica. La validación en agent-command-policy.mjs analiza el comando propuesto antes de enviarlo al proceso del sistema. Bloqueamos cualquier tipo de redirección de flujo que intente escribir fuera del límite de /workspace. Si la ruta del archivo de destino no resuelve dentro del directorio de trabajo aislado, el comando es rechazado. De la misma manera, se bloquean por completo los caracteres de backtick y la sintaxis de process substitution. Si el agente intenta concatenar una instrucción para que la salida de una consulta de red sea evaluada directamente por el intérprete de comandos, la sesión se cierra de inmediato. Incluso comandos aparentemente simples como rm pasan por un filtro que impide que los argumentos apunten a la raíz del sistema (/), a directorios del usuario o a variables globales de entorno.

Cuando implementas restricciones de este nivel, la primera sospecha es que has inutilizado al agente. Pensás que la jaula es tan estrecha que el worker no podrá resolver problemas reales.

Lo que descubrimos es justo lo contrario. La restricción en el vocabulario operativo de la IA no mermó su capacidad de resolución; eliminó su capacidad de fallar de manera catastrófica. Cuando el agente no puede recurrir a un script bash complejo lleno de redirecciones y comandos del sistema para modificar un bloque de texto, se ve obligado a escribir un script de Node.js limpio, acotado y predecible que procesa el archivo usando las APIs de lectura y escritura estándar del lenguaje. La fricción del entorno forzó al modelo a generar código más legible, más estructurado y, sobre todo, más fácil de auditar para nosotros.

Además de la sintaxis, tuvimos que modelar las restricciones de recursos físicos. Los procesos de los agentes no solo son impredecibles en sus intenciones; también lo son en su ejecución. Un bucle infinito mal controlado que escribe en la consola puede agotar la memoria del contenedor o saturar el almacenamiento del VPS en cuestión de minutos. Para mitigar esto, definimos límites cuantitativos: cada comando tiene un tope de salida de 20KB. Si el agente intenta imprimir volcados de base de datos enteros en la salida estándar para evadir los límites de lectura, el worker trunca el flujo. El rate-limit está fijado en 1MB por segundo y limitamos las sesiones activas concurrentes a un máximo de 50 por worker, con un timeout absoluto de 30 minutos por inactividad.

Toda esta arquitectura no busca la infalibilidad del agente. Al contrario: parte de la certeza de que el agente va a fallar, va a alucinar y va a proponer comandos inválidos o dañinos en algún momento.

Diseñar para la desconfianza no significa paralizar el desarrollo. Significa trazar límites lo suficientemente sólidos e inteligibles para que, cuando el error ocurra, las consecuencias se queden contenidas en el espacio destinado para ello. La jaula no está ahí para limitar al worker; es el mecanismo de diseño que permite que el worker pueda existir en producción sin representar un riesgo sistémico para el resto de la infraestructura.

A veces leo sobre la frustración de quienes intentan construir sistemas autónomos y se quejan de que las capas de seguridad hacen que los modelos parezcan menos capaces. Piden acceso root, persistencia ilimitada y control total sobre el host para "liberar" el potencial de la tecnología.

Creo que confunden el valor de la autonomía con la renuncia al criterio de diseño. La verdadera robustez de un agente no se demuestra cuando tiene permisos para hacer cualquier cosa en el sistema, sino cuando es capaz de resolver la tarea respetando las fronteras del entorno que lo aloja. Al final, no estamos construyendo sistemas para que actúen sin control, sino para que operen bajo reglas claras. Y la calidad de esas reglas es la única medida del criterio técnico que las sostiene.