632 lines
29 KiB
YAML
632 lines
29 KiB
YAML
# 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: 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: "20260809-terminal-recovery"
|
|
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, titan-18]
|
|
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/home/.kube \
|
|
/opt/data/herdr \
|
|
/opt/data/logs \
|
|
/opt/data/tools/bin \
|
|
/opt/data/workspace/coordinator \
|
|
/opt/data/workspace/projects \
|
|
/opt/data/workspace/skills
|
|
if [ ! -e /opt/data/home/.hermes ]; then
|
|
ln -s /opt/data /opt/data/home/.hermes
|
|
fi
|
|
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:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
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
|
|
ttyd_version="$("${tools}/bin/ttyd" --version 2>/dev/null || true)"
|
|
case "${ttyd_version}" in *1.7.7*) ttyd_ready=1 ;; *) ttyd_ready=0 ;; esac
|
|
if [ "${ttyd_ready}" != "1" ]; then
|
|
curl -fsSL -o "${tools}/bin/ttyd.tmp" https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.aarch64
|
|
printf '%s %s\n' b38acadd89d1d396a0f5649aa52c539edbad07f4bc7348b27b4f4b7219dd4165 "${tools}/bin/ttyd.tmp" | sha256sum -c -
|
|
chmod 0755 "${tools}/bin/ttyd.tmp"
|
|
mv "${tools}/bin/ttyd.tmp" "${tools}/bin/ttyd"
|
|
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
|
|
kubectl_version="$("${tools}/bin/kubectl" version --client --output=json 2>/dev/null || true)"
|
|
case "${kubectl_version}" in *\"gitVersion\":\"v1.33.3\"*) kubectl_ready=1 ;; *) kubectl_ready=0 ;; esac
|
|
if [ "${kubectl_ready}" != "1" ]; then
|
|
curl -fsSL -o "${tools}/bin/kubectl.tmp" https://dl.k8s.io/release/v1.33.3/bin/linux/arm64/kubectl
|
|
printf '%s %s\n' 3d514dbae5dc8c09f773df0ef0f5d449dfad05b3aca5c96b13565f886df345fd "${tools}/bin/kubectl.tmp" | sha256sum -c -
|
|
chmod 0755 "${tools}/bin/kubectl.tmp"
|
|
mv "${tools}/bin/kubectl.tmp" "${tools}/bin/kubectl"
|
|
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:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
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:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
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}
|
|
- name: install-herdr-integrations
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -ec
|
|
- |
|
|
herdr integration install codex
|
|
herdr integration install claude
|
|
herdr integration install hermes
|
|
env:
|
|
- {name: HERMES_HOME, value: /opt/data}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: CODEX_HOME, value: /opt/data/home/.codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude}
|
|
- {name: PATH, value: /opt/data/tools/bin:/opt/hermes/.venv/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: 25m, memory: 32Mi}
|
|
limits: {cpu: 250m, memory: 128Mi}
|
|
- name: prepare-ttyd-index
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_ttyd_index.py
|
|
- /opt/data/tools/bin/ttyd
|
|
- /ttyd-index/index.html
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: ttyd-index, mountPath: /ttyd-index}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 32Mi}
|
|
limits: {cpu: 250m, memory: 128Mi}
|
|
containers:
|
|
- name: hermes
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/opt/hermes/.venv/bin/hermes]
|
|
args: [gateway, run, --no-supervise]
|
|
ports:
|
|
- {name: api, containerPort: 8642, 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: KUBECONFIG, value: /opt/data/home/.kube/config}
|
|
- {name: PATH, value: /opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/.venv/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/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, value: "0"}
|
|
- {name: HERMES_DASHBOARD_PUBLIC_URL, value: https://agent.hermes.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://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}
|
|
- {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, 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
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 250m, memory: 512Mi}
|
|
limits: {cpu: "2", memory: 4Gi}
|
|
- name: webui
|
|
image: registry.bstein.dev/bstein/hermes-webui@sha256:a771858bd668d25e19c74864baea5425101c8cd5215d1ba3a312f3312ce6c5e1
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
api_key="$(sed -n 's/^API_SERVER_KEY=//p' /opt/data/.env | tail -n 1)"
|
|
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: /shared-auth/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://agent.hermes.bstein.dev}
|
|
- {name: HERMES_WEBUI_TRUST_FORWARDED_HOST, value: "1"}
|
|
- {name: HERMES_WEBUI_TRUST_FORWARDED_PROTO, value: "1"}
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: provider-auth, mountPath: /shared-auth, 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}
|
|
- name: herdr-tui
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
set -a
|
|
. /opt/data/.env
|
|
set +a
|
|
exec /opt/data/tools/bin/ttyd \
|
|
--writable \
|
|
--check-origin \
|
|
--interface 0.0.0.0 \
|
|
--port 7681 \
|
|
--cwd /opt/data/workspace \
|
|
--terminal-type xterm-256color \
|
|
--index /ttyd-index/index.html \
|
|
--client-option "titleFixed=Hermes Agent - HERDR" \
|
|
--client-option fontSize=15 \
|
|
/bin/sh -c '
|
|
while true; do
|
|
/opt/data/tools/bin/herdr
|
|
exit_code=$?
|
|
printf "\r\nHerdr disconnected (exit %s); reconnecting in 2 seconds...\r\n" "${exit_code}"
|
|
sleep 2
|
|
done
|
|
'
|
|
ports:
|
|
- {name: herdr-tui, containerPort: 7681, protocol: TCP}
|
|
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:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin}
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: tmp, mountPath: /tmp}
|
|
- {name: ttyd-index, mountPath: /ttyd-index, readOnly: true}
|
|
startupProbe:
|
|
tcpSocket: {port: herdr-tui}
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
failureThreshold: 60
|
|
readinessProbe:
|
|
tcpSocket: {port: herdr-tui}
|
|
periodSeconds: 10
|
|
timeoutSeconds: 3
|
|
livenessProbe:
|
|
tcpSocket: {port: herdr-tui}
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
timeoutSeconds: 5
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 500m, memory: 512Mi}
|
|
- name: herdr-server
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
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
|
|
pane_file=/opt/data/herdr/coordinator-pane-id
|
|
pane="$(cat "${pane_file}" 2>/dev/null || true)"
|
|
pane_live=no
|
|
if [ -n "${pane}" ]; then
|
|
for attempt in $(seq 1 20); do
|
|
if herdr pane process-info --pane "${pane}" >/dev/null 2>&1; then
|
|
pane_live=yes
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
fi
|
|
if [ "${pane_live}" != "yes" ]; then
|
|
if [ -n "${pane}" ]; then
|
|
workspace="${pane%%:*}"
|
|
herdr workspace close "${workspace}" >/dev/null 2>&1 || true
|
|
fi
|
|
created="$(herdr workspace create \
|
|
--cwd /opt/data/workspace \
|
|
--label coordinator \
|
|
--env HERMES_HOME=/opt/data \
|
|
--env HERMES_AUTH_FILE=/shared-auth/auth.json \
|
|
--env HOME=/opt/data/home \
|
|
--env KUBECONFIG=/opt/data/home/.kube/config \
|
|
--env PYTHONPATH=/opt/hermes \
|
|
--env PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright \
|
|
--env AGENT_BROWSER_EXECUTABLE_PATH=/opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell \
|
|
--env AGENT_BROWSER_ARGS=--no-sandbox,--disable-dev-shm-usage \
|
|
--focus)"
|
|
pane="$(printf '%s' "${created}" | /opt/hermes/.venv/bin/python -c \
|
|
'import json,sys; print(json.load(sys.stdin)["result"]["root_pane"]["pane_id"])')"
|
|
printf '%s\n' "${pane}" > "${pane_file}"
|
|
fi
|
|
active_workspace="${pane%%:*}"
|
|
coordinator_workspaces="$(herdr workspace list | /opt/hermes/.venv/bin/python -c \
|
|
'import json,sys; active=sys.argv[1]; payload=json.load(sys.stdin); print(" ".join(item["workspace_id"] for item in payload["result"]["workspaces"] if item.get("label") == "coordinator" and item["workspace_id"] != active))' \
|
|
"${active_workspace}")"
|
|
for stale_workspace in ${coordinator_workspaces}; do
|
|
herdr workspace close "${stale_workspace}" >/dev/null 2>&1 || true
|
|
done
|
|
process_info="$(herdr pane process-info --pane "${pane}" 2>/dev/null || printf '%s\n' '{"result":{"process_info":{"foreground_processes":[]}}}')"
|
|
coordinator_running="$(printf '%s' "${process_info}" | /opt/hermes/.venv/bin/python -c \
|
|
'import json,sys; processes=json.load(sys.stdin)["result"]["process_info"].get("foreground_processes", []); print("yes" if any("hermes" in " ".join(str(p.get(k) or "") for k in ("name", "argv0", "cmdline")) for p in processes) else "no")')"
|
|
if [ "${coordinator_running}" != "yes" ]; then
|
|
herdr agent start coordinator \
|
|
--kind hermes \
|
|
--pane "${pane}" \
|
|
--timeout 60000
|
|
fi
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/herdr_tab_router.py \
|
|
--workspace-label coordinator \
|
|
--interval 1 &
|
|
router_pid=$!
|
|
trap 'kill "${router_pid}" "${server_pid}" 2>/dev/null || true' TERM INT
|
|
wait "${server_pid}"
|
|
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: KUBECONFIG, value: /opt/data/home/.kube/config}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: PATH, value: /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"}
|
|
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}
|
|
- {name: coordinator, mountPath: /opt/data/home/.local/bin/herdr-dispatch, subPath: herdr_dispatch.py, readOnly: true}
|
|
- {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true}
|
|
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
|
resources:
|
|
requests: {cpu: 50m, memory: 128Mi}
|
|
limits: {cpu: "1", memory: 2Gi}
|
|
- name: model-steward
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:7a1daefae2f068dcf14e7eb1f2f9aab2e1ce79c1b55b4bb0aa1092fbf4019bbc
|
|
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: kubeconfig
|
|
configMap:
|
|
name: hermes-agent-kubeconfig
|
|
defaultMode: 0444
|
|
- name: auth-patch
|
|
emptyDir: {}
|
|
- name: auto-router-plugin
|
|
configMap:
|
|
name: hermes-auto-router-plugin
|
|
- name: tmp
|
|
emptyDir:
|
|
sizeLimit: 256Mi
|
|
- name: ttyd-index
|
|
emptyDir:
|
|
sizeLimit: 2Mi
|