diff --git a/infrastructure/core/node-prefer-noschedule-cronjob.yaml b/infrastructure/core/node-prefer-noschedule-cronjob.yaml index 6e82d7f8..e9c225c1 100644 --- a/infrastructure/core/node-prefer-noschedule-cronjob.yaml +++ b/infrastructure/core/node-prefer-noschedule-cronjob.yaml @@ -82,9 +82,8 @@ spec: fi if k get node titan-21 >/dev/null 2>&1; then - # Keep replicas off the Jetson, but allow Longhorn to attach - # stateful fallback workloads there. - k label node titan-21 longhorn-host=true --overwrite=true || true + # The Tegra iSCSI initiator cannot host Longhorn engines. + k label node titan-21 longhorn-host- || true else echo "skipping missing node titan-21" fi diff --git a/infrastructure/vault-injector/helmrelease.yaml b/infrastructure/vault-injector/helmrelease.yaml index 4b305675..08bbb20c 100644 --- a/infrastructure/vault-injector/helmrelease.yaml +++ b/infrastructure/vault-injector/helmrelease.yaml @@ -42,6 +42,8 @@ spec: agentImage: repository: hashicorp/vault tag: "1.17.6" + agentDefaults: + cpuRequest: 25m webhook: failurePolicy: Ignore nodeSelector: diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index e572b4cb..be4c9919 100644 --- a/services/hermes/agent-deployment.yaml +++ b/services/hermes/agent-deployment.yaml @@ -24,7 +24,7 @@ spec: 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: rpi5 preferred; rpi4 next; titan-21 is the stateful fallback + ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available ai.bstein.dev/config-rev: "20260818-ai-usage-exporter-split-v3" prometheus.io/scrape: "true" prometheus.io/path: /metrics @@ -107,18 +107,6 @@ spec: - key: kubernetes.io/hostname operator: NotIn values: [titan-04, titan-08, titan-13, titan-14, titan-17, titan-18, titan-19] - - matchExpressions: - - key: kubernetes.io/arch - operator: In - values: [arm64] - - key: node-role.kubernetes.io/accelerator - operator: Exists - - key: kubernetes.io/hostname - operator: In - values: [titan-21] - - key: longhorn-host - operator: In - values: ["true"] preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: @@ -126,18 +114,6 @@ spec: - key: hardware operator: In values: [rpi5] - - weight: 50 - preference: - matchExpressions: - - key: hardware - operator: In - values: [rpi4] - - weight: 25 - preference: - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: [titan-21] initContainers: - name: init-config image: busybox:1.37 @@ -711,7 +687,7 @@ spec: seccompProfile: type: RuntimeDefault resources: - requests: {cpu: 200m, memory: 512Mi} + requests: {cpu: 150m, memory: 384Mi} limits: {cpu: "3", memory: 6Gi} - name: oauth2-proxy image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.3@sha256:10a1165743a192e1940b4708fb9647027185ce11a681a1c5519b442ff7f1f561 @@ -881,7 +857,7 @@ spec: seccompProfile: type: RuntimeDefault resources: - requests: {cpu: 25m, memory: 64Mi} + requests: {cpu: 25m, memory: 48Mi} limits: {cpu: 500m, memory: 512Mi} - name: cli-lane-runner image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 @@ -939,7 +915,7 @@ spec: timeoutSeconds: 5 failureThreshold: 3 resources: - requests: {cpu: 50m, memory: 192Mi} + requests: {cpu: 25m, memory: 128Mi} limits: {cpu: "2", memory: 6Gi} - name: model-steward image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 @@ -968,7 +944,7 @@ spec: - {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py} - {name: routing-catalog, mountPath: /routing-catalog} resources: - requests: {cpu: 25m, memory: 64Mi} + requests: {cpu: 25m, memory: 48Mi} limits: {cpu: 250m, memory: 512Mi} - name: kanban-supervisor image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 @@ -999,7 +975,7 @@ spec: - {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: 64Mi} + requests: {cpu: 10m, memory: 48Mi} limits: {cpu: 250m, memory: 256Mi} - name: credential-sync image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 diff --git a/services/hermes/execution-coordinator-patch.yaml b/services/hermes/execution-coordinator-patch.yaml index 816f6068..753e0919 100644 --- a/services/hermes/execution-coordinator-patch.yaml +++ b/services/hermes/execution-coordinator-patch.yaml @@ -59,7 +59,7 @@ spec: initialDelaySeconds: 30 periodSeconds: 30 resources: - requests: {cpu: 50m, memory: 128Mi} + requests: {cpu: 50m, memory: 96Mi} limits: {cpu: 500m, memory: 512Mi} volumes: - name: execution-pool-code diff --git a/testing/tests/test_hermes_agent_layout.py b/testing/tests/test_hermes_agent_layout.py index 1e945b95..2946c527 100644 --- a/testing/tests/test_hermes_agent_layout.py +++ b/testing/tests/test_hermes_agent_layout.py @@ -134,13 +134,13 @@ def test_cli_lane_reserves_cpu_headroom_for_ui_and_auth(): assert environment["HERMES_CLI_LANE_CONCURRENCY"] == "2" assert lane["resources"] == { - "requests": {"cpu": "50m", "memory": "192Mi"}, + "requests": {"cpu": "25m", "memory": "128Mi"}, "limits": {"cpu": "2", "memory": "6Gi"}, } -def test_agent_avoids_unhealthy_nodes_and_has_a_stateful_fallback(): - """The owner agent keeps a bounded fallback when Pi workers are full.""" +def test_agent_avoids_unhealthy_nodes_and_stays_on_storage_workers(): + """The owner agent stays off unhealthy and attach-incompatible nodes.""" pod = _agent_deployment()["spec"]["template"]["spec"] terms = pod["affinity"]["nodeAffinity"][ "requiredDuringSchedulingIgnoredDuringExecution" @@ -154,24 +154,24 @@ def test_agent_avoids_unhealthy_nodes_and_has_a_stateful_fallback(): assert hostnames["operator"] == "NotIn" assert set(hostnames["values"]) >= {"titan-04", "titan-19"} - fallback = {item["key"]: item for item in terms[1]["matchExpressions"]} - assert fallback["node-role.kubernetes.io/accelerator"]["operator"] == "Exists" - assert fallback["kubernetes.io/hostname"]["values"] == ["titan-21"] - assert fallback["longhorn-host"]["values"] == ["true"] + assert len(terms) == 1 preferences = pod["affinity"]["nodeAffinity"][ "preferredDuringSchedulingIgnoredDuringExecution" ] - assert [item["weight"] for item in preferences] == [100, 50, 25] + assert [item["weight"] for item in preferences] == [100] hermes = next(item for item in pod["containers"] if item["name"] == "hermes") - assert hermes["resources"]["requests"]["cpu"] == "200m" + assert hermes["resources"]["requests"] == { + "cpu": "150m", + "memory": "384Mi", + } node_labels = ( Path(__file__).parents[2] / "infrastructure/core/node-prefer-noschedule-cronjob.yaml" ).read_text() - assert "k label node titan-21 longhorn-host=true" in node_labels + assert "k label node titan-21 longhorn-host-" in node_labels def test_agent_root_is_stock_dashboard_and_terminal_is_a_separate_path():