Park/resume and gateway-owned stop receipts passed the live gates, so HUX_TOOL_ENFORCEMENT=1 ships to ordinal 3 (partition unchanged). Every canary tool call now requires a HUX release; the fleet stays observe-off until the approvals UX proves out under real traffic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
822 lines
38 KiB
YAML
822 lines
38 KiB
YAML
# services/hermes/chat-statefulset.yaml
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
name: hermes-chat-tenant
|
|
namespace: hermes
|
|
labels:
|
|
app: hermes-chat-tenant
|
|
annotations:
|
|
kustomize.toolkit.fluxcd.io/force: enabled
|
|
spec:
|
|
serviceName: hermes-chat-tenant
|
|
replicas: 4
|
|
podManagementPolicy: Parallel
|
|
persistentVolumeClaimRetentionPolicy:
|
|
whenDeleted: Retain
|
|
whenScaled: Retain
|
|
updateStrategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
partition: 3
|
|
selector:
|
|
matchLabels:
|
|
app: hermes-chat-tenant
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: hermes-chat-tenant
|
|
annotations:
|
|
ai.bstein.dev/role: isolated-user-chat
|
|
ai.bstein.dev/router-wire-contract: ollama-numeric-keepalive
|
|
ai.bstein.dev/isolation: one Hermes process and PVC per Keycloak subject
|
|
ai.bstein.dev/model-policy: uniform automatic policy with per-user overrides
|
|
ai.bstein.dev/config-rev: "20260816-telegram-topics"
|
|
ai.bstein.dev/hux-config-rev: "20260824-hux-v1"
|
|
vault.hashicorp.com/agent-inject: "true"
|
|
vault.hashicorp.com/role: hermes-chat
|
|
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-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-chat
|
|
automountServiceAccountToken: true
|
|
securityContext:
|
|
fsGroup: 10000
|
|
fsGroupChangePolicy: OnRootMismatch
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
affinity:
|
|
nodeAffinity:
|
|
requiredDuringSchedulingIgnoredDuringExecution:
|
|
nodeSelectorTerms:
|
|
- matchExpressions:
|
|
- key: kubernetes.io/arch
|
|
operator: In
|
|
values: [arm64]
|
|
- key: node-role.kubernetes.io/worker
|
|
operator: In
|
|
values: ["true"]
|
|
- key: node-role.kubernetes.io/storage-backbone
|
|
operator: DoesNotExist
|
|
- key: kubernetes.io/hostname
|
|
operator: NotIn
|
|
values: [titan-05, titan-13, titan-14, titan-17, titan-18, titan-19]
|
|
preferredDuringSchedulingIgnoredDuringExecution:
|
|
- weight: 100
|
|
preference:
|
|
matchExpressions:
|
|
- key: node-role.kubernetes.io/storage-backbone
|
|
operator: DoesNotExist
|
|
- weight: 100
|
|
preference:
|
|
matchExpressions:
|
|
- key: hardware
|
|
operator: In
|
|
values: [rpi5]
|
|
- weight: 40
|
|
preference:
|
|
matchExpressions:
|
|
- key: hardware
|
|
operator: In
|
|
values: [rpi4]
|
|
podAntiAffinity:
|
|
preferredDuringSchedulingIgnoredDuringExecution:
|
|
- weight: 100
|
|
podAffinityTerm:
|
|
labelSelector:
|
|
matchLabels:
|
|
app: hermes-chat-tenant
|
|
topologyKey: kubernetes.io/hostname
|
|
initContainers:
|
|
- name: init-config
|
|
image: busybox:1.37
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
set -eu
|
|
mkdir -p /opt/data/home/.local/bin /opt/data/logs /opt/data/workspace
|
|
if [ ! -e /opt/data/workspace/.hermes-workspace-v1 ]; then
|
|
if [ -d /legacy-home/workspace ]; then
|
|
cp -a /legacy-home/workspace/. /opt/data/workspace/
|
|
fi
|
|
touch /opt/data/workspace/.hermes-workspace-v1
|
|
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
|
|
touch /opt/data/.env
|
|
for key in ANTHROPIC_API_KEY API_SERVER_KEY CLAUDE_API_KEY CLAUDE_CODE_OAUTH_TOKEN GITEA_TOKEN GITEA_USERNAME HERMES_IMAGE_BROKER_KEY OPENAI_API_KEY; do
|
|
grep -v "^${key}=" /opt/data/.env > /opt/data/.env.tmp || true
|
|
mv /opt/data/.env.tmp /opt/data/.env
|
|
done
|
|
rm -f /opt/data/auth.json
|
|
chmod 0600 /opt/data/.env
|
|
chown 10000:10000 \
|
|
/opt/data \
|
|
/opt/data/home \
|
|
/opt/data/home/.local \
|
|
/opt/data/home/.local/bin \
|
|
/opt/data/logs \
|
|
/opt/data/workspace \
|
|
/opt/data/config.yaml \
|
|
/opt/data/SOUL.md \
|
|
/opt/data/workspace/AGENTS.md \
|
|
/opt/data/.env
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: home, mountPath: /legacy-home, readOnly: true}
|
|
- {name: workspace, mountPath: /opt/data/workspace}
|
|
- {name: config, mountPath: /config, readOnly: true}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 32Mi}
|
|
limits: {cpu: 100m, memory: 64Mi}
|
|
- name: init-hux-runtime
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
umask 077
|
|
tenant_root="/hux-data/${HOSTNAME}"
|
|
export HUX_INIT_ROOT="${tenant_root}"
|
|
mkdir -p \
|
|
"${tenant_root}/binding" \
|
|
"${tenant_root}/context" \
|
|
"${tenant_root}/store" \
|
|
/hux-relay \
|
|
/hux-worker
|
|
chown 10000:10000 \
|
|
"${tenant_root}" \
|
|
"${tenant_root}/binding" \
|
|
"${tenant_root}/context" \
|
|
"${tenant_root}/store" \
|
|
/hux-relay \
|
|
/hux-worker
|
|
chmod 0700 \
|
|
"${tenant_root}" \
|
|
"${tenant_root}/binding" \
|
|
"${tenant_root}/context" \
|
|
"${tenant_root}/store" \
|
|
/hux-relay \
|
|
/hux-worker
|
|
if [ ! -e "${tenant_root}/context/context-key" ]; then
|
|
dd if=/dev/urandom of="${tenant_root}/context/.context-key.tmp" bs=32 count=1 2>/dev/null
|
|
chown 10000:10000 "${tenant_root}/context/.context-key.tmp"
|
|
chmod 0600 "${tenant_root}/context/.context-key.tmp"
|
|
mv "${tenant_root}/context/.context-key.tmp" "${tenant_root}/context/context-key"
|
|
fi
|
|
test "$(wc -c < "${tenant_root}/context/context-key")" -eq 32
|
|
chown 10000:10000 "${tenant_root}/context/context-key"
|
|
chmod 0600 "${tenant_root}/context/context-key"
|
|
ordinal="${HOSTNAME##*-}"
|
|
HUX_INIT_SLOT="slot-${ordinal}" \
|
|
/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 "${tenant_root}/context/redaction-canary" ]; then
|
|
dd if=/dev/urandom bs=32 count=1 2>/dev/null \
|
|
| sha256sum | cut -d ' ' -f 1 \
|
|
> "${tenant_root}/context/.redaction-canary.tmp"
|
|
chown 10000:10000 "${tenant_root}/context/.redaction-canary.tmp"
|
|
chmod 0400 "${tenant_root}/context/.redaction-canary.tmp"
|
|
mv "${tenant_root}/context/.redaction-canary.tmp" "${tenant_root}/context/redaction-canary"
|
|
fi
|
|
for target in /hux-relay/relay-key /hux-worker/worker-key; do
|
|
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}"
|
|
done
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
add: [CHOWN, DAC_OVERRIDE, FOWNER]
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: hux-data, mountPath: /hux-data}
|
|
- {name: hux-relay-key, mountPath: /hux-relay}
|
|
- {name: hux-worker-key, mountPath: /hux-worker}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 16Mi}
|
|
limits: {cpu: 50m, memory: 32Mi}
|
|
- name: stage-hux-evidence
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
# HUX-12 enablement is fail-closed: until the Vault annotation
|
|
# projects hux-evidence-key, nothing is staged and the evidence
|
|
# capability stays off. The key is staged only for the hux
|
|
# service and producer containers, never for hermes or webui.
|
|
if [ -s /vault/secrets/hux-evidence-key ]; then
|
|
umask 077
|
|
tr -d '\r\n' < /vault/secrets/hux-evidence-key > /hux-evidence/.evidence-key.tmp
|
|
chown 10000:10000 /hux-evidence/.evidence-key.tmp
|
|
chmod 0400 /hux-evidence/.evidence-key.tmp
|
|
mv /hux-evidence/.evidence-key.tmp /hux-evidence/evidence-key
|
|
fi
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
add: [CHOWN, DAC_OVERRIDE, FOWNER]
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: hux-evidence-key, mountPath: /hux-evidence}
|
|
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
|
|
- chat
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 0
|
|
runAsGroup: 0
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 32Mi}
|
|
limits: {cpu: 100m, memory: 64Mi}
|
|
- name: patch-auth
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_hermes_auth.py
|
|
- /opt/hermes/hermes_cli/auth.py
|
|
- /patched/auth.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: auth-patch, mountPath: /patched}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 100m, memory: 128Mi}
|
|
- name: patch-stream-recovery
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /opt/hermes/.venv/bin/python
|
|
- /opt/coordinator/patch_stream_recovery.py
|
|
- /opt/hermes/agent/conversation_loop.py
|
|
- /patched/conversation_loop.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: stream-recovery-patch, mountPath: /patched}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 100m, memory: 128Mi}
|
|
- name: patch-api-server-sessions
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/patch_api_server_sessions.py \
|
|
/opt/hermes/gateway/platforms/api_server.py /patched/api_server.py
|
|
grep -Fq 'conversation_history = compact_telegram_history(conversation_history)' /patched/api_server.py
|
|
grep -Fq 'full_history = compact_telegram_history(full_history)' /patched/api_server.py
|
|
grep -Fq 'conversation_history_snapshot = compact_telegram_history(' /patched/api_server.py
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/migrate_telegram_api_sessions.py \
|
|
/opt/data/state.db /opt/data/response_store.db
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: api-server-patch, mountPath: /patched}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 100m, memory: 128Mi}
|
|
- name: patch-subprocess-secret-boundary
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- /bin/sh
|
|
- -ec
|
|
- |
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/patch_subprocess_secret_boundary.py \
|
|
/opt/hermes/tools/environments/local.py /patched/local.py
|
|
/opt/hermes/.venv/bin/python /opt/coordinator/patch_process_output_redaction.py \
|
|
/opt/hermes/tools/process_registry.py /patched/process_registry.py
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumeMounts:
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: subprocess-secret-patch, mountPath: /patched}
|
|
resources:
|
|
requests: {cpu: 25m, memory: 64Mi}
|
|
limits: {cpu: 100m, memory: 128Mi}
|
|
containers:
|
|
- name: hermes
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
ordinal="${HOSTNAME##*-}"
|
|
export HERMES_CODE_SANDBOX_URL="http://hermes-chat-sandbox-${ordinal}.hermes-chat-sandbox.hermes.svc.cluster.local:9080/v1/execute"
|
|
set -a
|
|
. /opt/data/.env
|
|
set +a
|
|
API_SERVER_KEY="$(tr -d '\r\n' < /runtime-access/chat-relay-key)"
|
|
test -n "${API_SERVER_KEY}"
|
|
export API_SERVER_KEY
|
|
export HUX_TENANT_SLOT="slot-${ordinal}"
|
|
exec /opt/hermes/.venv/bin/hermes gateway run
|
|
ports:
|
|
- {name: api, containerPort: 8642, protocol: TCP}
|
|
env:
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.name
|
|
- {name: HERMES_HOME, value: /opt/data}
|
|
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: TERMINAL_CWD, value: /opt/data/workspace}
|
|
- {name: HERMES_WRITE_SAFE_ROOT, value: /opt/data/workspace}
|
|
- {name: PATH, value: '/opt/data/home/.local/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: 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://chat.bstein.dev'}
|
|
- {name: HERMES_IMAGE_BROKER_URL, value: 'http://hermes-image-broker.hermes.svc.cluster.local:9002'}
|
|
- {name: HERMES_IMAGE_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
- {name: HERMES_AUTO_ROUTER_PROFILE, value: chat}
|
|
- {name: HUX_BASE_URL, value: 'http://127.0.0.1:8790'}
|
|
- {name: HUX_RUNTIME_ENABLED, value: "1"}
|
|
# First rollout is observe-only until approval parking/resume is
|
|
# connected to the upstream tool loop and proven live.
|
|
- {name: HUX_TOOL_ENFORCEMENT, value: "1"}
|
|
- {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: home, mountPath: /opt/data}
|
|
- {name: workspace, mountPath: /opt/data/workspace}
|
|
- {name: runtime-access, mountPath: /runtime-access}
|
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
|
- {name: api-server-patch, mountPath: /opt/hermes/gateway/platforms/api_server.py, subPath: api_server.py}
|
|
- {name: coordinator, mountPath: /opt/hermes/gateway/platforms/telegram_continuity.py, subPath: migrate_telegram_api_sessions.py, readOnly: true}
|
|
- {name: stream-recovery-patch, mountPath: /opt/hermes/agent/conversation_loop.py, subPath: conversation_loop.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: image-plugin, mountPath: /opt/hermes/plugins/image_gen/atlas-broker, readOnly: true}
|
|
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
|
- {name: hux-runtime-plugin, mountPath: /opt/data/plugins/hux-runtime, readOnly: true}
|
|
- {name: hux-worker-key, mountPath: /run/hermes-hux-worker, readOnly: true}
|
|
- {name: hux-data, mountPath: /run/hermes-hux-context, subPathExpr: $(POD_NAME)/context, readOnly: true}
|
|
- {name: hux-data, mountPath: /run/hermes-hux-subject, subPathExpr: $(POD_NAME)/binding, readOnly: true}
|
|
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: 256Mi}
|
|
limits: {cpu: "1", memory: 2Gi}
|
|
- name: webui
|
|
image: registry.bstein.dev/bstein/hermes-webui:git-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release@sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04 # {"$imagepolicy": "hermes:hermes-webui-release"}
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
api_key="$(tr -d '\r\n' < /runtime-access/chat-relay-key)"
|
|
test -n "${api_key}"
|
|
export API_SERVER_KEY="${api_key}"
|
|
export HERMES_WEBUI_GATEWAY_API_KEY="${api_key}"
|
|
exec /opt/hermes/.venv/bin/python /opt/hermes-webui/server.py
|
|
ports:
|
|
- {name: webui, containerPort: 8787, protocol: TCP}
|
|
env:
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.name
|
|
- {name: HERMES_HOME, value: /opt/data}
|
|
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
|
- {name: HOME, value: /opt/data/home}
|
|
- {name: HERMES_WEBUI_AGENT_DIR, value: /opt/hermes}
|
|
- {name: HERMES_WEBUI_HOST, value: 0.0.0.0}
|
|
- {name: HERMES_WEBUI_PORT, value: "8787"}
|
|
- {name: HERMES_WEBUI_STATE_DIR, value: /opt/data/webui}
|
|
- {name: HERMES_WEBUI_DEFAULT_WORKSPACE, value: /opt/data/workspace}
|
|
- {name: HERMES_WEBUI_CHAT_BACKEND, value: gateway}
|
|
- {name: HERMES_WEBUI_GATEWAY_BASE_URL, value: 'http://127.0.0.1:8642'}
|
|
- {name: HERMES_WEBUI_GATEWAY_USE_RUNS_API, value: "true"}
|
|
- {name: HERMES_WEBUI_SKIP_ONBOARDING, value: "1"}
|
|
- {name: HERMES_WEBUI_SECURE, value: "1"}
|
|
- {name: HERMES_WEBUI_COOKIE_NAME, value: hermes_chat_session}
|
|
- {name: HERMES_WEBUI_PROFILE_COOKIE_NAME, value: hermes_chat_profile}
|
|
- {name: HERMES_WEBUI_TRUSTED_AUTH_HEADER, value: X-Hermes-Tenant-Identity}
|
|
# NetworkPolicy admits this port only from hermes-chat-router; the
|
|
# CIDR lets the WebUI validate that router's changing pod address.
|
|
- {name: HERMES_WEBUI_TRUSTED_PROXY_CIDRS, value: 10.42.0.0/16}
|
|
- {name: HERMES_WEBUI_ALLOWED_ORIGINS, value: 'https://chat.bstein.dev'}
|
|
- {name: HERMES_WEBUI_TRUST_FORWARDED_HOST, value: "1"}
|
|
- {name: HERMES_WEBUI_TRUST_FORWARDED_PROTO, value: "1"}
|
|
- {name: HERMES_ROUTER_PROFILE, value: chat}
|
|
- {name: HERMES_STT_URL, value: 'http://hermes-stt.hermes.svc.cluster.local:9000/v1/audio/transcriptions'}
|
|
- {name: HERMES_WEBUI_ATLAS_STT_STREAM_URL, value: 'http://hermes-stt.hermes.svc.cluster.local:9000/v1/audio/transcriptions/stream'}
|
|
- {name: HERMES_LOCAL_STT_COMMAND, value: "/opt/hermes/.venv/bin/python /opt/coordinator/hermes_stt_client.py {input_path} --output-dir {output_dir} --language {language} --model {model}"}
|
|
- {name: HERMES_WEBUI_ATLAS_TTS_URL, value: 'http://hermes-tts.hermes.svc.cluster.local:9001/v1/audio/speech'}
|
|
- {name: HERMES_WEBUI_ATLAS_TTS_STREAM_URL, value: 'http://hermes-tts.hermes.svc.cluster.local:9001/v1/audio/speech/stream'}
|
|
- {name: HUX_CONTEXT_KEY_FILE, value: /run/hermes-hux-context/context-key}
|
|
- {name: HUX_PROJECT_SOURCE, value: 'profile:default'}
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data}
|
|
- {name: workspace, mountPath: /opt/data/workspace}
|
|
- {name: runtime-access, mountPath: /runtime-access, readOnly: true}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: tmp, mountPath: /tmp}
|
|
- {name: hux-relay-key, mountPath: /run/hermes-webui-hux, readOnly: true}
|
|
- {name: hux-data, mountPath: /run/hermes-hux-context, subPathExpr: $(POD_NAME)/context, readOnly: true}
|
|
readinessProbe:
|
|
httpGet: {path: /health, port: webui}
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
livenessProbe:
|
|
httpGet: {path: /health, port: webui}
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
timeoutSeconds: 10
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 100m, memory: 224Mi}
|
|
limits: {cpu: 750m, memory: 1Gi}
|
|
- name: hux
|
|
image: registry.bstein.dev/bstein/hermes-webui:git-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release@sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04 # {"$imagepolicy": "hermes:hermes-webui-release"}
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
ordinal="${HOSTNAME##*-}"
|
|
export HUX_TENANT_SLOT="slot-${ordinal}"
|
|
exec /opt/hermes/.venv/bin/python -m hux.server
|
|
ports:
|
|
- {name: hux-loopback, containerPort: 8790, protocol: TCP}
|
|
env:
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.name
|
|
- {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_DATA_ROOT, value: /var/lib/hux/store}
|
|
- {name: HUX_FLAGS, value: 'hux.foundation,hux.activity_timeline,hux.projects,hux.privacy,hux.memory_control,hux.artifacts,hux.research,hux.friendly_modes,hux.multimodal,hux.onboarding,hux.autonomy'}
|
|
- {name: HUX_RELAY_KEY_FILE, value: /run/hermes-webui-hux/relay-key}
|
|
- {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-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release'} # {"$imagepolicy": "hermes:hermes-webui-release:tag"}
|
|
- {name: HUX_IMAGE_DIGEST, value: 'sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04'} # {"$imagepolicy": "hermes:hermes-webui-release:digest"}
|
|
- {name: HUX_SWITCHYARD_ROUTE_CATALOG, value: 'atlas/manual/codex/luna,atlas/manual/codex/terra,atlas/manual/codex/sol,atlas/manual/claude/haiku,atlas/manual/claude/fable,atlas/manual/claude/sonnet,atlas/manual/claude/opus,atlas/manual/local/qwen-14b'}
|
|
- {name: HUX_RELEASE_EVIDENCE_KEY_FILE, value: /run/hermes-hux-evidence/evidence-key}
|
|
- {name: HUX_RELEASE_EVIDENCE_POLICY_FILE, value: /etc/hux-evidence/policy.json}
|
|
- {name: HUX_READS_PER_MINUTE, value: "600"}
|
|
- {name: HUX_WRITES_PER_MINUTE, value: "120"}
|
|
- {name: HUX_REQUEST_TIMEOUT_SECONDS, value: "10"}
|
|
volumeMounts:
|
|
- {name: hux-data, mountPath: /var/lib/hux, subPathExpr: $(POD_NAME)}
|
|
- {name: hux-relay-key, mountPath: /run/hermes-webui-hux, readOnly: true}
|
|
- {name: hux-worker-key, mountPath: /run/hermes-hux-worker, readOnly: true}
|
|
- {name: hux-tmp, mountPath: /tmp}
|
|
- {name: hux-evidence-key, mountPath: /run/hermes-hux-evidence, readOnly: true}
|
|
- {name: hux-evidence-policy, mountPath: /etc/hux-evidence, readOnly: true}
|
|
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: 2
|
|
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: 3
|
|
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}
|
|
- name: hux-evidence-producer
|
|
image: registry.bstein.dev/bstein/hermes-webui:git-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release@sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04 # {"$imagepolicy": "hermes:hermes-webui-release"}
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/bin/sh, -ec]
|
|
args:
|
|
- |
|
|
ordinal="${HOSTNAME##*-}"
|
|
export HUX_TENANT_SLOT="slot-${ordinal}"
|
|
while true; do
|
|
if [ -s /run/hermes-hux-evidence/evidence-key ] \
|
|
&& [ -s /run/hermes-hux-subject/subject ] \
|
|
&& [ -n "${HUX_PRODUCER_PROJECT_ID:-}" ] \
|
|
&& [ -n "${HUX_PRODUCER_CONVERSATION_ID:-}" ]; then
|
|
HUX_PRODUCER_SUBJECT="$(tr -d '\r\n' < /run/hermes-hux-subject/subject)" \
|
|
/opt/hermes/.venv/bin/python -c \
|
|
'from hux_producer import run_once; run_once()' || true
|
|
fi
|
|
sleep 60
|
|
done
|
|
env:
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.name
|
|
- {name: PYTHONPATH, value: /opt/hermes-hux}
|
|
- {name: PYTHONDONTWRITEBYTECODE, value: "1"}
|
|
- {name: HOME, value: /tmp}
|
|
- {name: HUX_BASE_URL, value: 'http://127.0.0.1:8790'}
|
|
- {name: HUX_PRODUCER_WORKLOAD, value: hermes-webui}
|
|
- {name: HUX_PRODUCER_NAMESPACE, value: hermes}
|
|
- {name: HUX_PRODUCER_POD_SELECTOR, value: 'app=hermes-chat-tenant'}
|
|
- {name: HUX_PRODUCER_WORKLOAD_KIND, value: statefulset}
|
|
- {name: HUX_PRODUCER_WORKLOAD_NAME, value: hermes-chat-tenant}
|
|
- {name: HUX_PRODUCER_TIMEOUT_SECONDS, value: "10"}
|
|
- {name: HUX_RELEASE_EVIDENCE_KEY_FILE, value: /run/hermes-hux-evidence/evidence-key}
|
|
- {name: HUX_RELEASE_EVIDENCE_POLICY_FILE, value: /etc/hux-evidence/policy.json}
|
|
envFrom:
|
|
- configMapRef:
|
|
name: hermes-hux-evidence-scope
|
|
optional: true
|
|
volumeMounts:
|
|
- {name: hux-evidence-key, mountPath: /run/hermes-hux-evidence, readOnly: true}
|
|
- {name: hux-evidence-policy, mountPath: /etc/hux-evidence, readOnly: true}
|
|
- {name: hux-data, mountPath: /run/hermes-hux-subject, subPathExpr: $(POD_NAME)/binding, readOnly: true}
|
|
- {name: hux-tmp, mountPath: /tmp}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 10m, memory: 48Mi}
|
|
limits: {cpu: 100m, memory: 128Mi}
|
|
- name: telegram-media
|
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
|
imagePullPolicy: IfNotPresent
|
|
command: [/opt/hermes/.venv/bin/python, /opt/coordinator/telegram_media_server.py]
|
|
ports:
|
|
- {name: telegram-media, containerPort: 8788, protocol: TCP}
|
|
env:
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.name
|
|
- {name: HERMES_MEDIA_RELAY_KEY_FILE, value: /runtime-access/chat-relay-key}
|
|
volumeMounts:
|
|
- {name: home, mountPath: /opt/data, readOnly: true}
|
|
- {name: workspace, mountPath: /opt/data/workspace, readOnly: true}
|
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
|
- {name: runtime-access, mountPath: /runtime-access, readOnly: true}
|
|
readinessProbe:
|
|
httpGet: {path: /healthz, port: telegram-media}
|
|
initialDelaySeconds: 2
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet: {path: /healthz, port: telegram-media}
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 20
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
readOnlyRootFilesystem: true
|
|
runAsUser: 10000
|
|
runAsGroup: 10000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
resources:
|
|
requests: {cpu: 10m, memory: 24Mi}
|
|
limits: {cpu: 100m, memory: 64Mi}
|
|
volumes:
|
|
- name: runtime-access
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 2Mi
|
|
- name: config
|
|
configMap:
|
|
name: hermes-chat-config
|
|
- name: coordinator
|
|
configMap:
|
|
name: hermes-coordinator
|
|
defaultMode: 0555
|
|
- name: auth-patch
|
|
emptyDir: {}
|
|
- name: api-server-patch
|
|
emptyDir: {}
|
|
- name: stream-recovery-patch
|
|
emptyDir: {}
|
|
- name: subprocess-secret-patch
|
|
emptyDir: {}
|
|
- name: auto-router-plugin
|
|
configMap:
|
|
name: hermes-auto-router-plugin
|
|
- name: image-plugin
|
|
configMap:
|
|
name: hermes-chat-image-plugin
|
|
- 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}
|
|
- name: tmp
|
|
emptyDir:
|
|
sizeLimit: 256Mi
|
|
- name: hux-relay-key
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 1Mi
|
|
- name: hux-worker-key
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 1Mi
|
|
- name: hux-tmp
|
|
emptyDir:
|
|
sizeLimit: 64Mi
|
|
- name: hux-evidence-key
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 1Mi
|
|
- name: hux-evidence-policy
|
|
configMap:
|
|
name: hermes-hux-evidence-policy
|
|
defaultMode: 0444
|
|
- name: hux-data
|
|
persistentVolumeClaim:
|
|
claimName: hermes-chat-hux-data
|
|
volumeClaimTemplates:
|
|
- metadata:
|
|
name: home
|
|
labels:
|
|
app: hermes-chat-tenant
|
|
spec:
|
|
accessModes: [ReadWriteOnce]
|
|
storageClassName: astreae
|
|
resources:
|
|
requests:
|
|
storage: 10Gi
|
|
- metadata:
|
|
name: workspace
|
|
labels:
|
|
app: hermes-chat-tenant
|
|
ai.bstein.dev/data: user-workspace
|
|
spec:
|
|
accessModes: [ReadWriteMany]
|
|
storageClassName: astreae
|
|
resources:
|
|
requests:
|
|
storage: 10Gi
|