1616 lines
79 KiB
YAML
1616 lines
79 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/router-wire-contract: ollama-numeric-keepalive
|
|
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: primary amd64 accelerator titan-22; arm64 rpi5 fleet fallback; storage-backbone nodes excluded
|
|
ai.bstein.dev/config-rev: "20260913-soteria-kanban-recovery-v2"
|
|
prometheus.io/scrape: "true"
|
|
prometheus.io/path: /metrics
|
|
prometheus.io/port: "9010"
|
|
vault.hashicorp.com/agent-inject: "true"
|
|
vault.hashicorp.com/role: hermes-agent
|
|
vault.hashicorp.com/agent-inject-containers: stage-runtime-access
|
|
vault.hashicorp.com/agent-inject-secret-agent-api-key: kv/data/atlas/hermes/agent-tokens
|
|
vault.hashicorp.com/agent-inject-template-agent-api-key: |
|
|
{{- with secret "kv/data/atlas/hermes/agent-tokens" -}}
|
|
{{ .Data.data.agent_api_key }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-claude-credentials: kv/data/atlas/hermes/agent-tokens
|
|
vault.hashicorp.com/agent-inject-template-claude-credentials: |
|
|
{{- with secret "kv/data/atlas/hermes/agent-tokens" -}}
|
|
{{ .Data.data.claude_credentials_json }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-claude-oauth-token: kv/data/atlas/hermes/agent-tokens
|
|
vault.hashicorp.com/agent-inject-template-claude-oauth-token: |
|
|
{{- with secret "kv/data/atlas/hermes/agent-tokens" -}}
|
|
{{ .Data.data.claude_oauth_token }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-codex-auth: kv/data/atlas/hermes/agent-tokens
|
|
vault.hashicorp.com/agent-inject-template-codex-auth: |
|
|
{{- with secret "kv/data/atlas/hermes/agent-tokens" -}}
|
|
{{ .Data.data.codex_auth_json }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-jenkins-image-build-token: kv/data/atlas/hermes/developer-jenkins
|
|
vault.hashicorp.com/agent-inject-template-jenkins-image-build-token: |
|
|
{{- with secret "kv/data/atlas/hermes/developer-jenkins" -}}
|
|
{{ .Data.data.build_token }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-node-ssh-private-key: kv/data/atlas/hermes/developer-ssh
|
|
vault.hashicorp.com/agent-inject-template-node-ssh-private-key: |
|
|
{{- with secret "kv/data/atlas/hermes/developer-ssh" -}}
|
|
{{ .Data.data.private_key }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-node-ssh-config: kv/data/atlas/hermes/developer-ssh
|
|
vault.hashicorp.com/agent-inject-template-node-ssh-config: |
|
|
{{- with secret "kv/data/atlas/hermes/developer-ssh" -}}
|
|
{{ .Data.data.config }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-node-ssh-known-hosts: kv/data/atlas/hermes/developer-ssh
|
|
vault.hashicorp.com/agent-inject-template-node-ssh-known-hosts: |
|
|
{{- with secret "kv/data/atlas/hermes/developer-ssh" -}}
|
|
{{ .Data.data.known_hosts }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-chat-relay-key: kv/data/atlas/hermes/chat-telegram
|
|
vault.hashicorp.com/agent-inject-template-chat-relay-key: |
|
|
{{- with secret "kv/data/atlas/hermes/chat-telegram" -}}
|
|
{{ .Data.data.relay_key }}
|
|
{{- end }}
|
|
vault.hashicorp.com/agent-inject-secret-oidc-config: kv/data/atlas/hermes/agent-oidc
|
|
vault.hashicorp.com/agent-inject-template-oidc-config: |
|
|
{{- with secret "kv/data/atlas/hermes/agent-oidc" -}}
|
|
client_id = "{{ .Data.data.client_id }}"
|
|
client_secret = "{{ .Data.data.client_secret }}"
|
|
cookie_secret = "{{ .Data.data.cookie_secret }}"
|
|
{{- 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
|
|
enableServiceLinks: false
|
|
automountServiceAccountToken: true
|
|
securityContext:
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
# titan-22 is the STRONG/primary home for hermes-agent now that both the
|
|
# agent image (a68d1c4d, via the kustomize images: override) and the hux
|
|
# sidecar (build-39) are multi-arch. The arm64 pi fleet stays as an OR'd
|
|
# fallback so the worker is never stranded if titan-22 is unavailable.
|
|
affinity:
|
|
nodeAffinity:
|
|
requiredDuringSchedulingIgnoredDuringExecution:
|
|
nodeSelectorTerms:
|
|
# Fallback set: the arm64 pi fleet (unchanged). nodeSelectorTerms
|
|
# are OR'd, so hermes-agent still runs here if titan-22 is gone.
|
|
- 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-04, titan-06, titan-08, titan-13, titan-14, titan-17, titan-18, titan-19]
|
|
# Primary amd64 home: accelerator titan-22. No worker=true here.
|
|
- matchExpressions:
|
|
- key: kubernetes.io/arch
|
|
operator: In
|
|
values: [amd64]
|
|
- key: node-role.kubernetes.io/accelerator
|
|
operator: In
|
|
values: ["true"]
|
|
- key: kubernetes.io/hostname
|
|
operator: In
|
|
values: [titan-22]
|
|
preferredDuringSchedulingIgnoredDuringExecution:
|
|
# STRONG primary preference: titan-22 (weight 100, the scheduler
|
|
# maximum) outranks the pi-fleet nudge below, so hermes-agent
|
|
# actually lives on titan-22 whenever it is schedulable.
|
|
- weight: 100
|
|
preference:
|
|
matchExpressions:
|
|
- key: kubernetes.io/hostname
|
|
operator: In
|
|
values: [titan-22]
|
|
# Weaker fallback nudge within the pi fleet.
|
|
- weight: 50
|
|
preference:
|
|
matchExpressions:
|
|
- key: hardware
|
|
operator: In
|
|
values: [rpi5]
|
|
tolerations:
|
|
# Permit (do not force) scheduling onto the hard-tainted accelerator
|
|
# titan-22. Harmless where the taint is absent; only lets hermes-agent
|
|
# consider titan-22. Does not change jellyfin's media-core priority.
|
|
- key: node-role.kubernetes.io/accelerator
|
|
operator: Equal
|
|
value: "true"
|
|
effect: NoSchedule
|
|
# titan-22 currently carries the soft media-primary taint (media node);
|
|
# tolerate it so the weight-100 titan-22 preference is not offset by the
|
|
# PreferNoSchedule penalty and placement on titan-22 is deterministic.
|
|
- key: atlas.bstein.dev/media-primary
|
|
operator: Equal
|
|
value: "true"
|
|
effect: PreferNoSchedule
|
|
initContainers:
|
|
- name: init-config
|
|
image: busybox:1.37
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
set -eu
|
|
env_file=/opt/data/.env
|
|
profile_file=/opt/data/home/.profile
|
|
mkdir -p \
|
|
/opt/data/home/.claude \
|
|
/opt/data/home/.codex \
|
|
/opt/data/home/.kube \
|
|
/opt/data/home/.ssh \
|
|
/opt/data/cli-lanes \
|
|
/opt/data/logs \
|
|
/opt/data/provider-health \
|
|
/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
|
|
# OpenSSH resolves ~/.ssh from the account's passwd home
|
|
# (/opt/data), not the HOME override used by Hermes. Keep one
|
|
# Vault-populated directory visible through both paths.
|
|
if [ ! -e /opt/data/.ssh ] && [ ! -L /opt/data/.ssh ]; then
|
|
ln -s home/.ssh /opt/data/.ssh
|
|
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
|
|
rm -f \
|
|
/opt/data/auth.json \
|
|
/opt/data/home/.claude/.credentials.json \
|
|
/opt/data/home/.codex/auth.json \
|
|
/opt/data/home/.ssh/config \
|
|
/opt/data/home/.ssh/known_hosts \
|
|
/opt/data/home/.ssh/id_ed25519_atlas_nodes
|
|
ln -s /runtime-access/node-ssh-config /opt/data/home/.ssh/config
|
|
ln -s /runtime-access/node-ssh-known-hosts /opt/data/home/.ssh/known_hosts
|
|
ln -s /runtime-access/node-ssh-private-key /opt/data/home/.ssh/id_ed25519_atlas_nodes
|
|
chmod 0700 /opt/data/home/.ssh
|
|
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}"
|
|
}
|
|
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}=" "${env_file}" > "${env_file}.tmp" || true
|
|
mv "${env_file}.tmp" "${env_file}"
|
|
done
|
|
for profile_env in /opt/data/profiles/*/.env; do
|
|
[ -f "${profile_env}" ] || continue
|
|
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}=" "${profile_env}" > "${profile_env}.tmp" || true
|
|
mv "${profile_env}.tmp" "${profile_env}"
|
|
done
|
|
chmod 0600 "${profile_env}"
|
|
chown 10000:10000 "${profile_env}"
|
|
done
|
|
upsert_env GIT_TERMINAL_PROMPT 0
|
|
upsert_env HERMES_SCM_BROKER_URL http://hermes-scm-broker.hermes-scm.svc.cluster.local:9081
|
|
upsert_env JENKINS_BASE_URL http://jenkins.jenkins.svc.cluster.local:8080
|
|
upsert_env ARIADNE_BASE_URL http://ariadne.maintenance.svc.cluster.local
|
|
upsert_env VICTORIA_METRICS_URL http://victoria-metrics-single-server.monitoring.svc.cluster.local:8428
|
|
upsert_env GRAFANA_BASE_URL https://metrics.bstein.dev
|
|
chmod 0600 "${env_file}"
|
|
touch "${profile_file}"
|
|
if ! grep -qxF '# Hermes managed operator PATH.' "${profile_file}"; then
|
|
printf '%s\n' \
|
|
'' \
|
|
'# Hermes managed operator PATH.' \
|
|
'case ":${PATH}:" in' \
|
|
' *":/opt/data/tools/bin:"*) ;;' \
|
|
' *) PATH="/opt/coordinator:/opt/data/tools/bin:/opt/hermes/.venv/bin:/opt/hermes/bin:${PATH}" ;;' \
|
|
'esac' \
|
|
'export PATH' >> "${profile_file}"
|
|
fi
|
|
chmod 0644 "${profile_file}"
|
|
# Existing owner data is already written as uid/gid 10000. A
|
|
# recursive chown made every routine rollout walk the full 20Gi
|
|
# workspace while the dashboard had no endpoint. Own only the
|
|
# paths this init container creates or updates.
|
|
chown 10000:10000 \
|
|
/opt/data \
|
|
/opt/data/home \
|
|
/opt/data/home/.claude \
|
|
/opt/data/home/.codex \
|
|
/opt/data/home/.kube \
|
|
/opt/data/home/.ssh \
|
|
/opt/data/cli-lanes \
|
|
/opt/data/logs \
|
|
/opt/data/provider-health \
|
|
/opt/data/tools \
|
|
/opt/data/tools/bin \
|
|
/opt/data/workspace \
|
|
/opt/data/workspace/coordinator \
|
|
/opt/data/workspace/projects \
|
|
/opt/data/workspace/skills \
|
|
/opt/data/config.yaml \
|
|
/opt/data/SOUL.md \
|
|
/opt/data/workspace/AGENTS.md \
|
|
/opt/data/workspace/START-HERE.md \
|
|
"${profile_file}" \
|
|
"${env_file}"
|
|
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}
|
|
# Worker HUX identity provisioning. Mirrors the chat tenants'
|
|
# init-hux-runtime, adapted to the single-replica Deployment: the
|
|
# subtree name is the fixed literal "hux" (Deployment pod names churn,
|
|
# so ${HOSTNAME} would break identity persistence across restarts) on
|
|
# the durable hermes-agent-home claim, and the slot is pinned to
|
|
# slot-100 — outside the chat tenants' slot-0..slot-3 range. Only a
|
|
# worker transport key is provisioned: the Worker has no relay or
|
|
# human browser surface, so relay trust stays fail-closed by absence.
|
|
- name: init-hux-runtime
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
umask 077
|
|
worker_root="/hux-data/hux"
|
|
export HUX_INIT_ROOT="${worker_root}"
|
|
mkdir -p \
|
|
"${worker_root}/binding" \
|
|
"${worker_root}/context" \
|
|
"${worker_root}/store" \
|
|
/hux-worker
|
|
chown 10000:10000 \
|
|
"${worker_root}" \
|
|
"${worker_root}/binding" \
|
|
"${worker_root}/context" \
|
|
"${worker_root}/store" \
|
|
/hux-worker
|
|
chmod 0700 \
|
|
"${worker_root}" \
|
|
"${worker_root}/binding" \
|
|
"${worker_root}/context" \
|
|
"${worker_root}/store" \
|
|
/hux-worker
|
|
if [ ! -e "${worker_root}/context/context-key" ]; then
|
|
dd if=/dev/urandom of="${worker_root}/context/.context-key.tmp" bs=32 count=1 2>/dev/null
|
|
chown 10000:10000 "${worker_root}/context/.context-key.tmp"
|
|
chmod 0600 "${worker_root}/context/.context-key.tmp"
|
|
mv "${worker_root}/context/.context-key.tmp" "${worker_root}/context/context-key"
|
|
fi
|
|
test "$(wc -c < "${worker_root}/context/context-key")" -eq 32
|
|
chown 10000:10000 "${worker_root}/context/context-key"
|
|
chmod 0600 "${worker_root}/context/context-key"
|
|
HUX_INIT_SLOT="slot-100" \
|
|
/opt/hermes/.venv/bin/python - <<'PY'
|
|
import hashlib
|
|
import hmac
|
|
import os
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
root = Path(os.environ["HUX_INIT_ROOT"])
|
|
key = (root / "context/context-key").read_bytes()
|
|
slot = os.environ["HUX_INIT_SLOT"]
|
|
subject = "usr_" + hmac.new(
|
|
key,
|
|
b"hux.subject.id.v1\0" + slot.encode("ascii"),
|
|
hashlib.sha256,
|
|
).hexdigest()
|
|
target = root / "binding/subject"
|
|
expected = (subject + "\n").encode("ascii")
|
|
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
|
|
try:
|
|
descriptor = os.open(target, flags, 0o440)
|
|
except FileExistsError:
|
|
info = target.lstat()
|
|
if (
|
|
not stat.S_ISREG(info.st_mode)
|
|
or info.st_uid != 10000
|
|
or stat.S_IMODE(info.st_mode) != 0o440
|
|
or info.st_nlink != 1
|
|
or target.read_bytes() != expected
|
|
):
|
|
raise SystemExit("persistent HUX subject binding is unsafe")
|
|
else:
|
|
try:
|
|
os.write(descriptor, expected)
|
|
os.fchown(descriptor, 10000, 10000)
|
|
os.fchmod(descriptor, 0o440)
|
|
os.fsync(descriptor)
|
|
finally:
|
|
os.close(descriptor)
|
|
PY
|
|
if [ ! -e "${worker_root}/context/redaction-canary" ]; then
|
|
dd if=/dev/urandom bs=32 count=1 2>/dev/null \
|
|
| sha256sum | cut -d ' ' -f 1 \
|
|
> "${worker_root}/context/.redaction-canary.tmp"
|
|
chown 10000:10000 "${worker_root}/context/.redaction-canary.tmp"
|
|
chmod 0400 "${worker_root}/context/.redaction-canary.tmp"
|
|
mv "${worker_root}/context/.redaction-canary.tmp" "${worker_root}/context/redaction-canary"
|
|
fi
|
|
target=/hux-worker/worker-key
|
|
if [ ! -e "${target}" ]; then
|
|
dd if=/dev/urandom bs=32 count=1 2>/dev/null \
|
|
| sha256sum | cut -d ' ' -f 1 > "${target}.tmp"
|
|
chown 10000:10000 "${target}.tmp"
|
|
chmod 0400 "${target}.tmp"
|
|
mv "${target}.tmp" "${target}"
|
|
fi
|
|
test "$(wc -c < "${target}")" -eq 65
|
|
chown 10000:10000 "${target}"
|
|
chmod 0400 "${target}"
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
add: [CHOWN, DAC_OVERRIDE, FOWNER]
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /hux-data}
|
|
- {name: hux-worker-key, mountPath: /hux-worker}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 16Mi}
|
|
limits: {cpu: 50m, memory: 32Mi}
|
|
- 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
|
|
- agent
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access}
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: oauth2-config, mountPath: /oauth2-config}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 32Mi}
|
|
limits: {cpu: 100m, memory: 64Mi}
|
|
- name: install-agent-tools
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
set -eu
|
|
tools=/opt/data/tools
|
|
mkdir -p "${tools}/bin"
|
|
# The tools volume is shared across whatever node the pod lands
|
|
# on, so this install must resolve the running node's arch instead
|
|
# of a hardcoded one. Fail closed on anything but the two arches we
|
|
# publish images for.
|
|
arch="$(uname -m)"
|
|
case "${arch}" in
|
|
aarch64)
|
|
ttyd_asset=ttyd.aarch64
|
|
ttyd_sha=b38acadd89d1d396a0f5649aa52c539edbad07f4bc7348b27b4f4b7219dd4165
|
|
kubectl_arch=arm64
|
|
kubectl_sha=3d514dbae5dc8c09f773df0ef0f5d449dfad05b3aca5c96b13565f886df345fd
|
|
;;
|
|
x86_64)
|
|
ttyd_asset=ttyd.x86_64
|
|
ttyd_sha=8a217c968aba172e0dbf3f34447218dc015bc4d5e59bf51db2f2cd12b7be4f55
|
|
kubectl_arch=amd64
|
|
kubectl_sha=2fcf65c64f352742dc253a25a7c95617c2aba79843d1b74e585c69fe4884afb0
|
|
;;
|
|
*)
|
|
echo "unsupported architecture for agent tools: ${arch}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
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_asset}"
|
|
printf '%s %s\n' "${ttyd_sha}" "${tools}/bin/ttyd.tmp" | sha256sum -c -
|
|
chmod 0755 "${tools}/bin/ttyd.tmp"
|
|
mv "${tools}/bin/ttyd.tmp" "${tools}/bin/ttyd"
|
|
fi
|
|
# npm materializes codex's native optional dep only for the current
|
|
# platform, and a sibling-arch `npm install` on the SHARED volume
|
|
# removes this arch's native binary (node_modules is shared across
|
|
# nodes). So re-run the install whenever this arch's native codex
|
|
# package is absent — not only when the arch stamp is missing —
|
|
# which self-heals after the pod has run on the other architecture.
|
|
case "$(uname -m)" in x86_64) nodearch=x64 ;; aarch64) nodearch=arm64 ;; *) nodearch=unknown ;; esac
|
|
codex_native="${tools}/lib/node_modules/@openai/codex-linux-${nodearch}"
|
|
codex_version="$("${tools}/bin/codex" --version 2>/dev/null || true)"
|
|
claude_version="$("${tools}/bin/claude" --version 2>/dev/null || true)"
|
|
case "${codex_version}" in *0.154.0*) codex_ready=1 ;; *) codex_ready=0 ;; esac
|
|
case "${claude_version}" in *2.1.226*) claude_ready=1 ;; *) claude_ready=0 ;; esac
|
|
if [ ! -f "${tools}/.cli-versions-0.154.0-2.1.226-${arch}" ] || [ ! -d "${codex_native}" ] \
|
|
|| [ "${codex_ready}" != "1" ] || [ "${claude_ready}" != "1" ]; then
|
|
# Interrupted npm global installs leave only these private
|
|
# staging directories; never follow or remove other entries.
|
|
for scope in "${tools}/lib/node_modules/@anthropic-ai" "${tools}/lib/node_modules/@openai"; do
|
|
[ -d "${scope}" ] && [ ! -L "${scope}" ] || continue
|
|
case "${scope}" in
|
|
*/@anthropic-ai) find "${scope}" -mindepth 1 -maxdepth 1 -type d -name '.claude-code-*' -exec rm -rf -- {} + ;;
|
|
*/@openai) find "${scope}" -mindepth 1 -maxdepth 1 -type d -name '.codex-*' -exec rm -rf -- {} + ;;
|
|
esac
|
|
done
|
|
npm install --global --omit=dev --no-audit --no-fund --prefix "${tools}" \
|
|
@openai/codex@0.154.0 \
|
|
@anthropic-ai/claude-code@2.1.226
|
|
touch "${tools}/.cli-versions-0.154.0-2.1.226-${arch}"
|
|
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/${kubectl_arch}/kubectl"
|
|
printf '%s %s\n' "${kubectl_sha}" "${tools}/bin/kubectl.tmp" | sha256sum -c -
|
|
chmod 0755 "${tools}/bin/kubectl.tmp"
|
|
mv "${tools}/bin/kubectl.tmp" "${tools}/bin/kubectl"
|
|
fi
|
|
/bin/sh /opt/coordinator/install_agent_tools.sh
|
|
HOME=/opt/data/home git config --global user.name "Hermes Agent"
|
|
HOME=/opt/data/home git config --global user.email "hermes@bstein.dev"
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- name: home
|
|
mountPath: /opt/data
|
|
- name: coordinator
|
|
mountPath: /opt/coordinator
|
|
readOnly: true
|
|
resources:
|
|
requests: {cpu: 100m, memory: 256Mi}
|
|
limits: {cpu: "1", memory: 1Gi}
|
|
- name: repair-cassandra-kanban
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/repair_cassandra_kanban.py
|
|
- --database
|
|
- /opt/data/kanban/boards/cassandra/kanban.db
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 250m, memory: 256Mi}
|
|
- name: recover-cassandra-workers
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/recover_cassandra_workers.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 250m, memory: 256Mi}
|
|
- 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-tui-gateway
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_tui_gateway.py
|
|
- /opt/hermes/tui_gateway/server.py
|
|
- /patched/server.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- name: coordinator
|
|
mountPath: /opt/coordinator
|
|
readOnly: true
|
|
- name: tui-gateway-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:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_api_server_sessions.py
|
|
- /opt/hermes/gateway/platforms/api_server.py
|
|
- /patched/api_server.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- 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-web-session-activity
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_web_session_activity.py
|
|
- /opt/hermes/hermes_cli/web_server.py
|
|
- /patched/web_server.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- name: coordinator
|
|
mountPath: /opt/coordinator
|
|
readOnly: true
|
|
- name: web-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: patch-codex-runtime
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_codex_runtime.py
|
|
- /opt/hermes/hermes_cli/runtime_provider.py
|
|
- /patched/runtime_provider.py
|
|
- /opt/hermes/agent/transports/codex_app_server_session.py
|
|
- /patched/codex_app_server_session.py
|
|
- /opt/hermes/agent/codex_runtime.py
|
|
- /patched/codex_runtime.py
|
|
- /opt/hermes/agent/chat_completion_helpers.py
|
|
- /patched/chat_completion_helpers.py
|
|
- /opt/hermes/agent/conversation_loop.py
|
|
- /patched/conversation_loop.py
|
|
- /opt/hermes/agent/auxiliary_client.py
|
|
- /patched/auxiliary_client.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- name: coordinator
|
|
mountPath: /opt/coordinator
|
|
readOnly: true
|
|
- name: codex-runtime-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:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
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: /runtime-access/hermes-auth.json}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
- {name: HERMES_CASSANDRA_ACTIVE_WORKTREE, value: /opt/data/workspace/projects/cassandra-hermes-v69}
|
|
- {name: PATH, value: '/opt/coordinator:/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: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: routing-catalog, mountPath: /routing-catalog}
|
|
resources:
|
|
requests: {cpu: 50m, memory: 128Mi}
|
|
limits: {cpu: 500m, memory: 512Mi}
|
|
- name: configure-agent-clients
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -ec
|
|
- |
|
|
set -a
|
|
. /opt/data/.env
|
|
set +a
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/configure_agent_clients.py
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/migrate_herdr_state.py
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/migrate_api_session_lineage.py
|
|
# Client configuration restores the persisted Codex CLI login.
|
|
# Refresh routing afterwards so AUTO sees the app-server lane on
|
|
# the first request instead of waiting for the hourly steward.
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/hermes_coordinator.py --once
|
|
env:
|
|
- {name: HERMES_HOME, value: /opt/data}
|
|
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
- {name: HERMES_CASSANDRA_ACTIVE_WORKTREE, value: /opt/data/workspace/projects/cassandra-hermes-v69}
|
|
- {name: PATH, value: '/opt/coordinator:/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: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: routing-catalog, mountPath: /routing-catalog}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 128Mi}
|
|
limits: {cpu: 500m, memory: 512Mi}
|
|
- name: prepare-ttyd-index
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
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:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
set -a
|
|
. /opt/data/.env
|
|
set +a
|
|
API_SERVER_KEY="$(tr -d '\r\n' < /runtime-access/agent-api-key)"
|
|
test -n "${API_SERVER_KEY}"
|
|
export API_SERVER_KEY
|
|
exec /init /opt/hermes/docker/main-wrapper.sh 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: /runtime-access/hermes-auth.json}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {name: HERMES_CLAUDE_BIN, value: /opt/coordinator/claude}
|
|
- {name: KUBECONFIG, value: /opt/data/home/.kube/config}
|
|
- {name: PATH, value: '/opt/coordinator:/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: "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://worker.bstein.dev'}
|
|
- {name: HERMES_TUI_AGENT_INIT_TIMEOUT_S, value: "180"}
|
|
- {name: HERMES_STREAM_STALE_TIMEOUT, value: "600"}
|
|
- {name: HERMES_API_CALL_STALE_TIMEOUT, value: "600"}
|
|
- {name: API_SERVER_ENABLED, value: "true"}
|
|
- {name: API_SERVER_HOST, value: 0.0.0.0}
|
|
- {name: API_SERVER_PORT, value: "8642"}
|
|
# Comma-separated; the legacy origin stays until agent.hermes.bstein.dev is retired.
|
|
- {name: API_SERVER_CORS_ORIGINS, value: "https://worker.bstein.dev,https://agent.hermes.bstein.dev"}
|
|
- {name: HERMES_MEDIA_DELIVERY_STRICT, value: "1"}
|
|
- {name: HERMES_MEDIA_ALLOW_DIRS, value: /opt/data/workspace}
|
|
- {name: HERMES_AUTO_ROUTER_PROFILE, value: agent}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
- {name: HERMES_CODEX_HEALTH_PATH, value: /opt/data/provider-health/codex.json}
|
|
- {name: HERMES_CLAUDE_HEALTH_PATH, value: /opt/data/provider-health/claude.json}
|
|
- {name: HERMES_IMAGE_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
# Worker HUX hook wiring (observe-only). The hux-runtime plugin is
|
|
# mounted but stays OUT of plugins.enabled until the activation
|
|
# commit (docs/hux/WORKER-PLAN.md), so this environment is inert
|
|
# until that deliberate flip. Enforcement is pinned to 0: the hook
|
|
# observes and emits, it never gates worker tools in this stage.
|
|
- {name: HUX_BASE_URL, value: 'http://127.0.0.1:8790'}
|
|
- {name: HUX_RUNTIME_ENABLED, value: "1"}
|
|
- {name: HUX_TOOL_ENFORCEMENT, value: "0"}
|
|
- {name: HUX_TENANT_SLOT, value: slot-100}
|
|
- {name: HUX_WORKER_KEY_FILE, value: /run/hermes-hux-worker/worker-key}
|
|
- {name: HUX_SUBJECT_FILE, value: /run/hermes-hux-subject/subject}
|
|
- {name: HUX_CONTEXT_KEY_FILE, value: /run/hermes-hux-context/context-key}
|
|
- {name: HUX_PROJECT_SOURCE, value: 'profile:default'}
|
|
- {name: HUX_TIMEOUT_SECONDS, value: "3"}
|
|
volumeMounts:
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: web-server-patch, mountPath: /opt/hermes/hermes_cli/web_server.py, subPath: web_server.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/hermes_cli/runtime_provider.py, subPath: runtime_provider.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/transports/codex_app_server_session.py, subPath: codex_app_server_session.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/codex_runtime.py, subPath: codex_runtime.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/chat_completion_helpers.py, subPath: chat_completion_helpers.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/conversation_loop.py, subPath: conversation_loop.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/auxiliary_client.py, subPath: auxiliary_client.py}
|
|
- {name: tui-gateway-patch, mountPath: /opt/hermes/tui_gateway/server.py, subPath: server.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: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true}
|
|
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
|
- {name: atlas-pr-skill, mountPath: /opt/data/workspace/skills/manage-atlas-pull-requests, readOnly: true}
|
|
- {name: scm-boundary, mountPath: /opt/scm, readOnly: true}
|
|
- {name: routing-catalog, mountPath: /routing-catalog, readOnly: true}
|
|
- {name: tmp, mountPath: /tmp}
|
|
- {name: hux-runtime-plugin, mountPath: /opt/data/plugins/hux-runtime, readOnly: true}
|
|
- {name: hux-worker-key, mountPath: /run/hermes-hux-worker, readOnly: true}
|
|
- {name: home, mountPath: /run/hermes-hux-context, subPath: hux/context, readOnly: true}
|
|
- {name: home, mountPath: /run/hermes-hux-subject, subPath: hux/binding, readOnly: true}
|
|
startupProbe:
|
|
exec:
|
|
command: [curl, -fsS, 'http://127.0.0.1:9119/api/status']
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 60
|
|
readinessProbe:
|
|
exec:
|
|
command: [curl, -fsS, 'http://127.0.0.1:9119/api/status']
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
livenessProbe:
|
|
exec:
|
|
command: [curl, -fsS, 'http://127.0.0.1:9119/api/status']
|
|
initialDelaySeconds: 90
|
|
periodSeconds: 30
|
|
timeoutSeconds: 15
|
|
failureThreshold: 5
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 125m, memory: 320Mi}
|
|
limits: {cpu: "3", memory: 6Gi}
|
|
- name: oauth2-proxy
|
|
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.3@sha256:10a1165743a192e1940b4708fb9647027185ce11a681a1c5519b442ff7f1f561
|
|
imagePullPolicy: IfNotPresent
|
|
args:
|
|
- --provider=oidc
|
|
- --config=/oauth2-config/oidc-config
|
|
# Single canonical callback. The __Host- cookie prefix below binds both the
|
|
# session and the CSRF cookie to one host, so a fresh login must start on
|
|
# worker.bstein.dev; agent.hermes.bstein.dev keeps serving already
|
|
# authenticated sessions until it is retired separately.
|
|
- --redirect-url=https://worker.bstein.dev/oauth2/callback
|
|
- --oidc-issuer-url=https://sso.bstein.dev/realms/atlas
|
|
- --user-id-claim=sub
|
|
- --oidc-groups-claim=groups
|
|
- --allowed-group=/hermes-owner
|
|
- --code-challenge-method=S256
|
|
- --scope=openid profile email
|
|
- --email-domain=*
|
|
- --authenticated-emails-file=/etc/oauth2-proxy/allowed-emails
|
|
- --custom-templates-dir=/etc/oauth2-proxy/templates
|
|
- --set-xauthrequest=true
|
|
- --pass-user-headers=true
|
|
- --pass-basic-auth=false
|
|
- --pass-host-header=false
|
|
- --proxy-websockets=true
|
|
- --cookie-name=__Host-hermes_agent
|
|
- --cookie-path=/
|
|
- --cookie-secure=true
|
|
- --cookie-samesite=lax
|
|
- --cookie-csrf-expire=10m
|
|
- --cookie-csrf-per-request=true
|
|
- --cookie-csrf-per-request-limit=8
|
|
- --cookie-refresh=19m
|
|
- --cookie-expire=8h
|
|
- --session-store-type=redis
|
|
- --redis-connection-url=redis://hermes-oauth-sessions.hermes.svc.cluster.local:6379/0
|
|
- --upstream=http://127.0.0.1:7681/terminal/
|
|
- --upstream=http://127.0.0.1:9119/
|
|
- --http-address=0.0.0.0:4180
|
|
- --skip-provider-button=true
|
|
- --reverse-proxy=true
|
|
- --api-route=^/api/
|
|
- --trusted-proxy-ip=10.42.0.0/16
|
|
ports:
|
|
- {name: auth-http, containerPort: 4180, protocol: TCP}
|
|
readinessProbe:
|
|
httpGet: {path: /ping, port: auth-http}
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet: {path: /ping, port: auth-http}
|
|
initialDelaySeconds: 20
|
|
periodSeconds: 20
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 250m, memory: 256Mi}
|
|
volumeMounts:
|
|
- {name: allowlist, mountPath: /etc/oauth2-proxy, readOnly: true}
|
|
- {name: oauth2-config, mountPath: /oauth2-config, readOnly: true}
|
|
- {name: oauth-templates, mountPath: /etc/oauth2-proxy/templates, readOnly: true}
|
|
- {name: oauth-tmp, mountPath: /tmp}
|
|
- name: terminal
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
set -a
|
|
. /opt/data/.env
|
|
set +a
|
|
exec /opt/data/tools/bin/ttyd \
|
|
--writable \
|
|
--base-path /terminal \
|
|
--interface 0.0.0.0 \
|
|
--port 7681 \
|
|
--cwd /opt/data/workspace \
|
|
--terminal-type xterm-256color \
|
|
--index /ttyd-index/index.html \
|
|
--client-option disableReconnect=true \
|
|
--client-option "titleFixed=Hermes Agent" \
|
|
--client-option fontSize=15 \
|
|
/usr/bin/tmux new-session -A \
|
|
-s hermes-coordinator \
|
|
-c /opt/data/workspace \
|
|
/bin/sh -lc '
|
|
while true; do
|
|
/opt/hermes/.venv/bin/hermes \
|
|
--continue \
|
|
--tui \
|
|
--yolo \
|
|
--accept-hooks
|
|
exit_code=$?
|
|
printf "\r\nHermes exited (exit %s); resuming in 2 seconds...\r\n" "${exit_code}"
|
|
sleep 2
|
|
done
|
|
'
|
|
ports:
|
|
- {name: terminal, containerPort: 7681, protocol: TCP}
|
|
env:
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: HERMES_HOME, value: /opt/data}
|
|
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
|
- {name: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {name: HERMES_CLAUDE_BIN, value: /opt/coordinator/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_TUI_AGENT_INIT_TIMEOUT_S, value: "180"}
|
|
- {name: HERMES_AUTO_ROUTER_PROFILE, value: agent}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
- {name: HERMES_CODEX_HEALTH_PATH, value: /opt/data/provider-health/codex.json}
|
|
- {name: HERMES_CLAUDE_HEALTH_PATH, value: /opt/data/provider-health/claude.json}
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/hermes_cli/runtime_provider.py, subPath: runtime_provider.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/transports/codex_app_server_session.py, subPath: codex_app_server_session.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/codex_runtime.py, subPath: codex_runtime.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/chat_completion_helpers.py, subPath: chat_completion_helpers.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/conversation_loop.py, subPath: conversation_loop.py}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/auxiliary_client.py, subPath: auxiliary_client.py}
|
|
- {name: tui-gateway-patch, mountPath: /opt/hermes/tui_gateway/server.py, subPath: 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: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true}
|
|
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
|
- {name: atlas-pr-skill, mountPath: /opt/data/workspace/skills/manage-atlas-pull-requests, readOnly: true}
|
|
- {name: scm-boundary, mountPath: /opt/scm, readOnly: true}
|
|
- {name: routing-catalog, mountPath: /routing-catalog, readOnly: true}
|
|
- {name: tmp, mountPath: /tmp}
|
|
- {name: ttyd-index, mountPath: /ttyd-index, readOnly: true}
|
|
startupProbe:
|
|
tcpSocket: {port: terminal}
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
failureThreshold: 60
|
|
readinessProbe:
|
|
tcpSocket: {port: terminal}
|
|
periodSeconds: 10
|
|
timeoutSeconds: 3
|
|
livenessProbe:
|
|
tcpSocket: {port: terminal}
|
|
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: 32Mi}
|
|
limits: {cpu: 500m, memory: 512Mi}
|
|
- name: cli-lane-runner
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
set -a
|
|
. /opt/data/.env
|
|
set +a
|
|
exec /opt/hermes/.venv/bin/python /opt/coordinator/cli_lane_runner.py
|
|
ports:
|
|
- {name: lane-metrics, containerPort: 9011, 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: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {name: HERMES_CLAUDE_BIN, value: /opt/coordinator/claude}
|
|
- {name: KUBECONFIG, value: /opt/data/home/.kube/config}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: HERMES_CLI_LANE_CONCURRENCY, value: "2"}
|
|
- {name: HERMES_CLI_HEALTH_MAX_AGE_SECONDS, value: "60"}
|
|
- {name: HERMES_CLI_LANE_METRICS_PORT, value: "9011"}
|
|
- {name: HERMES_CLI_QUOTA_METRICS_URL, value: "http://127.0.0.1:9010/metrics"}
|
|
- {name: HERMES_AUTO_ROUTER_PROFILE, value: agent}
|
|
- {name: PATH, value: '/opt/coordinator:/opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin'}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.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: atlas-pr-skill, mountPath: /opt/data/workspace/skills/manage-atlas-pull-requests, readOnly: true}
|
|
- {name: scm-boundary, mountPath: /opt/scm, readOnly: true}
|
|
- {name: tmp, mountPath: /tmp}
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /healthz
|
|
port: lane-metrics
|
|
initialDelaySeconds: 2
|
|
periodSeconds: 5
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
resources:
|
|
requests: {cpu: 25m, memory: 96Mi}
|
|
limits: {cpu: "2", memory: 6Gi}
|
|
- name: model-steward
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
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: /runtime-access/hermes-auth.json}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
- {name: HERMES_MODEL_EVAL_CODEX_ENDPOINT, value: 'http://127.0.0.1:9003/v1/responses'}
|
|
- {name: HERMES_MODEL_EVAL_CLAUDE_ENDPOINT, value: 'http://127.0.0.1:9006/v1/messages'}
|
|
- {name: HERMES_MODEL_EVAL_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
- {name: HERMES_CASSANDRA_ACTIVE_WORKTREE, value: /opt/data/workspace/projects/cassandra-hermes-v69}
|
|
- {name: PATH, value: '/opt/coordinator:/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}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: routing-catalog, mountPath: /routing-catalog}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 32Mi}
|
|
limits: {cpu: 250m, memory: 512Mi}
|
|
- name: kanban-supervisor
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
# Autonomous cross-card review->repair->re-review driver. Reads/writes
|
|
# only the local Kanban DB under /opt/data; creates Kanban cards that
|
|
# route through the existing subscription lanes. No runtime-access
|
|
# mount and no provider client: it holds no metered/API-key path. Inert
|
|
# until kanban.auto_supervise is set true in the deployed config.
|
|
command: [/opt/hermes/.venv/bin/python, /opt/coordinator/kanban_supervisor.py]
|
|
env:
|
|
- {name: HERMES_HOME, value: /opt/data}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: PYTHONDONTWRITEBYTECODE, value: "1"}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 32Mi}
|
|
limits: {cpu: 250m, memory: 256Mi}
|
|
- name: credential-sync
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/sync_runtime_credentials.py
|
|
env:
|
|
- {name: HERMES_CREDENTIAL_SYNC_VAULT_ROLE, value: hermes-credential-sync}
|
|
- {name: HERMES_CREDENTIAL_SYNC_INTERVAL, value: "60"}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
# Reconcile provider rotations to Vault and restore runtime files if a
|
|
# client leaves an incomplete credential behind after refresh.
|
|
- {name: runtime-access, mountPath: /runtime-access/claude, subPath: claude}
|
|
- {name: runtime-access, mountPath: /runtime-access/codex, subPath: codex}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 32Mi}
|
|
limits: {cpu: 100m, memory: 128Mi}
|
|
- name: ai-usage-exporter
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/ai_usage_exporter.py
|
|
ports:
|
|
- {name: ai-metrics, containerPort: 9010, protocol: TCP}
|
|
env:
|
|
- {name: HOME, value: /tmp}
|
|
- {name: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: ATLAS_AI_CODEX_BIN, value: /opt/data/tools/bin/codex}
|
|
- {name: ATLAS_AI_CODEX_QUERY_TIMEOUT_SECONDS, value: "45"}
|
|
- {name: ATLAS_AI_CLAUDE_CREDENTIALS, value: /runtime-access/claude/.credentials.json}
|
|
- {name: ATLAS_AI_CLAUDE_OAUTH_TOKEN_FILE, value: /claude-oauth-access/token}
|
|
- {name: ATLAS_AI_CLAUDE_QUERY_TIMEOUT_SECONDS, value: "30"}
|
|
- {name: ATLAS_AI_CLAUDE_QUOTA_MODEL, value: claude-haiku-4-5-20251001}
|
|
- {name: ATLAS_AI_CLAUDE_USAGE_URL, value: 'https://api.anthropic.com/api/oauth/usage'}
|
|
- {name: ATLAS_AI_AUTHENTICATION_GRACE_SECONDS, value: "1200"}
|
|
- {name: ATLAS_AI_PROVIDER_HEALTH_ROOT, value: /provider-health}
|
|
- {name: ATLAS_AI_USAGE_INTERVAL_SECONDS, value: "300"}
|
|
- {name: ATLAS_AI_USAGE_PORT, value: "9010"}
|
|
readinessProbe:
|
|
httpGet: {path: /healthz, port: ai-metrics}
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet: {path: /healthz, port: ai-metrics}
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: home, mountPath: /opt/data/tools, subPath: tools, readOnly: true}
|
|
- {name: home, mountPath: /provider-health, subPath: provider-health, readOnly: true}
|
|
- {name: runtime-access, mountPath: /runtime-access/claude, subPath: claude}
|
|
- {name: runtime-access, mountPath: /runtime-access/codex, subPath: codex}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: tmp, mountPath: /tmp}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 32Mi}
|
|
limits: {cpu: 250m, memory: 192Mi}
|
|
- name: image-broker
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
exec /opt/hermes/.venv/bin/python /opt/coordinator/image_broker.py
|
|
ports:
|
|
- {name: image-broker, containerPort: 9002, 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: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: HERMES_IMAGE_BROKER_DEFAULT_MODEL, value: atlas-image-auto-high}
|
|
- {name: HERMES_IMAGE_BROKER_LISTEN_PORT, value: "9002"}
|
|
- {name: HERMES_IMAGE_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
- {name: HERMES_LOCAL_IMAGE_URL, value: 'http://hermes-local-image.hermes.svc.cluster.local:9004'}
|
|
- {name: HERMES_IMAGE_POLICY_PATH, value: /etc/hermes-image-policy/policy.json}
|
|
readinessProbe:
|
|
tcpSocket: {port: image-broker}
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
tcpSocket: {port: image-broker}
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/auxiliary_client.py, subPath: auxiliary_client.py}
|
|
- {name: image-policy, mountPath: /etc/hermes-image-policy, readOnly: true}
|
|
- {name: tmp, mountPath: /tmp}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: "1", memory: 1Gi}
|
|
- name: codex-broker
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
exec /opt/hermes/.venv/bin/python /opt/coordinator/codex_broker.py
|
|
ports:
|
|
- {name: codex-broker, containerPort: 9003, 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: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: PYTHONPATH, value: /opt/hermes}
|
|
- {name: HERMES_CODEX_BROKER_LISTEN_PORT, value: "9003"}
|
|
- {name: HERMES_CODEX_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
- {name: HERMES_CODEX_HEALTH_PATH, value: /opt/data/provider-health/codex.json}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
readinessProbe:
|
|
tcpSocket: {port: codex-broker}
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
tcpSocket: {port: codex-broker}
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: codex-runtime-patch, mountPath: /opt/hermes/agent/auxiliary_client.py, subPath: auxiliary_client.py}
|
|
- {name: tmp, mountPath: /tmp}
|
|
- {name: routing-catalog, mountPath: /routing-catalog, readOnly: true}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: "1", memory: 1Gi}
|
|
- name: claude-broker
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
unset ANTHROPIC_API_KEY CLAUDE_API_KEY
|
|
exec /opt/hermes/.venv/bin/python /opt/coordinator/claude_oauth_broker.py
|
|
ports:
|
|
- {name: claude-broker, containerPort: 9006, 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: CODEX_HOME, value: /runtime-access/codex}
|
|
- {name: CLAUDE_CONFIG_DIR, value: /runtime-access/claude}
|
|
- {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: HERMES_CLAUDE_BIN, value: /opt/coordinator/claude}
|
|
- {name: HERMES_CLAUDE_BROKER_PORT, value: "9006"}
|
|
- {name: HERMES_CLAUDE_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
- {name: HERMES_CLAUDE_BROKER_READ_TIMEOUT, value: "1800"}
|
|
- {name: HERMES_CLAUDE_BROKER_CONCURRENCY, value: "2"}
|
|
- {name: HERMES_CLAUDE_HEALTH_POLL_SECONDS, value: "300"}
|
|
- {name: HERMES_CLAUDE_HEALTH_PATH, value: /opt/data/provider-health/claude.json}
|
|
- {name: HERMES_ROUTING_CATALOG_PATH, value: /routing-catalog/catalog.json}
|
|
readinessProbe:
|
|
tcpSocket: {port: claude-broker}
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
tcpSocket: {port: claude-broker}
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: claude-oauth-access, mountPath: /claude-oauth-access, readOnly: true}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: tmp, mountPath: /tmp}
|
|
- {name: routing-catalog, mountPath: /routing-catalog, readOnly: true}
|
|
resources:
|
|
requests: {cpu: 50m, memory: 128Mi}
|
|
limits: {cpu: "3", memory: 3Gi}
|
|
# Worker HUX service: the same reviewed WebUI image line as the chat
|
|
# tenants (one artifact, one Flux image policy), loopback-only, with
|
|
# foundation as the only enabled card until the Worker lifecycle gates
|
|
# in docs/hux/WORKER-PLAN.md pass on this instance.
|
|
- name: hux
|
|
image: registry.bstein.dev/bstein/hermes-webui:git-2c91aea01d6b874e247c2fc3528e5b4bb580ffe4-build-39-release@sha256:4f60fae01efb8ffc00b6af78865c975417446904beb5c447d11e39c1ecc1438c # {"$imagepolicy": "hermes:hermes-webui-release"}
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/opt/hermes/.venv/bin/python, -m, hux.server]
|
|
ports:
|
|
- {name: hux-loopback, containerPort: 8790, protocol: TCP}
|
|
env:
|
|
- {name: PYTHONPATH, value: /opt/hermes-hux}
|
|
- {name: PYTHONDONTWRITEBYTECODE, value: "1"}
|
|
- {name: HOME, value: /tmp}
|
|
- {name: HUX_BIND, value: 127.0.0.1}
|
|
- {name: HUX_PORT, value: "8790"}
|
|
- {name: HUX_TENANT_SLOT, value: slot-100}
|
|
- {name: HUX_DATA_ROOT, value: /var/lib/hux/store}
|
|
- {name: HUX_FLAGS, value: hux.foundation}
|
|
- {name: HUX_WORKER_KEY_FILE, value: /run/hermes-hux-worker/worker-key}
|
|
- {name: HUX_SUBJECT_BINDING_FILE, value: /var/lib/hux/binding/subject}
|
|
- {name: HUX_CONTEXT_KEY_FILE, value: /var/lib/hux/context/context-key}
|
|
- {name: HUX_CANARY_FILE, value: /var/lib/hux/context/redaction-canary}
|
|
- name: HUX_IMAGE_TAG
|
|
value: git-2c91aea01d6b874e247c2fc3528e5b4bb580ffe4-build-39-release # {"$imagepolicy": "hermes:hermes-webui-release:tag"}
|
|
- name: HUX_IMAGE_DIGEST
|
|
value: sha256:4f60fae01efb8ffc00b6af78865c975417446904beb5c447d11e39c1ecc1438c # {"$imagepolicy": "hermes:hermes-webui-release:digest"}
|
|
- {name: HUX_READS_PER_MINUTE, value: "600"}
|
|
- {name: HUX_WRITES_PER_MINUTE, value: "120"}
|
|
- {name: HUX_REQUEST_TIMEOUT_SECONDS, value: "10"}
|
|
volumeMounts:
|
|
- {name: home, mountPath: /var/lib/hux, subPath: hux}
|
|
- {name: hux-worker-key, mountPath: /run/hermes-hux-worker, readOnly: true}
|
|
- {name: hux-tmp, mountPath: /tmp}
|
|
readinessProbe:
|
|
exec:
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- -c
|
|
- "import json,urllib.request; body=json.load(urllib.request.urlopen('http://127.0.0.1:8790/healthz', timeout=2)); assert body['status']=='ok'"
|
|
initialDelaySeconds: 2
|
|
periodSeconds: 5
|
|
timeoutSeconds: 5
|
|
failureThreshold: 12
|
|
livenessProbe:
|
|
exec:
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- -c
|
|
- "import json,urllib.request; body=json.load(urllib.request.urlopen('http://127.0.0.1:8790/healthz', timeout=2)); assert body['status']=='ok'"
|
|
initialDelaySeconds: 20
|
|
periodSeconds: 20
|
|
timeoutSeconds: 5
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 250m, memory: 256Mi}
|
|
volumes:
|
|
- name: home
|
|
persistentVolumeClaim:
|
|
claimName: hermes-agent-home
|
|
- name: runtime-access
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 128Mi
|
|
- name: claude-oauth-access
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 1Mi
|
|
- name: routing-catalog
|
|
persistentVolumeClaim:
|
|
claimName: hermes-routing-catalog
|
|
- 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: tui-gateway-patch
|
|
emptyDir: {}
|
|
- name: api-server-patch
|
|
emptyDir: {}
|
|
- name: web-server-patch
|
|
emptyDir: {}
|
|
- name: subprocess-secret-patch
|
|
emptyDir: {}
|
|
- name: codex-runtime-patch
|
|
emptyDir: {}
|
|
- name: auto-router-plugin
|
|
configMap:
|
|
name: hermes-auto-router-plugin
|
|
items:
|
|
- {key: __init__.py, path: __init__.py}
|
|
- {key: provider_status.py, path: provider_status.py}
|
|
- {key: plugin.yaml, path: plugin.yaml}
|
|
- {key: dashboard-manifest.json, path: dashboard/manifest.json}
|
|
- {key: dashboard-api.py, path: dashboard/plugin_api.py}
|
|
- {key: dashboard-index.js, path: dashboard/dist/index.js}
|
|
- {key: dashboard-style.css, path: dashboard/dist/style.css}
|
|
- name: atlas-pr-skill
|
|
configMap:
|
|
name: hermes-atlas-pr-skill
|
|
items:
|
|
- {key: SKILL.md, path: SKILL.md}
|
|
- {key: openai.yaml, path: agents/openai.yaml}
|
|
- name: scm-boundary
|
|
configMap:
|
|
name: hermes-scm-boundary-v2
|
|
defaultMode: 0555
|
|
- name: image-policy
|
|
configMap:
|
|
name: hermes-image-policy
|
|
- name: tmp
|
|
emptyDir:
|
|
sizeLimit: 4Gi
|
|
- name: oauth-tmp
|
|
emptyDir:
|
|
sizeLimit: 64Mi
|
|
- name: oauth2-config
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 1Mi
|
|
- name: allowlist
|
|
configMap:
|
|
name: hermes-owner-allowlist
|
|
- name: oauth-templates
|
|
configMap:
|
|
name: hermes-chat-oauth-templates
|
|
- name: ttyd-index
|
|
emptyDir:
|
|
sizeLimit: 2Mi
|
|
- name: hux-worker-key
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 1Mi
|
|
- name: hux-tmp
|
|
emptyDir:
|
|
sizeLimit: 64Mi
|
|
- name: hux-runtime-plugin
|
|
configMap:
|
|
name: hermes-hux-runtime-plugin
|
|
items:
|
|
- {key: __init__.py, path: __init__.py}
|
|
- {key: context_ids.py, path: context_ids.py}
|
|
- {key: emitters.py, path: emitters.py}
|
|
- {key: runtime.py, path: runtime.py}
|
|
- {key: tool_policy.py, path: tool_policy.py}
|
|
- {key: plugin.yaml, path: plugin.yaml}
|
|
- {key: hux-hook-init.py, path: hux_hook/__init__.py}
|
|
- {key: hux-hook-client.py, path: hux_hook/client.py}
|
|
- {key: hux-hook-hooks.py, path: hux_hook/hooks.py}
|