Llevo casi treinta años procurando que las bases de datos no se caigan, así que no reparto acceso root a la ligera. Pero mi homelab ya llegó al tamaño donde hacerlo todo a mano es perder tiempo: un clúster Proxmox de cuatro nodos, un firewall OPNsense, Technitium para DNS, TrueNAS, un Synology, switches administrables, varias VLAN y una docena larga de contenedores y máquinas virtuales. Cada servicio nuevo pide lo mismo. Crear el contenedor, actualizarlo, instalar Docker, agregar DNS, tomar un snapshot y dejarlo documentado.
Configuré Hermes Agent, de Nous Research, para hacer ese trabajo. Corre en un pequeño LXC del clúster y hablo con él desde WhatsApp. La grabación narrada está en la página del proyecto Hermes. Aquí quiero explicar la ingeniería. Elegí una ejecución exitosa después de diez intentos previos de práctica; esto no es una prueba de confiabilidad al primer intento.
Las piezas
Pienso en el sistema en cuatro partes: el canal, el modelo, las herramientas y las reglas.
El canal es WhatsApp. El gateway de Hermes recibe los mensajes y dirige cada conversación a un perfil. Mis mensajes directos llegan al perfil homelab; mi familia usa otro para calendarios y listas. En mi configuración tienen instrucciones, memoria y entornos separados, y las credenciales de infraestructura solo se montan en el entorno homelab. Esa separación importa, pero el nombre de un perfil por sí solo no es una barrera de seguridad del sistema operativo.
El modelo del perfil homelab es gpt-6-luna, mediante una suscripción de Codex. El registro proporcionado no incluye una factura por token: los conteos describen esta sesión, no una estimación en dólares. Sus instrucciones describen el laboratorio, las herramientas y las reglas de operación. Hermes guarda las sesiones en SQLite, comprime el contexto cuando la conversación crece y carga skills, procedimientos reutilizables que escribimos el agente o yo.
Las herramientas viven en un entorno Docker preparado para esto. El backend de terminal Docker inicia ahí los comandos de shell. Desde ese entorno, los comandos pueden llegar a otras máquinas por SSH. La imagen tiene nmap, arp-scan, dig, mtr, traceroute, tcpdump, snmpwalk, curl y jq, además de Python con requests, proxmoxer y paramiko. Usa la red del host, elimina las capacidades Linux y recupera solo algunas, incluidas las de sockets raw que necesita nmap.
Tiene dos montajes. /lab permite lectura y escritura y guarda el inventario, el registro de cambios, procedimientos y respaldos. /secrets es de solo lectura y contiene tokens de API y la llave SSH del agente. Las instrucciones prohíben imprimirlos. Solo lectura evita modificar esos archivos, pero permite usar las credenciales y acceder a la infraestructura que autorizan.
La política de aprobación y el registro de cambios son mi forma de supervisar ese acceso.
Los controles y sus límites
La pieza central es una CLI pequeña llamada lab. Agrupa las API de Proxmox, OPNsense y Technitium: lab pve GET /cluster/resources, lab opn GET /api/diagnostics/interface/getArp, lab dns GET /api/zones/list. Las lecturas pasan directamente. Rechaza las escrituras si no incluyen --confirmed "<lo que aprobó el humano>". Cada escritura confirmada agrega una entrada en /lab/changelog.md con el servicio, método, ruta, estado HTTP, cuerpo de la petición y texto de aprobación. También puede respaldar OPNsense y Technitium.
El propio agente proporciona el texto de confirmación. La CLI no comprueba de forma independiente que una persona haya aprobado la operación, y una llamada directa a la API o un comando SSH pueden evitarla. Las instrucciones agregan la política:
- Las lecturas están permitidas. Primero el inventario, después las API en vivo.
- Cada cambio empieza con un plan concreto: qué, dónde, valores, efecto esperado, riesgo y reversión. Después pregunta "Apply? (yes/no)". Un sí solo cubre el plan presentado.
- Respaldar OPNsense o Technitium antes de modificarlos y ofrecer un snapshot de Proxmox antes de una operación riesgosa.
- No crear reglas que corten la ruta de administración entre el agente y el firewall, DNS o Proxmox. Usar el mecanismo savepoint de OPNsense para cambios del firewall. Esta ejecución no modificó reglas ni probó esa reversión.
- Eliminar una máquina, contenedor, almacenamiento o zona requiere otro sí que nombre el objeto exacto.
- Los cambios por SSH, que la CLI no observa, requieren que el agente agregue una entrada al registro justo después.
Hermes tiene además aprobación por comando, con modos manual, smart y off. En smart, otra llamada al modelo decide si el comando necesita aprobación. Es útil, pero no determinista: una misma tarea puede producir comandos redactados de otra forma. En esta grabación estaba apagada. La aprobación del plan fue la intervención humana que permitió continuar.
Nada de esto es nuevo. Es un comité de cambios de una sola persona. Puedo revisar un plan, una aprobación y un registro. Pero aquí el agente puede reescribir ese registro, así que no es una auditoría inmutable. La comodidad está en que el operador prepara el plan por mí; la autoridad que le concedí sigue siendo mi responsabilidad.
Un mensaje, un servidor
La petición en WhatsApp pedía un contenedor listo para Docker:
- Un LXC sin privilegios con Debian 13, llamado
work, en un nodo concreto, con el siguiente VMID libre, 4 cores, 8 GB de RAM, 2 GB de swap, 64 GB en ZFS ynesting=1,keyctl=1para Docker. - La misma configuración de bridge, VLAN y firewall que un contenedor existente, con IP estática y dominio de búsqueda.
- Acceso root por SSH con una sola llave pública, contraseñas desactivadas y validación con
sshd -t. apt full-upgrade, Docker CE y el plugin Compose desde el repositorio de Docker, y una prueba conhello-world.- Respaldo de Technitium y un registro A para
work.home.thelatentsource.com. - Un snapshot
baseline-docker, actualización del inventario y registro de cada paso.
Primero hizo lecturas: recursos del clúster para elegir el VMID, red del contenedor de referencia, plantillas y almacenamiento del nodo, existencia del registro DNS, tabla ARP y leases DHCP del firewall. Unos 70 segundos después del mensaje respondió con un plan y esperó.
También señaló que la comprobación de IP no era concluyente. Que no haya entrada ARP, lease o respuesta al ping no prueba que la dirección esté reservada. Pidió confirmar su disponibilidad y responder yes. Yo solo respondí "yes" y continuó. La creación funcionó, pero esa respuesta no resolvió la ambigüedad. Hace falta comprobar la reserva antes de crear el contenedor.
Después de la aprobación usó SSH como root en el nodo Proxmox y ejecutó pct. El ajuste keyctl requiere privilegios que el token de API limitado no tenía. El token conservó su alcance, pero root por SSH dio al agente autoridad amplia sobre el hipervisor. Es un privilegio considerable, aunque las instrucciones le exijan documentar cada cambio. Guardó la llave pública en un archivo temporal, lo pasó a pct create --ssh-public-keys y lo eliminó después.
Todo dentro del contenedor pasó por pct exec: la actualización, el repositorio de Docker, la instalación, hello-world y la configuración de SSH. Este Debian 13 usaba activación por socket para SSH; el agente validó con sshd -t y no reinició ssh.service. Creó el snapshot después de validar SSH. Para DNS usó lab: respaldo, registro y lectura de comprobación. Después actualizó el inventario e hizo la verificación final.
El agente nunca inició sesión por SSH en el servidor nuevo. Solo instaló mi llave. Eso evita dejar su llave en el invitado, aunque conserva acceso por el hipervisor. Yo comprobé DNS con dig y entré con mi propia llave. La grabación muestra ese acceso y otra ejecución exitosa de hello-world. No muestra un intento de contraseña rechazado; el agente verificó la configuración y su sintaxis.
Una edición del inventario falló porque las líneas esperadas no coincidían. No se modificó ningún archivo. Corrigió el patch unos trece segundos después, sin otro mensaje mío. Un script también puede manejar errores previstos; aquí lo útil fue recuperarse de una edición que el propio agente había construido mal.
Los números
Del mensaje de WhatsApp a las 20:43:49 UTC al resumen de las 20:50:03 pasaron unos 6 minutos 14 segundos. La grabación completa dura 8 minutos 57.5 segundos e incluye la escritura del mensaje y mi comprobación independiente. El video narrado acelera las esperas y etiqueta las velocidades y las imágenes detenidas.
El registro de uso continúa hasta las 20:50:31. Cuenta 29 llamadas al modelo hasta el resumen y otras tres después. El total de 32 no debe presentarse como el costo exclusivo de llegar al resumen.
| Fase | Llamadas al modelo | Tokens de entrada | Tokens de salida |
|---|---|---|---|
| Comprobaciones y plan | 7 | 185,063 | 2,296 |
| Desde aprobación hasta resumen | 22 | 936,759 | 7,482 |
| Después del resumen | 3 | 162,210 | 1,120 |
| Sesión registrada completa | 32 | 1,284,032 | 10,898 |
La sesión completa suma 1,294,930 tokens y unos 279 segundos de latencia registrada en llamadas al modelo. Incluye las llamadas posteriores al resumen, así que no descompone limpiamente los seis minutos de aprovisionamiento. La exportación de la sesión contiene 33 llamadas a herramientas: 21 comandos de terminal, tres lecturas de archivo, tres patches, dos actualizaciones de tareas y cuatro consultas de skills. Las llamadas a herramientas y al modelo cuentan cosas distintas.
La entrada representa el 99.2% de los tokens. Las instrucciones, el historial y las salidas de comandos se repiten; las respuestas promedian unos 341 tokens por llamada. La llamada más grande llevó 56,582 tokens de entrada. Con compresión, las peticiones posteriores no necesariamente contienen toda la conversación original. Son conteos acumulados, no texto único ni una medición del ahorro por caché.
El CSV por llamada permite revisar las cuentas. Excluye los intentos de práctica y un modelo revisor de seguridad, que estaba apagado en esta toma.
Hay un detalle que me preocupa más que el total. Después de comprimir contexto, Hermes cambió una entrada anterior de "49 packages upgraded" a "completed successfully". La salida retenida del comando no respaldaba el número de paquetes, así que quitarlo fue una corrección. La secuencia no demuestra que la compresión causara la edición. Sí demuestra que el agente puede reescribir su propia historia. Quiero correcciones agregadas como entradas nuevas, conservando la original, y un destino de auditoría que el agente no pueda reescribir.
Con qué me quedo
Un script de shell puede crear el contenedor más rápido. Hermes escribió uno para esta tarea después. Lo que obtengo del agente es el razonamiento alrededor: lee el estado real, completa valores a partir de un contenedor existente, elige otra ruta cuando la API no permite una operación, corrige algunos errores y deja un registro que puedo revisar. Para un homelab que cambia constantemente, eso me sirve más que ahorrar unos segundos.
Quiero conservar la aprobación humana. Sigue siendo un sistema no determinista, y revisar un plan cuesta poco frente a cambiar mi red. Las mejoras pendientes son concretas: resolver la reserva de IP antes de crear, comprobar la aprobación fuera del texto que genera el agente y guardar la auditoría fuera de su acceso de escritura. La grabación narrada del proyecto muestra esta ejecución exitosa, incluido el error del patch y mi comprobación independiente.
