From b21843e35a74f262a34e23f18f21d14847ed6a2e Mon Sep 17 00:00:00 2001 From: jenkins Date: Sat, 8 Aug 2026 22:05:39 -0300 Subject: [PATCH] feat(hermes): expose persistent agent TUI --- services/hermes/agent-configmap.yaml | 5 +- services/hermes/agent-deployment.yaml | 97 ++++++++++++++++++++++++++- services/hermes/networkpolicy.yaml | 2 +- services/hermes/oauth2-proxy.yaml | 2 +- services/hermes/service.yaml | 3 + 5 files changed, 104 insertions(+), 5 deletions(-) diff --git a/services/hermes/agent-configmap.yaml b/services/hermes/agent-configmap.yaml index adaf8cd28..e4cd64309 100644 --- a/services/hermes/agent-configmap.yaml +++ b/services/hermes/agent-configmap.yaml @@ -186,8 +186,9 @@ data: START-HERE.md: | # Agent Hermes - Select the Cassandra project and state the outcome you want. Hermes will - classify its difficulty, choose Codex or Claude Code, preserve the task on + The authenticated root of agent.hermes.bstein.dev opens the persistent + Herdr terminal interface. Give Hermes the outcome you want and it will + classify the difficulty, choose Codex or Claude Code, preserve the task on the Cassandra board, supervise the worker through Herdr, and synthesize the evidence. The first native Codex worker requires one device-code login; subsequent sessions persist on the agent volume. diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index 53406320a..b61afdc6b 100644 --- a/services/hermes/agent-deployment.yaml +++ b/services/hermes/agent-deployment.yaml @@ -24,7 +24,7 @@ spec: ai.bstein.dev/execution: Herdr-supervised Codex and Claude Code ai.bstein.dev/model-policy: difficulty-aware low through xhigh, cross-provider fallback ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available - ai.bstein.dev/config-rev: "20260808-herdr-coordinator" + ai.bstein.dev/config-rev: "20260808-herdr-browser-tui" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: hermes-agent vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens @@ -155,6 +155,14 @@ spec: chmod 0755 "${tools}/bin/herdr.tmp" mv "${tools}/bin/herdr.tmp" "${tools}/bin/herdr" fi + ttyd_version="$("${tools}/bin/ttyd" --version 2>/dev/null || true)" + case "${ttyd_version}" in *1.7.7*) ttyd_ready=1 ;; *) ttyd_ready=0 ;; esac + if [ "${ttyd_ready}" != "1" ]; then + curl -fsSL -o "${tools}/bin/ttyd.tmp" https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.aarch64 + printf '%s %s\n' b38acadd89d1d396a0f5649aa52c539edbad07f4bc7348b27b4f4b7219dd4165 "${tools}/bin/ttyd.tmp" | sha256sum -c - + chmod 0755 "${tools}/bin/ttyd.tmp" + mv "${tools}/bin/ttyd.tmp" "${tools}/bin/ttyd" + fi if [ ! -f "${tools}/.cli-versions-0.147.0-2.1.226" ]; then npm install --global --omit=dev --no-audit --no-fund --prefix "${tools}" \ @openai/codex@0.147.0 \ @@ -332,6 +340,65 @@ spec: resources: requests: {cpu: 50m, memory: 128Mi} limits: {cpu: 750m, memory: 1Gi} + - name: herdr-tui + image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f + imagePullPolicy: IfNotPresent + command: [/bin/sh, -ec] + args: + - | + set -a + . /opt/data/.env + set +a + exec /opt/data/tools/bin/ttyd \ + --writable \ + --check-origin \ + --auth-header X-Forwarded-User \ + --interface 0.0.0.0 \ + --port 7681 \ + --cwd /opt/data/workspace \ + --terminal-type xterm-256color \ + --client-option "titleFixed=Hermes Agent - HERDR" \ + --client-option fontSize=15 \ + /opt/data/tools/bin/herdr + ports: + - {name: herdr-tui, containerPort: 7681, protocol: TCP} + env: + - {name: HOME, value: /opt/data/home} + - {name: CODEX_HOME, value: /opt/data/home/.codex} + - {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude} + - {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml} + - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock} + - {name: PATH, value: /opt/data/tools/bin:/usr/local/bin:/usr/bin:/bin} + volumeMounts: + - {name: home, mountPath: /opt/data} + - {name: tmp, mountPath: /tmp} + startupProbe: + tcpSocket: {port: herdr-tui} + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 60 + readinessProbe: + tcpSocket: {port: herdr-tui} + periodSeconds: 10 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: {port: herdr-tui} + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 10000 + runAsGroup: 10000 + seccompProfile: + type: RuntimeDefault + resources: + requests: {cpu: 25m, memory: 64Mi} + limits: {cpu: 500m, memory: 512Mi} - name: herdr-server image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f imagePullPolicy: IfNotPresent @@ -352,13 +419,37 @@ spec: done herdr integration install codex || true herdr integration install claude || true + pane_file=/opt/data/herdr/coordinator-pane-id + pane="$(cat "${pane_file}" 2>/dev/null || true)" + if [ -z "${pane}" ] || ! herdr pane get "${pane}" >/dev/null 2>&1; then + created="$(herdr workspace create \ + --cwd /opt/data/workspace \ + --label coordinator \ + --env HERMES_HOME=/opt/data \ + --env HERMES_AUTH_FILE=/shared-auth/auth.json \ + --env HOME=/opt/data/home \ + --env PYTHONPATH=/opt/hermes \ + --focus)" + pane="$(printf '%s' "${created}" | /opt/hermes/.venv/bin/python -c \ + 'import json,sys; print(json.load(sys.stdin)["result"]["root_pane"]["pane_id"])')" + printf '%s\n' "${pane}" > "${pane_file}" + fi + process_info="$(herdr pane process-info --pane "${pane}" 2>/dev/null || printf '%s\n' '{"result":{"process_info":{"foreground_processes":[]}}}')" + coordinator_running="$(printf '%s' "${process_info}" | /opt/hermes/.venv/bin/python -c \ + 'import json,sys; processes=json.load(sys.stdin)["result"]["process_info"].get("foreground_processes", []); print("yes" if any("hermes" in " ".join(str(p.get(k) or "") for k in ("name", "argv0", "cmdline")) for p in processes) else "no")')" + if [ "${coordinator_running}" != "yes" ]; then + herdr pane run "${pane}" /opt/hermes/.venv/bin/hermes + fi wait "${server_pid}" env: + - {name: HERMES_HOME, value: /opt/data} + - {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json} - {name: HOME, value: /opt/data/home} - {name: CODEX_HOME, value: /opt/data/home/.codex} - {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude} - {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml} - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock} + - {name: PYTHONPATH, value: /opt/hermes} - {name: PATH, value: /opt/data/tools/bin:/usr/local/bin:/usr/bin:/bin} securityContext: allowPrivilegeEscalation: false @@ -368,6 +459,10 @@ spec: type: RuntimeDefault volumeMounts: - {name: home, mountPath: /opt/data} + - {name: provider-auth, mountPath: /shared-auth, readOnly: true} + - {name: coordinator, mountPath: /opt/coordinator, readOnly: true} + - {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py} + - {name: coordinator, mountPath: /opt/data/home/.local/bin/herdr-dispatch, subPath: herdr_dispatch.py, readOnly: true} resources: requests: {cpu: 50m, memory: 128Mi} limits: {cpu: "1", memory: 2Gi} diff --git a/services/hermes/networkpolicy.yaml b/services/hermes/networkpolicy.yaml index e003d358a..3d83f5755 100644 --- a/services/hermes/networkpolicy.yaml +++ b/services/hermes/networkpolicy.yaml @@ -72,7 +72,7 @@ spec: matchLabels: app: oauth2-proxy-hermes-agent ports: - - {protocol: TCP, port: 8787} + - {protocol: TCP, port: 7681} egress: - to: - namespaceSelector: diff --git a/services/hermes/oauth2-proxy.yaml b/services/hermes/oauth2-proxy.yaml index a10a1cc8f..3651cfb25 100644 --- a/services/hermes/oauth2-proxy.yaml +++ b/services/hermes/oauth2-proxy.yaml @@ -96,7 +96,7 @@ spec: - --cookie-samesite=lax - --cookie-refresh=1h - --cookie-expire=8h - - --upstream=http://hermes-agent.hermes.svc.cluster.local:8787 + - --upstream=http://hermes-agent.hermes.svc.cluster.local:7681 - --http-address=0.0.0.0:4180 - --skip-provider-button=true - --reverse-proxy=true diff --git a/services/hermes/service.yaml b/services/hermes/service.yaml index 871efbbaf..56354c4a1 100644 --- a/services/hermes/service.yaml +++ b/services/hermes/service.yaml @@ -54,6 +54,9 @@ spec: - name: dashboard port: 8787 targetPort: dashboard + - name: herdr-tui + port: 7681 + targetPort: herdr-tui --- apiVersion: v1 kind: Service