diff --git a/services/hermes/agent-configmap.yaml b/services/hermes/agent-configmap.yaml index ee41489af..4f9cb8934 100644 --- a/services/hermes/agent-configmap.yaml +++ b/services/hermes/agent-configmap.yaml @@ -120,6 +120,13 @@ data: tool_progress: all interim_assistant_messages: true long_running_notifications: true + # The upstream reconnect recap clips older messages to 300/200 chars. + # Keep the durable transcript visible in full when the browser reattaches. + resume_exchanges: 10000 + resume_max_user_chars: 10000000 + resume_max_assistant_chars: 10000000 + resume_max_assistant_lines: 1000000 + resume_skip_tool_only: true tool_loop_guardrails: warnings_enabled: true diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index f4122d488..ac751946a 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: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available - ai.bstein.dev/config-rev: "20260809-browser-delegation-tools" + ai.bstein.dev/config-rev: "20260809-browser-runtime-complete-resume" 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 @@ -277,6 +277,9 @@ spec: - {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml} - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock} - {name: PATH, value: /opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/.venv/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin} + - {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright} + - {name: AGENT_BROWSER_EXECUTABLE_PATH, value: /opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell} + - {name: AGENT_BROWSER_ARGS, value: "--no-sandbox,--disable-dev-shm-usage"} - {name: HERMES_DASHBOARD, value: "0"} - {name: HERMES_DASHBOARD_PUBLIC_URL, value: https://agent.hermes.bstein.dev} - {name: API_SERVER_ENABLED, value: "true"} @@ -478,6 +481,9 @@ spec: --env HERMES_AUTH_FILE=/shared-auth/auth.json \ --env HOME=/opt/data/home \ --env PYTHONPATH=/opt/hermes \ + --env PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright \ + --env AGENT_BROWSER_EXECUTABLE_PATH=/opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell \ + --env AGENT_BROWSER_ARGS=--no-sandbox,--disable-dev-shm-usage \ --focus)" pane="$(printf '%s' "${created}" | /opt/hermes/.venv/bin/python -c \ 'import json,sys; print(json.load(sys.stdin)["result"]["root_pane"]["pane_id"])')" @@ -510,6 +516,9 @@ spec: - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock} - {name: PYTHONPATH, value: /opt/hermes} - {name: PATH, value: /opt/data/tools/bin:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin} + - {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright} + - {name: AGENT_BROWSER_EXECUTABLE_PATH, value: /opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell} + - {name: AGENT_BROWSER_ARGS, value: "--no-sandbox,--disable-dev-shm-usage"} securityContext: allowPrivilegeEscalation: false runAsUser: 10000 diff --git a/services/hermes/chat-statefulset.yaml b/services/hermes/chat-statefulset.yaml index 03d039756..d5788cb24 100644 --- a/services/hermes/chat-statefulset.yaml +++ b/services/hermes/chat-statefulset.yaml @@ -28,7 +28,7 @@ spec: ai.bstein.dev/role: isolated-user-chat 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: "20260809-browser-files-auth-refresh" + ai.bstein.dev/config-rev: "20260809-browser-runtime-path" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: hermes-chat vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens @@ -185,6 +185,9 @@ spec: - {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} diff --git a/testing/tests/test_hermes_chat_quality.py b/testing/tests/test_hermes_chat_quality.py index d2c783f90..4db707679 100644 --- a/testing/tests/test_hermes_chat_quality.py +++ b/testing/tests/test_hermes_chat_quality.py @@ -128,6 +128,13 @@ def test_chat_auth_file_mount_survives_atomic_provider_refresh(): assert mount["mountPath"] == "/shared-auth" assert "subPath" not in mount + hermes_env = { + item["name"]: item["value"] + for item in next(item for item in containers if item["name"] == "hermes")["env"] + } + assert hermes_env["AGENT_BROWSER_EXECUTABLE_PATH"].endswith("/chrome-linux/headless_shell") + assert "--no-sandbox" in hermes_env["AGENT_BROWSER_ARGS"] + def test_sandbox_executes_python_with_bounded_output(tmp_path: Path, monkeypatch): source = ROOT / "dockerfiles" / "hermes-chat-sandbox-server.py" diff --git a/testing/tests/test_hermes_herdr.py b/testing/tests/test_hermes_herdr.py index ca1c45b77..ddd281834 100644 --- a/testing/tests/test_hermes_herdr.py +++ b/testing/tests/test_hermes_herdr.py @@ -199,6 +199,11 @@ def test_agent_installs_hermes_integration_before_startup(): env = {item["name"]: item["value"] for item in containers[name]["env"]} assert "/opt/hermes/.venv/bin" in env["PATH"].split(":") + for name in ("hermes", "herdr-server"): + env = {item["name"]: item["value"] for item in containers[name]["env"]} + assert env["AGENT_BROWSER_EXECUTABLE_PATH"].endswith("/chrome-linux/headless_shell") + assert "--no-sandbox" in env["AGENT_BROWSER_ARGS"] + server_command = containers["herdr-server"]["command"][-1] assert 'herdr pane process-info --pane "${pane}"' in server_command assert 'herdr workspace close "${workspace}"' in server_command @@ -208,6 +213,7 @@ def test_agent_installs_hermes_integration_before_startup(): assert "herdr agent start coordinator" in server_command assert "--kind hermes" in server_command assert "--timeout 60000" in server_command + assert "--env AGENT_BROWSER_EXECUTABLE_PATH=" in server_command tui_command = containers["herdr-tui"]["args"][0] assert "while true" in tui_command @@ -233,6 +239,27 @@ def test_agent_mounts_auto_router_into_both_hermes_runtimes(): assert volume["configMap"]["name"] == "hermes-auto-router-plugin" +def test_agent_reconnect_renders_complete_conversation_history(): + documents = [ + document + for document in yaml.safe_load_all((HERMES / "agent-configmap.yaml").read_text()) + if document + ] + config_map = next( + document + for document in documents + if document["kind"] == "ConfigMap" + and document["metadata"]["name"] == "hermes-agent-config" + ) + config = yaml.safe_load(config_map["data"]["config.yaml"]) + display = config["display"] + + assert display["resume_exchanges"] >= 10000 + assert display["resume_max_user_chars"] >= 10000000 + assert display["resume_max_assistant_chars"] >= 10000000 + assert display["resume_max_assistant_lines"] >= 1000000 + + def test_agent_coordinator_has_a_long_running_tool_budget(): configmap = yaml.safe_load((HERMES / "agent-configmap.yaml").read_text()) config = yaml.safe_load(configmap["data"]["config.yaml"])