1. Introdução
Visão geral
Neste laboratório, você vai implantar uma instância totalmente persistente e segura do Hermes Agent (da Nous Research) nas instâncias do Cloud Run. Você vai interagir com seu agente de IA usando o painel da Web do Hermes e fazer backup do espaço de trabalho persistente com o Google Cloud Storage.
Embora o Hermes ofereça suporte a um modo de gateway que pode ser executado como um serviço do Cloud Run com escalonamento automático, ele também funciona como um agente com estado que verifica habilidades na inicialização e processa a execução em segundo plano. As instâncias do Cloud Run oferecem um ambiente de longa duração e endereçável individualmente, perfeito para essa carga de trabalho.
Atividades deste laboratório
- Prepare um bucket do Cloud Storage para manter o estado e as configurações do contêiner.
- Crie um supervisor personalizado do Python (
run_hermes.py) e um script de inicialização (start_hermes.sh) para processar a inicialização da inicialização. - Implante o agente do Hermes usando
gcloud beta run instances deploy. - Acesse e faça a autenticação no painel do Hermes.
O que você vai aprender
- Como implantar o agente do Hermes em instâncias do Cloud Run.
- Como ativar buckets do Cloud Storage em instâncias do Cloud Run usando o GCSFuse.
- Como configurar com segurança o SQLite e os caches temporários para evitar as limitações de bloqueio de arquivos do GCSFuse.
2. Configuração e requisitos
Configuração do projeto do GCP
- Faça login no Console do Google Cloud.
- Crie ou selecione um projeto do Google Cloud.
- Confira se o faturamento está ativado para seu projeto do Google Cloud
Abrir o Cloud Shell
Ative o Google Cloud Shell na barra de ferramentas da parte de cima do console do Cloud.
Definir o projeto e instalar o gcloud beta
Primeiro, defina o projeto e a região como variáveis de ambiente.
export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION="us-west2"
export BUCKET_NAME="hermes-state-${PROJECT_ID}"
e configure seu projeto para a gcloud.
gcloud config set project $PROJECT_ID
Verifique se o componente beta está instalado para gcloud beta run instances:
gcloud components install beta --quiet
E sua versão da gcloud está atualizada.
gcloud components updates
Ativar as APIs obrigatórias do Google Cloud
No Cloud Shell, ative as APIs Cloud Run, Cloud Storage e Secret Manager:
gcloud services enable \
run.googleapis.com \
secretmanager.googleapis.com \
storage.googleapis.com \
compute.googleapis.com \
aiplatform.googleapis.com
3. Criar conta de serviço dedicada
Para seguir o princípio do menor privilégio, crie uma conta de serviço dedicada do IAM para o agente do Hermes e conceda a ela as permissões necessárias para invocar modelos da 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. Armazenar credenciais no Secret Manager
Vamos armazenar credenciais sensíveis, como a senha do painel, no Google Cloud Secret Manager para que o Cloud Run possa injetá-las com segurança no contêiner na hora da inicialização.
Gere uma senha aleatória segura para seu painel e armazene-a no 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. Preparar o bucket do Cloud Storage e os arquivos de configuração
O Hermes precisa de armazenamento permanente montado como /opt/data. Vamos usar um bucket do Google Cloud Storage (GCS) e montá-lo usando as montagens de volume do Cloud Storage.
1. Crie um bucket do 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. Criar config.yaml
Crie um arquivo config.yaml. Inclua _config_version: 12 para garantir que a configuração seja carregada corretamente:
_config_version: 12
model:
default: "google/gemini-3.8-flash"
provider: "vertex"
dashboard:
enabled: true
database:
journal_mode: delete
3. Criar o script do supervisor (run_hermes.py)
O Cloud Storage não é compatível com os mecanismos específicos de bloqueio de arquivos necessários para a execução segura de bancos de dados SQLite. Para evitar a corrupção do banco de dados, precisamos de um script "supervisor" personalizado (run_hermes.py). Esse script configura o Hermes para armazenar os bloqueios temporários do banco de dados na memória local do contêiner em vez do Cloud Storage antes de iniciar o agente.
Crie run_hermes.py localmente:
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. Criar o script de inicialização (start_hermes.sh)
Crie start_hermes.sh localmente.
#!/bin/sh
set -e
export PYTHONUNBUFFERED=1
exec python3 /opt/data/run_hermes.py
5. Fazer upload de arquivos para o Cloud Storage
Copie os arquivos de configuração para a raiz do bucket do GCS:
gcloud storage cp config.yaml run_hermes.py start_hermes.sh gs://${BUCKET_NAME}/
6. Implantar o Hermes em instâncias do Cloud Run
Usamos o gcloud beta run instances deploy para implantar o contêiner. Esse comando inclui configurações específicas para resolver problemas conhecidos com o GCSFuse e limites de contêineres.
Verifique se as variáveis de ambiente (PROJECT_ID, REGION, BUCKET_NAME, SERVICE_ACCOUNT) foram exportadas na sessão de terminal ativa.
Implante a instância:
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
Configurações importantes incluídas acima:
--service-account: anexa a conta de serviçohermes-sadedicada.- Scripts do supervisor:
start_hermes.shchama o supervisor Python personalizadorun_hermes.py, que encaminha os limites de bloqueio do SQLite e os problemas de cache do GCS FUSE para o tmpfs local. --set-secrets: injeta credenciais diretamente do Secret Manager em variáveis de ambiente.
7. Interagir diretamente pela interface da Web do Hermes
Depois da implantação, você poderá acessar o painel no URL .run.app gerado. Quando a autenticação for solicitada, insira admin como nome de usuário e ${DASHBOARD_PASSWORD} como senha.
Converse com o agente
Você pode tentar usar echo "hello" para confirmar se o agente está funcionando.
Testar o armazenamento permanente
Você pode testar o armazenamento permanente no seu bucket do Google Cloud pedindo ao agente
Write "hello world" to a file named hello.txt in your workspace.
Em seguida, no shell, execute
gcloud storage cat gs://$BUCKET_NAME/workspace/hello.txt
Por fim, para verificar se seus chats e arquivos são mantidos em novas instâncias do Cloud Run (já que uma instância do Cloud Run tem um tempo de execução contínuo de até sete dias, com uma política de reinicialização automática configurada por padrão), execute o comando gcloud beta run instances deploy exatamente como antes. Em seguida, você vai ver suas sessões de chat. Você pode pedir ao agente
Read the contents of the file hello.txt in your workspace.
e você vai ver "hello world".
8. Limpeza
Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste codelab:
- Exclua a instância do Cloud Run:
gcloud beta run instances delete hermes-instance --region ${REGION} --quiet - Excluir secrets do Secret Manager:
gcloud secrets delete hermes-dashboard-password --quiet - Excluir bucket do Cloud Storage:
gcloud storage rm -r gs://${BUCKET_NAME} - Excluir conta de serviço dedicada:
gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
9. Conclusão
Parabéns! Você implantou com sucesso uma instância segura e totalmente persistente do agente do Hermes em instâncias do Cloud Run com suporte do Cloud Storage.
O que você aprendeu
- Como implantar o agente do Hermes em instâncias do Cloud Run.
- Como ativar buckets do Cloud Storage em instâncias do Cloud Run usando o GCSFuse.
- Como configurar com segurança o SQLite e os caches temporários para evitar as limitações de bloqueio de arquivos do GCSFuse.