Cloud Run इंस्टेंस पर Hermes एजेंट को डिप्लॉय करने का तरीका

1. परिचय

खास जानकारी

इस लैब में, Cloud Run इंस्टेंस पर Hermes Agent (Nous Research का बनाया गया) का पूरी तरह से परसिस्टेंट और सुरक्षित इंस्टेंस डिप्लॉय किया जाएगा. आपको अपने एआई एजेंट के साथ इंटरैक्ट करने के लिए, Hermes वेब डैशबोर्ड का इस्तेमाल करना होगा. साथ ही, Google Cloud Storage का इस्तेमाल करके, अपने वर्कस्पेस को सेव करना होगा.

Hermes में एक गेटवे मोड होता है. यह ऑटो-स्केलिंग Cloud Run सेवा के तौर पर काम कर सकता है. साथ ही, यह एक स्टेटफ़ुल एजेंट के तौर पर भी काम करता है. यह बूट होने पर, स्किल को स्कैन करता है और बैकग्राउंड में होने वाले प्रोसेस को मैनेज करता है. Cloud Run इंस्टेंस, लंबे समय तक चलने वाला और अलग-अलग पतों के हिसाब से काम करने वाला एनवायरमेंट उपलब्ध कराते हैं. यह इस वर्कलोड के लिए सबसे सही है.

आपको क्या करना होगा

  • कंटेनर की स्थिति और कॉन्फ़िगरेशन को बनाए रखने के लिए, Cloud Storage बकेट तैयार करें.
  • बूट शुरू होने की प्रोसेस को मैनेज करने के लिए, कस्टम Python सुपरवाइज़र (run_hermes.py) और स्टार्टअप स्क्रिप्ट (start_hermes.sh) बनाएं.
  • gcloud beta run instances deploy का इस्तेमाल करके, Hermes एजेंट को डिप्लॉय करें.
  • Hermes डैशबोर्ड को ऐक्सेस करें और पुष्टि करें.

आपको क्या सीखने को मिलेगा

  • Hermes एजेंट को Cloud Run इंस्टेंस पर डिप्लॉय करने का तरीका.
  • GCSFuse का इस्तेमाल करके, Cloud Run इंस्टेंस में Cloud Storage बकेट को माउंट करने का तरीका.
  • GCSFuse में फ़ाइल लॉक करने से जुड़ी पाबंदियों को बायपास करने के लिए, SQLite और कुछ समय के लिए सेव की जाने वाली कैश मेमोरी को सुरक्षित तरीके से कॉन्फ़िगर करने का तरीका.

2. सेटअप और ज़रूरी शर्तें

GCP प्रोजेक्ट सेट अप करना

  1. Google Cloud Console में साइन इन करें.
  2. Google Cloud प्रोजेक्ट बनाएं या चुनें.
  3. पक्का करें कि आपके Google Cloud प्रोजेक्ट के लिए बिलिंग चालू हो.

Cloud Shell खोलें

Cloud Console के सबसे ऊपर मौजूद टूलबार से, Google Cloud Shell चालू करें.

प्रोजेक्ट सेट करना और gcloud beta इंस्टॉल करना

सबसे पहले, अपने प्रोजेक्ट और क्षेत्र को एनवायरमेंट वैरिएबल के तौर पर सेट करें.

export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION="us-west2"
export BUCKET_NAME="hermes-state-${PROJECT_ID}"

साथ ही, gcloud के लिए अपना प्रोजेक्ट कॉन्फ़िगर करें.

gcloud config set project $PROJECT_ID

पक्का करें कि gcloud beta run instances के लिए beta कॉम्पोनेंट इंस्टॉल किया गया हो:

gcloud components install beta --quiet

साथ ही, gcloud का वर्शन अप-टू-डेट हो.

gcloud components updates

ज़रूरी Google Cloud API चालू करना

Cloud Shell में, Cloud Run, Cloud Storage, और Secret Manager API चालू करें:

gcloud services enable \
  run.googleapis.com \
  secretmanager.googleapis.com \
  storage.googleapis.com \
  compute.googleapis.com \
  aiplatform.googleapis.com

