# services/hermes/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: hermes namespace: hermes labels: app: hermes spec: replicas: 1 revisionHistoryLimit: 2 progressDeadlineSeconds: 2700 strategy: type: Recreate selector: matchLabels: app: hermes template: metadata: labels: app: hermes annotations: ai.bstein.dev/frontend-fix: scope PTY attachment by selected conversation ai.bstein.dev/router-wire-contract: ollama-numeric-keepalive ai.bstein.dev/model: anthropic/claude-opus-5, falling back to openai-codex/gpt-5.6-terra then titan-20 Qwen 14B ai.bstein.dev/role: testing-triage ai.bstein.dev/placement: titan-21 preferred, Jetson preferred, arm64 fallback ai.bstein.dev/config-rev: "20260815-runtime-access-boundary" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: hermes vault.hashicorp.com/agent-inject-secret-triage-api-key: kv/data/atlas/hermes/triage-api vault.hashicorp.com/agent-inject-template-triage-api-key: | {{- with secret "kv/data/atlas/hermes/triage-api" -}} {{ .Data.data.api_key }} {{- end }} vault.hashicorp.com/agent-pre-populate-only: "true" # Without this the agent init container is appended after the # pod's own init containers, so init-config runs before the # secret file exists and silently falls back to the Secret - # the migration looks complete while Vault is never read. vault.hashicorp.com/agent-init-first: "true" vault.hashicorp.com/agent-requests-cpu: 25m vault.hashicorp.com/agent-requests-mem: 32Mi vault.hashicorp.com/agent-limits-cpu: 100m vault.hashicorp.com/agent-limits-mem: 128Mi spec: serviceAccountName: hermes-triage automountServiceAccountToken: true securityContext: fsGroup: 10000 fsGroupChangePolicy: OnRootMismatch seccompProfile: type: RuntimeDefault affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/arch operator: In values: - arm64 - key: node-role.kubernetes.io/worker operator: In values: - "true" - key: node-role.kubernetes.io/storage-backbone operator: DoesNotExist - key: kubernetes.io/hostname operator: NotIn values: - titan-13 - titan-14 - titan-17 - titan-18 preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: node-role.kubernetes.io/storage-backbone operator: DoesNotExist - weight: 80 preference: matchExpressions: - key: atlas.bstein.dev/spillover operator: DoesNotExist - weight: 60 preference: matchExpressions: - key: hardware operator: In values: - rpi5 - weight: 50 preference: matchExpressions: - key: hardware operator: In values: - rpi4 initContainers: - name: init-config image: busybox:1.37 imagePullPolicy: IfNotPresent command: - sh - -c - | set -eu mkdir -p /opt/data/workspace/triage-proof /opt/data/home/.local/bin /opt/data/logs cp /config/config.yaml /opt/data/config.yaml cp /config/SOUL.md /opt/data/SOUL.md cp /config/AGENTS.md /opt/data/workspace/AGENTS.md cp /config/START-HERE.md /opt/data/workspace/START-HERE.md cp /config/HERMES-CAPABILITIES.md /opt/data/workspace/HERMES-CAPABILITIES.md cp /guide/OPERATOR-RUNBOOK.md /opt/data/workspace/HERMES-OPERATOR-RUNBOOK.md cp /config/ATLAS-TRIAGE-PROOFS.md /opt/data/workspace/triage-proof/ATLAS-TRIAGE-PROOFS.md cp /config/START-HERE.md /opt/data/START-HERE.md cp /config/HERMES-CAPABILITIES.md /opt/data/HERMES-CAPABILITIES.md cp /guide/OPERATOR-RUNBOOK.md /opt/data/HERMES-OPERATOR-RUNBOOK.md cp /config/ATLAS-TRIAGE-PROOFS.md /opt/data/ATLAS-TRIAGE-PROOFS.md touch /opt/data/.env for key in ANTHROPIC_API_KEY API_SERVER_KEY CLAUDE_API_KEY CLAUDE_CODE_OAUTH_TOKEN GITEA_TOKEN GITEA_USERNAME HERMES_IMAGE_BROKER_KEY OPENAI_API_KEY; do grep -v "^${key}=" /opt/data/.env > /opt/data/.env.tmp || true mv /opt/data/.env.tmp /opt/data/.env done rm -f /opt/data/auth.json chmod 0600 /opt/data/.env chown -R 10000:10000 /opt/data securityContext: runAsUser: 0 runAsGroup: 0 volumeMounts: - name: home mountPath: /opt/data - name: config mountPath: /config - name: operator-guide mountPath: /guide resources: requests: cpu: 25m memory: 32Mi limits: cpu: 100m memory: 64Mi - name: stage-runtime-access image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 imagePullPolicy: IfNotPresent command: - /opt/hermes/.venv/bin/python - /opt/coordinator/stage_runtime_access.py - triage securityContext: allowPrivilegeEscalation: false runAsUser: 0 runAsGroup: 0 seccompProfile: type: RuntimeDefault volumeMounts: - {name: coordinator, mountPath: /opt/coordinator, readOnly: true} - {name: runtime-access, mountPath: /runtime-access} resources: requests: {cpu: 25m, memory: 32Mi} limits: {cpu: 100m, memory: 64Mi} - name: patch-auth image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 imagePullPolicy: IfNotPresent command: - /opt/hermes/.venv/bin/python - /opt/coordinator/patch_hermes_auth.py - /opt/hermes/hermes_cli/auth.py - /patched/auth.py securityContext: allowPrivilegeEscalation: false runAsUser: 10000 runAsGroup: 10000 seccompProfile: type: RuntimeDefault volumeMounts: - name: coordinator mountPath: /opt/coordinator readOnly: true - name: auth-patch mountPath: /patched resources: requests: cpu: 25m memory: 64Mi limits: cpu: 100m memory: 128Mi - name: patch-api-server-sessions image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 imagePullPolicy: IfNotPresent command: - /bin/sh - -ec - | /opt/hermes/.venv/bin/python /opt/coordinator/patch_api_server_sessions.py \ /opt/hermes/gateway/platforms/api_server.py /patched/api_server.py # The legacy lineage migration is already complete. Reopening the # RWO state database on every WebUI rollout made sidecar-only # releases wait on Longhorn I/O before Worker could start. env: - {name: HERMES_API_DEFAULT_PARENT_SESSION_ID, value: automated-triage} - name: HERMES_API_DEFAULT_PARENT_MATCH_PREFIXES value: "A static-analysis finding, not a build failure.||Use $triage-titan-test-failures." securityContext: allowPrivilegeEscalation: false runAsUser: 10000 runAsGroup: 10000 seccompProfile: type: RuntimeDefault volumeMounts: - {name: home, mountPath: /opt/data} - {name: coordinator, mountPath: /opt/coordinator, readOnly: true} - {name: api-server-patch, mountPath: /patched} resources: requests: {cpu: 25m, memory: 64Mi} limits: {cpu: 100m, memory: 128Mi} - name: patch-subprocess-secret-boundary image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 imagePullPolicy: IfNotPresent command: - /bin/sh - -ec - | /opt/hermes/.venv/bin/python /opt/coordinator/patch_subprocess_secret_boundary.py \ /opt/hermes/tools/environments/local.py /patched/local.py /opt/hermes/.venv/bin/python /opt/coordinator/patch_process_output_redaction.py \ /opt/hermes/tools/process_registry.py /patched/process_registry.py securityContext: allowPrivilegeEscalation: false runAsUser: 10000 runAsGroup: 10000 seccompProfile: type: RuntimeDefault volumeMounts: - {name: coordinator, mountPath: /opt/coordinator, readOnly: true} - {name: subprocess-secret-patch, mountPath: /patched} resources: requests: {cpu: 25m, memory: 64Mi} limits: {cpu: 100m, memory: 128Mi} - name: install-kubectl image: bitnami/kubectl@sha256:554ab88b1858e8424c55de37ad417b16f2a0e65d1607aa0f3fe3ce9b9f10b131 imagePullPolicy: IfNotPresent command: - /bin/sh - -c - | set -e cp "$(command -v kubectl)" /tools/kubectl chmod 0755 /tools/kubectl chown 10000:10000 /tools/kubectl securityContext: runAsUser: 0 runAsGroup: 0 volumeMounts: - name: tools mountPath: /tools resources: requests: cpu: 25m memory: 32Mi limits: cpu: 100m memory: 64Mi containers: - name: hermes image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 imagePullPolicy: IfNotPresent command: [/bin/sh, -ec] args: - | API_SERVER_KEY="$(tr -d '\r\n' < /runtime-access/triage-api-key)" test -n "${API_SERVER_KEY}" export API_SERVER_KEY exec /opt/hermes/.venv/bin/hermes gateway run --no-supervise ports: - name: api containerPort: 8642 protocol: TCP env: - name: HERMES_HOME value: /opt/data - name: HERMES_AUTH_FILE value: /runtime-access/hermes-auth.json - name: HOME value: /opt/data/home - name: PATH value: /opt/data/home/.local/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - name: HERMES_DASHBOARD value: "0" - name: HERMES_DASHBOARD_PUBLIC_URL value: https://triage.bstein.dev - name: API_SERVER_ENABLED value: "true" - name: API_SERVER_HOST value: 0.0.0.0 - name: API_SERVER_PORT value: "8642" - name: API_SERVER_CORS_ORIGINS value: https://triage.bstein.dev - name: VICTORIA_METRICS_URL value: http://victoria-metrics-single-server.monitoring.svc.cluster.local:8428 - name: ARIADNE_BASE_URL value: http://ariadne.maintenance.svc.cluster.local - name: JENKINS_BASE_URL value: http://jenkins.jenkins.svc.cluster.local:8080 - name: GITEA_BASE_URL value: https://scm.bstein.dev - name: GRAFANA_BASE_URL value: https://metrics.bstein.dev - name: HERMES_AUTO_ROUTER_PROFILE value: triage - name: HERMES_API_DEFAULT_PARENT_SESSION_ID value: automated-triage - name: HERMES_API_DEFAULT_PARENT_MATCH_PREFIXES value: "A static-analysis finding, not a build failure.||Use $triage-titan-test-failures." securityContext: allowPrivilegeEscalation: false runAsNonRoot: true runAsUser: 10000 runAsGroup: 10000 capabilities: drop: [ALL] seccompProfile: type: RuntimeDefault volumeMounts: - name: home mountPath: /opt/data - name: runtime-access mountPath: /runtime-access - name: auth-patch mountPath: /opt/hermes/hermes_cli/auth.py subPath: auth.py - name: api-server-patch mountPath: /opt/hermes/gateway/platforms/api_server.py subPath: api_server.py - name: subprocess-secret-patch mountPath: /opt/hermes/tools/environments/local.py subPath: local.py - name: subprocess-secret-patch mountPath: /opt/hermes/tools/process_registry.py subPath: process_registry.py - name: tools mountPath: /usr/local/bin/kubectl subPath: kubectl - name: triage-skill mountPath: /opt/data/workspace/skills/triage-titan-test-failures readOnly: true - name: mastery-skill mountPath: /opt/data/workspace/skills/master-hermes-on-atlas readOnly: true - name: service-health-skill mountPath: /opt/data/workspace/skills/triage-atlas-service-health readOnly: true - name: alert-tuning-skill mountPath: /opt/data/workspace/skills/tune-atlas-alerts readOnly: true - name: auto-router-plugin mountPath: /opt/data/plugins/auto-router readOnly: true startupProbe: tcpSocket: port: api periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 readinessProbe: tcpSocket: port: api initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 livenessProbe: tcpSocket: port: api initialDelaySeconds: 90 periodSeconds: 30 timeoutSeconds: 10 resources: requests: cpu: 500m memory: 1Gi limits: cpu: "2" memory: 4Gi - name: webui image: registry.bstein.dev/bstein/hermes-webui:git-5b1f8320729ebd1514ce83b7e695a02938e5cef8-build-36-release@sha256:dfdacf006af93add1bd811637c30f3d4fd16b6f7e24d81ddc56d2989aa31210a # {"$imagepolicy": "hermes:hermes-webui-release"} imagePullPolicy: IfNotPresent command: [/bin/sh, -ec] args: - | api_key="$(tr -d '\r\n' < /runtime-access/triage-api-key)" test -n "${api_key}" export API_SERVER_KEY="${api_key}" export HERMES_WEBUI_GATEWAY_API_KEY="${api_key}" exec /opt/hermes/.venv/bin/python /opt/hermes-webui/server.py ports: - {name: dashboard, containerPort: 8787, protocol: TCP} env: - {name: HERMES_HOME, value: /opt/data} - {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json} - {name: HOME, value: /opt/data/home} - {name: HERMES_WEBUI_AGENT_DIR, value: /opt/hermes} - {name: HERMES_WEBUI_HOST, value: 0.0.0.0} - {name: HERMES_WEBUI_PORT, value: "8787"} - {name: HERMES_WEBUI_STATE_DIR, value: /opt/data/webui} - {name: HERMES_WEBUI_DEFAULT_WORKSPACE, value: /opt/data/workspace} - {name: HERMES_WEBUI_CHAT_BACKEND, value: gateway} - {name: HERMES_WEBUI_GATEWAY_BASE_URL, value: 'http://127.0.0.1:8642'} - {name: HERMES_WEBUI_GATEWAY_USE_RUNS_API, value: "true"} - {name: HERMES_WEBUI_SKIP_ONBOARDING, value: "1"} - {name: HERMES_WEBUI_SECURE, value: "1"} - {name: HERMES_WEBUI_ALLOWED_ORIGINS, value: 'https://triage.bstein.dev'} - {name: HERMES_WEBUI_TRUST_FORWARDED_HOST, value: "1"} - {name: HERMES_WEBUI_TRUST_FORWARDED_PROTO, value: "1"} - {name: HERMES_ROUTER_PROFILE, value: triage} volumeMounts: - {name: home, mountPath: /opt/data} - {name: runtime-access, mountPath: /runtime-access, readOnly: true} - {name: tmp, mountPath: /tmp} readinessProbe: httpGet: {path: /health, port: dashboard} initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 5 livenessProbe: httpGet: {path: /health, port: dashboard} initialDelaySeconds: 30 periodSeconds: 30 timeoutSeconds: 10 securityContext: allowPrivilegeEscalation: false capabilities: drop: [ALL] readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 10000 runAsGroup: 10000 seccompProfile: type: RuntimeDefault resources: requests: {cpu: 50m, memory: 128Mi} limits: {cpu: 750m, memory: 1Gi} volumes: - name: home persistentVolumeClaim: claimName: hermes-home - name: runtime-access emptyDir: medium: Memory sizeLimit: 2Mi - name: config configMap: name: hermes-config - name: operator-guide configMap: name: hermes-operator-guide - name: tools emptyDir: {} - name: coordinator configMap: name: hermes-coordinator defaultMode: 0555 - name: auto-router-plugin configMap: name: hermes-auto-router-plugin - name: auth-patch emptyDir: {} - name: api-server-patch emptyDir: {} - name: subprocess-secret-patch emptyDir: {} - name: tmp emptyDir: sizeLimit: 256Mi - name: triage-skill configMap: name: hermes-triage-skill items: - key: SKILL.md path: SKILL.md - key: openai.yaml path: agents/openai.yaml - name: mastery-skill configMap: name: hermes-mastery-skill items: - key: SKILL.md path: SKILL.md - key: openai.yaml path: agents/openai.yaml - key: architecture.md path: references/architecture.md - key: curriculum.md path: references/curriculum.md - key: incident-drills.md path: references/incident-drills.md - key: mastery-rubric.md path: references/mastery-rubric.md - key: two-hour-proof-sprint.md path: references/two-hour-proof-sprint.md - name: service-health-skill configMap: name: hermes-service-health-skill items: - key: SKILL.md path: SKILL.md - key: openai.yaml path: agents/openai.yaml - key: service-map.md path: references/service-map.md - name: alert-tuning-skill configMap: name: hermes-alert-tuning-skill items: - key: SKILL.md path: SKILL.md - key: openai.yaml path: agents/openai.yaml - key: alert-review.md path: references/alert-review.md