atlas-iac/services/hermes/deployment.yaml

526 lines
20 KiB
YAML

# 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-ffa477585d225f99a82b4049aa461a66a5de4189-build-34-release@sha256:ce9adadf8cab3d20cd2d51db7e103d893c50c7cfea169a4ce7ef8c5b9707e548 # {"$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