3. डेडीकेटेड सेवा खाता बनाना

कम से कम अधिकारों के सिद्धांत का पालन करने के लिए, Hermes एजेंट के लिए एक खास IAM सेवा खाता बनाएं. साथ ही, उसे 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. Secret Manager में क्रेडेंशियल सेव करना

हम डैशबोर्ड के पासवर्ड जैसे संवेदनशील क्रेडेंशियल को Google Cloud Secret Manager में सेव करेंगे, ताकि Cloud Run बूट टाइम पर उन्हें कंटेनर में सुरक्षित तरीके से इंजेक्ट कर सके.

अपने डैशबोर्ड के लिए, सुरक्षित रैंडम पासवर्ड जनरेट करें और उसे 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. Cloud Storage बकेट और कॉन्फ़िगरेशन फ़ाइलें तैयार करना

Hermes को /opt/data के तौर पर माउंट किए गए स्थायी स्टोरेज की ज़रूरत होती है. हम Google Cloud Storage (GCS) बकेट का इस्तेमाल करेंगे और इसे Cloud Storage वॉल्यूम माउंट का इस्तेमाल करके माउंट करेंगे.

1. 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. config.yaml बनाएं

config.yaml फ़ाइल बनाएं. कॉन्फ़िगरेशन सही तरीके से लोड हो, इसके लिए _config_version: 12 को शामिल करना न भूलें:

_config_version: 12

model:
  default: "google/gemini-3.8-flash"
  provider: "vertex"

dashboard:
  enabled: true

database:
  journal_mode: delete

3. सुपरवाइज़र स्क्रिप्ट बनाना (run_hermes.py)

Cloud Storage, फ़ाइल लॉक करने के उन खास तरीकों के साथ काम नहीं करता जिनकी ज़रूरत SQLite डेटाबेस को सुरक्षित तरीके से चलाने के लिए होती है. डेटाबेस को खराब होने से बचाने के लिए, हमें कस्टम "सुपरवाइज़र" स्क्रिप्ट (run_hermes.py) की ज़रूरत होती है. यह स्क्रिप्ट, एजेंट को शुरू करने से पहले, Hermes को कॉन्फ़िगर करती है, ताकि वह अपने अस्थायी डेटाबेस लॉक को Cloud Storage के बजाय कंटेनर की लोकल मेमोरी में सेव कर सके.

लोकल तौर पर run_hermes.py बनाएं:

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. स्टार्टअप स्क्रिप्ट बनाना (start_hermes.sh)

start_hermes.sh को लोकल तौर पर बनाएँ.

#!/bin/sh
set -e
export PYTHONUNBUFFERED=1
exec python3 /opt/data/run_hermes.py

5. Cloud Storage में फ़ाइलें अपलोड करना

कॉन्फ़िगरेशन फ़ाइलों को अपने GCS बकेट के रूट में कॉपी करें:

gcloud storage cp config.yaml run_hermes.py start_hermes.sh gs://${BUCKET_NAME}/

6. Cloud Run इंस्टेंस पर Hermes को डिप्लॉय करना

हम कंटेनर को डिप्लॉय करने के लिए gcloud beta run instances deploy का इस्तेमाल करते हैं. इस कमांड में, GCSFuse और कंटेनर की सीमाओं से जुड़ी जानी-पहचानी समस्याओं को हल करने के लिए, कुछ खास कॉन्फ़िगरेशन शामिल हैं.

पक्का करें कि आपके एनवायरमेंट वैरिएबल (PROJECT_ID, REGION, BUCKET_NAME, SERVICE_ACCOUNT) आपके चालू टर्मिनल सेशन में एक्सपोर्ट किए गए हों.

इंस्टेंस को डिप्लॉय करें:

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

