diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index 224ae38df..490bc38c0 100644 --- a/services/hermes/agent-deployment.yaml +++ b/services/hermes/agent-deployment.yaml @@ -24,7 +24,7 @@ spec: ai.bstein.dev/execution: Hermes Kanban with durable direct Codex and Claude Code CLI workers ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available - ai.bstein.dev/config-rev: "20260810-stock-dashboard-init-hardening" + ai.bstein.dev/config-rev: "20260811-stock-supervisor" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: hermes-agent vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens @@ -319,10 +319,11 @@ spec: - name: hermes image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5 imagePullPolicy: IfNotPresent - command: [/opt/hermes/.venv/bin/hermes] - args: [gateway, run, --no-supervise] + command: [/init, /opt/hermes/docker/main-wrapper.sh] + args: [gateway, run] ports: - {name: api, containerPort: 8642, protocol: TCP} + - {name: dashboard, containerPort: 9119, protocol: TCP} env: - {name: HERMES_HOME, value: /opt/data} - {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json} @@ -334,8 +335,12 @@ spec: - {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright} - {name: AGENT_BROWSER_EXECUTABLE_PATH, value: /opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell} - {name: AGENT_BROWSER_ARGS, value: "--no-sandbox,--disable-dev-shm-usage"} - - {name: HERMES_DASHBOARD, value: "0"} + - {name: HERMES_DASHBOARD, value: "1"} + - {name: HERMES_DASHBOARD_HOST, value: 127.0.0.1} + - {name: HERMES_DASHBOARD_PORT, value: "9119"} + - {name: HERMES_DASHBOARD_FILES_ROOT, value: /opt/data/workspace} - {name: HERMES_DASHBOARD_PUBLIC_URL, value: https://agent.hermes.bstein.dev} + - {name: HERMES_TUI_AGENT_INIT_TIMEOUT_S, value: "180"} - {name: API_SERVER_ENABLED, value: "true"} - {name: API_SERVER_HOST, value: 0.0.0.0} - {name: API_SERVER_PORT, value: "8642"} @@ -347,98 +352,34 @@ spec: - {name: provider-auth, mountPath: /shared-auth} - {name: coordinator, mountPath: /opt/coordinator, readOnly: true} - {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py} + - {name: tui-gateway-patch, mountPath: /opt/hermes/tui_gateway/server.py, subPath: server.py} - {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true} - {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true} + - {name: tmp, mountPath: /tmp} startupProbe: - tcpSocket: {port: api} + tcpSocket: {port: dashboard} periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 readinessProbe: - tcpSocket: {port: api} + tcpSocket: {port: dashboard} initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 livenessProbe: - tcpSocket: {port: api} + tcpSocket: {port: dashboard} initialDelaySeconds: 90 periodSeconds: 30 timeoutSeconds: 10 securityContext: allowPrivilegeEscalation: false - runAsUser: 10000 - runAsGroup: 10000 + runAsUser: 0 + runAsGroup: 0 seccompProfile: type: RuntimeDefault resources: - requests: {cpu: 250m, memory: 512Mi} - limits: {cpu: "2", memory: 4Gi} - - name: dashboard - image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5 - imagePullPolicy: IfNotPresent - command: [/bin/sh, -ec] - args: - - | - set -a - . /opt/data/.env - set +a - exec /opt/hermes/.venv/bin/hermes dashboard \ - --host 127.0.0.1 \ - --port 9119 \ - --skip-build \ - --no-open - ports: - - {name: dashboard, containerPort: 9119, protocol: TCP} - env: - - {name: HERMES_HOME, value: /opt/data} - - {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json} - - {name: HOME, value: /opt/data/home} - - {name: CODEX_HOME, value: /opt/data/home/.codex} - - {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude} - - {name: KUBECONFIG, value: /opt/data/home/.kube/config} - - {name: PYTHONPATH, value: /opt/hermes} - - {name: PATH, value: /opt/coordinator:/opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin} - - {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright} - - {name: AGENT_BROWSER_EXECUTABLE_PATH, value: /opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell} - - {name: AGENT_BROWSER_ARGS, value: "--no-sandbox,--disable-dev-shm-usage"} - - {name: HERMES_DASHBOARD_FILES_ROOT, value: /opt/data/workspace} - - {name: HERMES_DASHBOARD_PUBLIC_URL, value: https://agent.hermes.bstein.dev} - - {name: HERMES_TUI_AGENT_INIT_TIMEOUT_S, value: "180"} - volumeMounts: - - {name: home, mountPath: /opt/data} - - {name: provider-auth, mountPath: /shared-auth} - - {name: coordinator, mountPath: /opt/coordinator, readOnly: true} - - {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py} - - {name: tui-gateway-patch, mountPath: /opt/hermes/tui_gateway/server.py, subPath: server.py} - - {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true} - - {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true} - - {name: tmp, mountPath: /tmp} - workingDir: /opt/data/workspace - readinessProbe: - exec: - command: [curl, -fsS, http://127.0.0.1:9119/api/status] - initialDelaySeconds: 20 - periodSeconds: 10 - timeoutSeconds: 5 - livenessProbe: - exec: - command: [curl, -fsS, http://127.0.0.1:9119/api/status] - initialDelaySeconds: 60 - periodSeconds: 30 - timeoutSeconds: 10 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: [ALL] - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 10000 - runAsGroup: 10000 - seccompProfile: - type: RuntimeDefault - resources: - requests: {cpu: 100m, memory: 256Mi} - limits: {cpu: "2", memory: 4Gi} + requests: {cpu: 350m, memory: 768Mi} + limits: {cpu: "3", memory: 6Gi} - name: oauth2-proxy image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.3@sha256:10a1165743a192e1940b4708fb9647027185ce11a681a1c5519b442ff7f1f561 imagePullPolicy: IfNotPresent diff --git a/testing/tests/test_hermes_cli_lanes.py b/testing/tests/test_hermes_cli_lanes.py index af939ba7f..55df05ab2 100644 --- a/testing/tests/test_hermes_cli_lanes.py +++ b/testing/tests/test_hermes_cli_lanes.py @@ -632,15 +632,17 @@ def test_agent_root_is_stock_dashboard_and_terminal_is_a_separate_path(): containers = {item["name"]: item for item in pod["containers"]} assert "webui" not in containers - dashboard = containers["dashboard"] - dashboard_command = dashboard["args"][0] - assert "/opt/hermes/.venv/bin/hermes dashboard" in dashboard_command - assert "--host 127.0.0.1" in dashboard_command - assert "--port 9119" in dashboard_command - assert "--skip-build" in dashboard_command - assert "--no-open" in dashboard_command - dashboard_env = {item["name"]: item["value"] for item in dashboard["env"]} - assert dashboard_env["HERMES_TUI_AGENT_INIT_TIMEOUT_S"] == "180" + assert "dashboard" not in containers + hermes = containers["hermes"] + assert hermes["command"] == ["/init", "/opt/hermes/docker/main-wrapper.sh"] + assert hermes["args"] == ["gateway", "run"] + hermes_env = {item["name"]: item["value"] for item in hermes["env"]} + assert hermes_env["HERMES_DASHBOARD"] == "1" + assert hermes_env["HERMES_DASHBOARD_HOST"] == "127.0.0.1" + assert hermes_env["HERMES_DASHBOARD_PORT"] == "9119" + assert hermes_env["HERMES_TUI_AGENT_INIT_TIMEOUT_S"] == "180" + assert hermes["securityContext"]["runAsUser"] == 0 + assert hermes["securityContext"]["runAsGroup"] == 0 terminal = containers["terminal"] command = terminal["args"][0] @@ -665,7 +667,7 @@ def test_agent_root_is_stock_dashboard_and_terminal_is_a_separate_path(): if item["name"] == "patch-tui-gateway" ) assert patch_init["command"][-1] == "/patched/server.py" - for name in ("dashboard", "terminal"): + for name in ("hermes", "terminal"): mounts = containers[name]["volumeMounts"] assert { "name": "tui-gateway-patch",