# services/hermes/agent-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hermes-agent
  namespace: hermes
  labels:
    app: hermes-agent
spec:
  replicas: 1
  revisionHistoryLimit: 2
  progressDeadlineSeconds: 2700
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: hermes-agent
  template:
    metadata:
      labels:
        app: hermes-agent
      annotations:
        ai.bstein.dev/role: project-coordinator
        ai.bstein.dev/execution: Herdr-supervised Codex and Claude Code
        ai.bstein.dev/model-policy: difficulty-aware low through xhigh, cross-provider fallback
        ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available
        ai.bstein.dev/config-rev: "20260808-herdr-coordinator"
        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
        vault.hashicorp.com/agent-inject-template-anthropic-token: |
          {{- with secret "kv/data/atlas/hermes/agent-tokens" -}}
          {{ .Data.data.anthropic_oauth_token }}
          {{- end }}
        vault.hashicorp.com/agent-inject-secret-gitea-token: kv/data/atlas/hermes/agent-tokens
        vault.hashicorp.com/agent-inject-template-gitea-token: |
          {{- with secret "kv/data/atlas/hermes/agent-tokens" -}}
          {{- with index .Data.data "gitea_token" -}}
          {{ . }}
          {{- end -}}
          {{- end }}
        vault.hashicorp.com/agent-pre-populate-only: "true"
        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-agent
      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: kubernetes.io/hostname
                    operator: NotIn
                    values: [titan-08, titan-13, titan-14, titan-17]
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: hardware
                    operator: In
                    values: [rpi5]
      initContainers:
        - name: init-config
          image: busybox:1.37
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - |
              set -eu
              env_file=/opt/data/.env
              mkdir -p \
                /opt/data/home/.claude \
                /opt/data/home/.codex \
                /opt/data/home/.config/herdr \
                /opt/data/herdr \
                /opt/data/logs \
                /opt/data/tools/bin \
                /opt/data/workspace/coordinator \
                /opt/data/workspace/projects \
                /opt/data/workspace/skills
              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
              touch "${env_file}"
              upsert_env() {
                key="$1"
                value="$2"
                { grep -v "^${key}=" "${env_file}" || true; printf '%s=%s\n' "${key}" "${value}"; } > "${env_file}.tmp"
                mv "${env_file}.tmp" "${env_file}"
              }
              if ! grep -q '^API_SERVER_KEY=' "${env_file}"; then
                api_key="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')"
                upsert_env API_SERVER_KEY "${api_key}"
              fi
              if [ -s /vault/secrets/anthropic-token ]; then
                token="$(tr -d '\r\n' < /vault/secrets/anthropic-token)"
                [ -z "${token}" ] || upsert_env CLAUDE_CODE_OAUTH_TOKEN "${token}"
              fi
              if [ -s /vault/secrets/gitea-token ]; then
                token="$(tr -d '\r\n' < /vault/secrets/gitea-token)"
                case "${token}" in ""|"<no value>"|"<nil>") ;; *) upsert_env GITEA_TOKEN "${token}" ;; esac
              fi
              upsert_env GITEA_USERNAME bstein
              upsert_env GIT_ASKPASS /opt/coordinator/gitea_askpass.sh
              upsert_env GIT_TERMINAL_PROMPT 0
              chmod 0600 "${env_file}"
              chown -R 10000:10000 /opt/data
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 0
            runAsGroup: 0
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: home
              mountPath: /opt/data
            - name: config
              mountPath: /config
              readOnly: true
          resources:
            requests: {cpu: 25m, memory: 32Mi}
            limits: {cpu: 100m, memory: 64Mi}
        - name: install-agent-tools
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - |
              set -eu
              tools=/opt/data/tools
              mkdir -p "${tools}/bin"
              herdr_version="$("${tools}/bin/herdr" --version 2>/dev/null || true)"
              case "${herdr_version}" in *0.8.0*) herdr_ready=1 ;; *) herdr_ready=0 ;; esac
              if [ "${herdr_ready}" != "1" ]; then
                curl -fsSL -o "${tools}/bin/herdr.tmp" https://github.com/herdrdev/herdr/releases/download/v0.8.0/herdr-linux-aarch64
                printf '%s  %s\n' f647ac66468d9efbc642fe534fb284468f0aea60641606fc008dfc0d82a3ca87 "${tools}/bin/herdr.tmp" | sha256sum -c -
                chmod 0755 "${tools}/bin/herdr.tmp"
                mv "${tools}/bin/herdr.tmp" "${tools}/bin/herdr"
              fi
              if [ ! -f "${tools}/.cli-versions-0.147.0-2.1.226" ]; then
                npm install --global --omit=dev --no-audit --no-fund --prefix "${tools}" \
                  @openai/codex@0.147.0 \
                  @anthropic-ai/claude-code@2.1.226
                touch "${tools}/.cli-versions-0.147.0-2.1.226"
              fi
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 10000
            runAsGroup: 10000
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: home
              mountPath: /opt/data
          resources:
            requests: {cpu: 100m, memory: 256Mi}
            limits: {cpu: "1", memory: 1Gi}
        - name: patch-auth
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          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: bootstrap-coordinator
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          command:
            - /opt/hermes/.venv/bin/python
            - /opt/coordinator/hermes_coordinator.py
            - --once
          env:
            - {name: HERMES_HOME, value: /opt/data}
            - {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json}
            - {name: HOME, value: /opt/data/home}
            - {name: PYTHONPATH, value: /opt/hermes}
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 10000
            runAsGroup: 10000
            seccompProfile:
              type: RuntimeDefault
          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}
          resources:
            requests: {cpu: 50m, memory: 128Mi}
            limits: {cpu: 500m, memory: 512Mi}
      containers:
        - name: hermes
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          command: [/opt/hermes/.venv/bin/hermes]
          args: [gateway, run]
          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: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml}
            - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock}
            - {name: PATH, value: /opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
            - {name: HERMES_DASHBOARD, value: "1"}
            - {name: HERMES_DASHBOARD_HOST, value: 0.0.0.0}
            - {name: HERMES_DASHBOARD_PORT, value: "9119"}
            - {name: HERMES_DASHBOARD_PUBLIC_URL, value: https://agent.hermes.bstein.dev}
          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: coordinator, mountPath: /opt/data/home/.local/bin/herdr-dispatch, subPath: herdr_dispatch.py, readOnly: true}
          startupProbe:
            httpGet: {path: /api/status, port: dashboard}
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 60
          readinessProbe:
            httpGet: {path: /api/status, port: dashboard}
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            httpGet: {path: /api/status, port: dashboard}
            initialDelaySeconds: 90
            periodSeconds: 30
            timeoutSeconds: 10
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 10000
            runAsGroup: 10000
            seccompProfile:
              type: RuntimeDefault
          resources:
            requests: {cpu: 250m, memory: 512Mi}
            limits: {cpu: "2", memory: 4Gi}
        - name: herdr-server
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - |
              set -eu
              set -a
              . /opt/data/.env
              set +a
              herdr server &
              server_pid=$!
              trap 'kill "${server_pid}" 2>/dev/null || true' TERM INT
              for attempt in $(seq 1 60); do
                if herdr status server >/dev/null 2>&1; then break; fi
                sleep 1
              done
              herdr integration install codex || true
              herdr integration install claude || true
              wait "${server_pid}"
          env:
            - {name: HOME, value: /opt/data/home}
            - {name: CODEX_HOME, value: /opt/data/home/.codex}
            - {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude}
            - {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml}
            - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock}
            - {name: PATH, value: /opt/data/tools/bin:/usr/local/bin:/usr/bin:/bin}
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 10000
            runAsGroup: 10000
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - {name: home, mountPath: /opt/data}
          resources:
            requests: {cpu: 50m, memory: 128Mi}
            limits: {cpu: "1", memory: 2Gi}
        - name: model-steward
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          command: [/opt/hermes/.venv/bin/python, /opt/coordinator/hermes_coordinator.py, --loop, --interval, "3600"]
          env:
            - {name: HERMES_HOME, value: /opt/data}
            - {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json}
            - {name: HOME, value: /opt/data/home}
            - {name: PYTHONPATH, value: /opt/hermes}
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 10000
            runAsGroup: 10000
            seccompProfile:
              type: RuntimeDefault
          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}
          resources:
            requests: {cpu: 25m, memory: 64Mi}
            limits: {cpu: 250m, memory: 512Mi}
      volumes:
        - name: home
          persistentVolumeClaim:
            claimName: hermes-agent-home
        - name: provider-auth
          persistentVolumeClaim:
            claimName: hermes-provider-auth
        - name: config
          configMap:
            name: hermes-agent-config
        - name: coordinator
          configMap:
            name: hermes-coordinator
            defaultMode: 0555
        - name: auth-patch
          emptyDir: {}