ऊपर दिए गए कॉन्फ़िगरेशन में ये शामिल हैं:

  • --service-account: इससे, hermes-sa सेवा खाता अटैच होता है.
  • सुपरवाइज़र स्क्रिप्ट: start_hermes.sh कस्टम Python सुपरवाइज़र run_hermes.py को कॉल करता है. यह SQLite लॉकिंग की सीमाओं और कैश मेमोरी से जुड़ी समस्याओं को GCS FUSE से हटाकर लोकल tmpfs में भेजता है.
  • --set-secrets: यह क्रेडेंशियल को सीधे Secret Manager से एनवायरमेंट वैरिएबल में डालता है.

7. Hermes के वेब यूज़र इंटरफ़ेस (यूआई) के ज़रिए सीधे इंटरैक्ट करना

डैशबोर्ड डिप्लॉय होने के बाद, जनरेट किए गए .run.app यूआरएल पर जाकर इसे ऐक्सेस किया जा सकता है. जब आपसे पुष्टि करने के लिए कहा जाए, तो उपयोगकर्ता नाम के तौर पर admin और पासवर्ड के तौर पर ${DASHBOARD_PASSWORD} डालें.

अपने एजेंट से चैट करना

echo "hello" जैसे विकल्प आज़माकर, यह पुष्टि की जा सकती है कि एजेंट काम कर रहा है या नहीं.

स्थायी स्टोरेज की जांच करना

Google Cloud Bucket में परसिस्टेंट स्टोरेज की सुविधा को टेस्ट किया जा सकता है. इसके लिए, एजेंट से यह सवाल पूछें

Write "hello world" to a file named hello.txt in your workspace.

इसके बाद, अपने शेल में जाकर यह पुष्टि की जा सकती है कि फ़ाइल लिखी गई है या नहीं. इसके लिए, यह कमांड चलाएं

gcloud storage cat gs://$BUCKET_NAME/workspace/hello.txt

आखिर में, यह पुष्टि करने के लिए कि आपकी चैट और फ़ाइलें, नए Cloud Run इंस्टेंस में भी मौजूद हैं, gcloud beta run instances deploy कमांड को पहले की तरह फिर से चलाया जा सकता है. ऐसा इसलिए, क्योंकि Cloud Run इंस्टेंस लगातार सात दिनों तक चलता है. साथ ही, इसमें डिफ़ॉल्ट रूप से अपने-आप रीस्टार्ट होने की नीति कॉन्फ़िगर होती है. इसके बाद, आपको अपने चैट सेशन दिखेंगे. इसके अलावा, अपने एजेंट से ये काम करने के लिए कहा जा सकता है

Read the contents of the file hello.txt in your workspace.

इसके बाद, आपको "hello world" दिखेगा.

8. स्टोरेज में जगह बनाएं

इस कोडलैब में इस्तेमाल किए गए संसाधनों के लिए, अपने Google Cloud खाते से शुल्क न लिए जाने के लिए:

  1. Cloud Run इंस्टेंस मिटाएं:
    gcloud beta run instances delete hermes-instance --region ${REGION} --quiet
    
  2. Secret Manager के सीक्रेट मिटाएं:
    gcloud secrets delete hermes-dashboard-password --quiet
    
  3. Cloud Storage बकेट मिटाना:
    gcloud storage rm -r gs://${BUCKET_NAME}
    
  4. डेडीकेटेड सेवा खाता मिटाएं:
    gcloud iam service-accounts delete ${SERVICE_ACCOUNT} --quiet
    

9. नतीजा

बधाई हो! आपने Cloud Storage की मदद से, Cloud Run इंस्टेंस पर Hermes Agent का सुरक्षित और पूरी तरह से परसिस्टेंट इंस्टेंस डिप्लॉय कर लिया है!

आपने क्या सीखा

  • Hermes एजेंट को Cloud Run इंस्टेंस पर डिप्लॉय करने का तरीका.
  • GCSFuse का इस्तेमाल करके, Cloud Run इंस्टेंस में Cloud Storage बकेट को माउंट करने का तरीका.
  • GCSFuse में फ़ाइल लॉक करने से जुड़ी पाबंदियों को बायपास करने के लिए, SQLite और कुछ समय के लिए सेव की जाने वाली कैश मेमोरी को सुरक्षित तरीके से कॉन्फ़िगर करने का तरीका.