1. Introducción
Descripción general
En este lab, implementarás una instancia segura y completamente persistente del agente de Hermes (de Nous Research) en instancias de Cloud Run. Interactuarás con tu agente de IA a través del panel web de Hermes y respaldarás tu espacio de trabajo persistente con Google Cloud Storage.
Si bien Hermes admite un modo de puerta de enlace que podría ejecutarse como un servicio de Cloud Run con ajuste de escala automático, también actúa como un agente con estado que analiza las habilidades durante el inicio y controla la ejecución en segundo plano. Las instancias de Cloud Run proporcionan un entorno de larga duración y direccionable individualmente que se adapta perfectamente a esta carga de trabajo.
Actividades
- Prepara un bucket de Cloud Storage para conservar el estado y las configuraciones del contenedor.
- Crea un supervisor de Python personalizado (
run_hermes.py) y una secuencia de comandos de inicio (start_hermes.sh) para controlar la inicialización del arranque. - Implementa el agente de Hermes con
gcloud beta run instances deploy. - Accede al panel de Hermes y autentícate.
Qué aprenderás
- Cómo implementar el agente de Hermes en instancias de Cloud Run
- Cómo activar buckets de Cloud Storage en instancias de Cloud Run con GCSFuse
- Cómo configurar de forma segura SQLite y las memorias caché efímeras para evitar las limitaciones de bloqueo de archivos de GCSFuse
2. Configuración y requisitos
Configuración del proyecto de GCP
- Accede a la consola de Google Cloud.
- Crea o selecciona un proyecto de Google Cloud.
- Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.
Abre Cloud Shell
Activa Google Cloud Shell desde la barra de herramientas superior de Cloud Console.
Configura el proyecto y, luego, instala gcloud beta
Primero, configura tu proyecto y región como variables de entorno.
export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION="us-west2"
export BUCKET_NAME="hermes-state-${PROJECT_ID}"
Además, configura tu proyecto para gcloud.
gcloud config set project $PROJECT_ID
Asegúrate de que el componente beta esté instalado para gcloud beta run instances:
gcloud components install beta --quiet
Tu versión de gcloud está actualizada.
gcloud components updates
Habilita las APIs de Google Cloud requeridas
En Cloud Shell, habilita las APIs de Cloud Run, Cloud Storage y Secret Manager:
gcloud services enable \
run.googleapis.com \
secretmanager.googleapis.com \
storage.googleapis.com \
compute.googleapis.com \
aiplatform.googleapis.com
3. Crea una cuenta de servicio dedicada
Para cumplir con el principio de privilegio mínimo, crea una cuenta de servicio de IAM dedicada para el agente de Hermes y otórgale los permisos necesarios para invocar modelos de Vertex AI:
export SERVICE_ACCOUNT_NAME="hermes-sa"
gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} \
--display-name="Hermes Service Account"
export SERVICE_ACCOUNT="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/aiplatform.user"
4. Almacena credenciales en Secret Manager
Almacenaremos credenciales sensibles, como la contraseña del panel, en Google Cloud Secret Manager para que Cloud Run pueda insertarlas de forma segura en el contenedor durante el inicio.
Genera una contraseña aleatoria segura para tu panel y almacénala en Secret Manager:
export DASHBOARD_PASSWORD=$(openssl rand -hex 16)
echo "Generated Hermes Dashboard Password: ${DASHBOARD_PASSWORD}"
echo -n "${DASHBOARD_PASSWORD}" | gcloud secrets create hermes-dashboard-password \
--data-file=- \
--replication-policy="automatic"
gcloud secrets add-iam-policy-binding hermes-dashboard-password \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/secretmanager.secretAccessor"
5. Prepara el bucket de Cloud Storage y los archivos de configuración
Hermes necesita almacenamiento persistente activado como /opt/data. Usaremos un bucket de Google Cloud Storage (GCS) y lo activaremos con activaciones de volúmenes de Cloud Storage.
1. Crea un bucket de Cloud Storage
gcloud storage buckets create gs://${BUCKET_NAME} --location=${REGION}
# Grant the service account permissions to mount the bucket
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role="roles/storage.objectAdmin"
2. Crear config.yaml
Crea un archivo config.yaml. Asegúrate de incluir _config_version: 12 para garantizar que la configuración se cargue correctamente:
_config_version: 12
model:
default: "google/gemini-3.8-flash"
provider: "vertex"
dashboard:
enabled: true
database:
journal_mode: delete
3. Crea el script del supervisor (run_hermes.py)
Cloud Storage no admite los mecanismos específicos de bloqueo de archivos que las bases de datos de SQLite necesitan para ejecutarse de forma segura. Para evitar la corrupción de la base de datos, necesitamos una secuencia de comandos de "supervisor" personalizada (run_hermes.py). Esta secuencia de comandos configura Hermes para que almacene sus bloqueos temporales de la base de datos en la memoria local del contenedor en lugar de en Cloud Storage antes de iniciar el agente.
Crea run_hermes.py de forma local:
import os
import shutil
import subprocess
import sys
import threading
import time
print(
"=== INITIALIZING HERMES SUPERVISOR ===", flush=True
)
# 1. Local Directory Setup
# Creates temporary, local folders (in /tmp) for the agent's caches and working directories.
# See more below in comment section NOTE ON CLOUD STORAGE FUSE
home_dir = "/tmp/hermes_home"
hermes_dir = os.path.join(home_dir, ".hermes")
os.makedirs(hermes_dir, exist_ok=True)
os.makedirs("/tmp/logs", exist_ok=True)
os.makedirs("/tmp/skills", exist_ok=True)
os.makedirs("/tmp/uv_cache", exist_ok=True)
os.makedirs("/tmp/cache", exist_ok=True)
os.makedirs("/opt/data/workspace", exist_ok=True)
os.makedirs("/opt/data/.hermes", exist_ok=True)
# 2. State Restoration & Database Config
# Copies your existing configurations and chat history (state.db) from Cloud Storage into the local folders.
# It also forces the SQLite database into TRUNCATE mode, a crucial step to prevent database corruption
# when eventually saving back to Cloud Storage. See more in section 3. Note on Cloud Storage Fuse below
if os.path.exists("/opt/data/config.yaml"):
shutil.copy("/opt/data/config.yaml", os.path.join(hermes_dir, "config.yaml"))
print(f"Synced config.yaml -> {hermes_dir}/config.yaml", flush=True)
elif os.path.exists("/opt/data/.hermes/config.yaml"):
shutil.copy("/opt/data/.hermes/config.yaml", os.path.join(hermes_dir, "config.yaml"))
print(f"Synced config.yaml from .hermes -> {hermes_dir}/config.yaml", flush=True)
if os.path.exists("/opt/data/.env"):
shutil.copy("/opt/data/.env", os.path.join(hermes_dir, ".env"))
print(f"Synced .env -> {hermes_dir}/.env", flush=True)
elif os.path.exists("/opt/data/.hermes/.env"):
shutil.copy("/opt/data/.hermes/.env", os.path.join(hermes_dir, ".env"))
print(f"Synced .env from .hermes -> {hermes_dir}/.env", flush=True)
if os.path.exists("/opt/data/.hermes/state.db"):
shutil.copy("/opt/data/.hermes/state.db", os.path.join(hermes_dir, "state.db"))
print(f"Synced state.db -> {hermes_dir}/state.db (restored previous chats!)", flush=True)
# 3. Note on Cloud Storage Fuse
# Cloud Storage FUSE is optimized for object storage, but is not fully POSIX compliant.
# This means GCS lacks the byte-range file locking required by active caches and default SQLite (WAL mode)
# which SQLite depends on to prevent data collisions.
# Without these locks, SQLite experiences database corruption and blocked I/O operations.
# To ensure stability, we route these active I/O processes to local container memory (/tmp).
# See section 5 Enable Autosave below on how /tmp is uploaded to Cloud Storage.
# Read more: https://cloud.google.com/storage/docs/cloud-storage-fuse/overview#differences-and-limitations
db_path = os.path.join(hermes_dir, "state.db")
try:
import sqlite3
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA journal_mode=TRUNCATE;")
conn.close()
print("Configured SQLite database to TRUNCATE mode for direct single-file persistence", flush=True)
except Exception as e:
print(f"Warning: Failed to configure TRUNCATE mode: {e}", flush=True)
subprocess.run(["chmod", "-R", "777", "/tmp"], check=False)
# 4. Update system environment variables
# Hermes needs to know to look at the new local /tmp folders rather than defaulting to the mounted bucket.
env = dict(os.environ)
env["HOME"] = home_dir
env["HERMES_HOME"] = hermes_dir
env["PATH"] = "/opt/hermes/.venv/bin:/opt/hermes/bin:" + env.get("PATH", "")
env["PYTHONUNBUFFERED"] = "1"
env["HERMES_STATE_PATH"] = hermes_dir
env["HERMES_SKILLS_PATH"] = "/tmp/skills"
env["UV_CACHE_DIR"] = "/tmp/uv_cache"
env["XDG_CACHE_HOME"] = "/tmp/cache"
env["SQLITE_BUSY_TIMEOUT"] = "30000"
env["HERMES_ALLOW_ROOT_GATEWAY"] = "1"
env["HERMES_WORKSPACE"] = "/opt/data/workspace"
env["HERMES_WRITE_SAFE_ROOT"] = "/opt/data"
python_bin = "/opt/hermes/.venv/bin/python3"
# 5. Enable Autosave
# Spawn a background worker thread to watch your local database and config files every 5 seconds.
# As you chat with your agent, this worker thread automatically copies the updated database content
# back to Cloud Storage to persist it.
def sync_to_gcs_loop():
files_to_sync = ["state.db", "config.yaml", ".env"]
last_mtimes = {}
# Initialize last_mtimes
for f in files_to_sync:
path = os.path.join(hermes_dir, f)
if os.path.exists(path):
last_mtimes[f] = os.path.getmtime(path)
else:
last_mtimes[f] = 0
while True:
time.sleep(5)
for f in files_to_sync:
src_path = os.path.join(hermes_dir, f)
if os.path.exists(src_path):
try:
mtime = os.path.getmtime(src_path)
if mtime > last_mtimes.get(f, 0):
dst_path = os.path.join("/opt/data/.hermes", f)
shutil.copy2(src_path, dst_path)
last_mtimes[f] = mtime
print(f"Auto-saved {f} to GCS volume mount", flush=True)
except Exception as e:
print(f"Error auto-saving {f} to GCS: {e}", flush=True)
threading.Thread(target=sync_to_gcs_loop, daemon=True).start()
# 6. Launch the Hermes Gateway (the AI backend) and the Web Dashboard (the UI)
# These are launched as parallel processes, sending logs to Cloud Run via stdout & stderr
print("=== STARTING GATEWAY IN BACKGROUND ===", flush=True)
gw = subprocess.Popen(
[python_bin, "-m", "hermes_cli.main", "gateway", "run"],
env=env,
cwd="/opt/data/workspace",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
def stream_gw():
for line in iter(gw.stdout.readline, ""):
if line:
print(f"[GATEWAY] {line.rstrip()}", flush=True)
threading.Thread(target=stream_gw, daemon=True).start()
print("=== STARTING DASHBOARD ON 0.0.0.0:8080 ===", flush=True)
sys.stdout.flush()
dash = subprocess.Popen(
[
python_bin,
"-m",
"hermes_cli.main",
"dashboard",
"--host",
"0.0.0.0",
"--port",
"8080",
"--skip-build",
],
env=env,
cwd="/opt/data/workspace",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
for line in iter(dash.stdout.readline, ""):
if line:
print(f"[DASHBOARD] {line.rstrip()}", flush=True)
rc = dash.wait()
print(f"DASHBOARD EXITED WITH RETURN CODE: {rc}", flush=True)
while True:
time.sleep(10)
4. Crea la secuencia de comandos de inicio (start_hermes.sh)
Crea start_hermes.sh de forma local.
#!/bin/sh
set -e
export PYTHONUNBUFFERED=1
exec python3 /opt/data/run_hermes.py
5. Sube archivos a Cloud Storage
Copia los archivos de configuración en la raíz de tu bucket de GCS:
gcloud storage cp config.yaml run_hermes.py start_hermes.sh gs://${BUCKET_NAME}/
6. Implementa Hermes en instancias de Cloud Run
Usamos gcloud beta run instances deploy para implementar el contenedor. Este comando incluye configuraciones específicas para abordar problemas conocidos con GCSFuse y límites de contenedores.
Asegúrate de que tus variables de entorno (PROJECT_ID, REGION, BUCKET_NAME, SERVICE_ACCOUNT) se exporten en tu sesión de terminal activa.
Implementa la instancia:
gcloud beta run instances deploy hermes-instance \
--image nousresearch/hermes-agent:latest \
--service-account ${SERVICE_ACCOUNT} \
--command "/bin/sh" \
--args "/opt/data/start_hermes.sh" \
--port 8080 \
--cpu 2 \
--memory 4Gi \
--ingress all \
--no-invoker-iam-check \
--add-volume name=hermes-storage,mount-path=/opt/data,type=cloud-storage,mount-options="uid=2000;gid=2000;file-mode=0777;dir-mode=0777;implicit-dirs",bucket=$BUCKET_NAME \
--set-secrets "HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=hermes-dashboard-password:latest" \
--set-env-vars "PYTHONUNBUFFERED=1,VERTEX_PROJECT_ID=$PROJECT_ID,VERTEX_LOCATION=global,HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin,HERMES_ALLOW_ROOT_GATEWAY=1,HERMES_WORKSPACE=/opt/data/workspace,HERMES_WRITE_SAFE_ROOT=/opt/data" \
--region $REGION \
--project $PROJECT_ID
Configuraciones importantes incluidas anteriormente:
--service-account: Adjunta la cuenta de serviciohermes-sadedicada.- Supervisor Scripts:
start_hermes.shllama al supervisor personalizado de Pythonrun_hermes.pyque enruta los límites de bloqueo de SQLite y los problemas de almacenamiento en caché fuera de GCS FUSE hacia tmpfs local. --set-secrets: Inserta credenciales directamente desde Secret Manager en variables de entorno.
7. Interactúa directamente a través de la IU web de Hermes
Una vez implementado, puedes acceder a tu panel en la URL .run.app generada. Cuando se te solicite la autenticación, ingresa admin como nombre de usuario y tu ${DASHBOARD_PASSWORD} como contraseña.
Chatea con tu agente
Puedes probar con echo "hello" para confirmar que el agente funciona.
Prueba el almacenamiento persistente
Puedes probar el almacenamiento persistente en tu bucket de Google Cloud preguntándole al agente
Write "hello world" to a file named hello.txt in your workspace.
Luego, en tu shell, puedes verificar que el archivo se haya escrito ejecutando
gcloud storage cat gs://$BUCKET_NAME/workspace/hello.txt
Por último, para verificar que tus chats y archivos se conserven en las nuevas instancias de Cloud Run (ya que una instancia de Cloud Run tiene un tiempo de ejecución continuo de hasta 7 días, con una política de reinicio automático configurada de forma predeterminada), puedes volver a ejecutar el comando gcloud beta run instances deploy exactamente como antes. Luego, verás tus sesiones de chat. Y puedes preguntarle a tu agente
Read the contents of the file hello.txt in your workspace.
y verás "hello world".
8. Limpieza
Para evitar que se generen cargos en tu cuenta de Google Cloud por los recursos que usaste en este codelab, sigue estos pasos:
- Borra la instancia de Cloud Run:
gcloud beta run instances delete hermes-instance --region ${REGION} --quiet - Borra secretos de Secret Manager:
gcloud secrets delete hermes-dashboard-password --quiet - Borra el bucket de Cloud Storage:
gcloud storage rm -r gs://${BUCKET_NAME} - Borra la cuenta de servicio dedicada:
gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
9. Conclusión
¡Felicitaciones! Implementaste correctamente una instancia segura y completamente persistente del agente de Hermes en instancias de Cloud Run respaldadas por Cloud Storage.
Qué aprendiste
- Cómo implementar el agente de Hermes en instancias de Cloud Run
- Cómo activar buckets de Cloud Storage en instancias de Cloud Run con GCSFuse
- Cómo configurar de forma segura SQLite y las memorias caché efímeras para evitar las limitaciones de bloqueo de archivos de GCSFuse