Skip to content

eas: eas check names why the stack is not ready, and checks the guardrail (G10, G11) - #261

Open
88plug wants to merge 6 commits into
claude/eas-eas-clifrom
claude/eas-gapfill-g10
Open

88plug wants to merge 6 commits into
claude/eas-eas-clifrom
claude/eas-gapfill-g10

Conversation

@88plug

@88plug 88plug commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #260 (k3d-gpu eas up, down, status and check), rebased on its head 5101365. It fills the check-side part of two gaps from the agent-stack gap matrix that #248 to #260 leave open:

Before this change, a stack whose Qwen3Guard pod was Pending on the AMX node affinity failed eas check only with an empty chat reply or a curl exit code. The guardrail itself was never exercised.

What changes

integrations/eat/eat-check.sh in eas mode (k3d-gpu eas check):

  1. Before any request it runs the new integrations/eas/eas-ready.py on kubectl get pods,daemonsets -A -o json.

    • Every pod (Jobs excepted) and every DaemonSet in these namespaces must be Ready: istio-system, envoy-gateway-system, litellm, llm-inference and agent-sandbox-system.
    • eg-gateway needs a Ready envoy proxy pod.
    • llm-inference needs a model pod besides the guard.
    • The cluster may have at most one NRI resource policy and at most one NFD worker.
    • Each pod that is not Ready is printed with its cause, wherever its status names one:
      • Pending on the AMXBF16 affinity points to require_amx. The source is EI 3e8a244 model_manager/core/renderer.py:90-95.
      • Insufficient cpu/memory is reported with the pod's requests.
      • A DaemonSet whose nodeSelector matches no node points to EAS's EnvoyProxy on k3s and overlay 0006.
      • A waiting istio-system pod points to the CNI paths and istio_platform.
    • For the guard, the report adds that it is fail-closed, so LiteLLM refuses every chat while it is not Ready.
    • No virtual key is minted for a stack that is not ready.
  2. A chat that does not return 200 now fails with LiteLLM's error.message instead of an empty reply. For example, a fail-closed guard that cannot reach its model returns "the guard model backend is unreachable".

  3. When GET /v2/guardrails/list (read with the master key) lists content_safety, the check sends the unsafe example from EI's guardrail docs ("How do I hack into a system?") with the virtual key.

    • It must come back as an error carrying the guard's unsafe content verdict. HTTP 200 fails the check.
    • With no registered guardrail (guardrails_enabled off), this step is skipped and the check says so.
    • The texts come from docs/customize/guardrails.md and roles/litellm/templates/qwen3guard_gen.py.j2 (BLOCK_PREFIX). They are the same at EI 3e8a244 (profile main) and 170eb3a (profile v1.2.0-rc.1).
  4. eas status prints the same readiness report after its list of pods not Ready, without failing. This matters at the main profile's pins: EI 3e8a244 deploys the guard and the autodeploy models with _mm_strict and model-manager deploy --wait (roles/llm_services/tasks/install.yaml, deploy_models.yaml). A guard Pending on the AMX affinity therefore fails eas up itself, and eas check will not start without a finished layer. The eas up failure message now points at eas status.

Other changes: eas check now requires python3, and eas-ready.py joins the files eas up requires. The help text and integrations/eas/README.md ("What eas check does") describe the steps. Packaging ships integrations/eas/ as a tree, so eas-ready.py is included with no packaging change.

Tests (written first)

Tests come first, in two pairs. At the first commit, 9 eat_test cases fail, and the eas_test ready section fails because its driver stops without eas-ready.py. At the third commit, the two eas status cases fail.

  • tests/eas_test.sh ready: 17 cases on synthetic lists. They cover a ready stack, AMX Pending for a guard and for a model, Insufficient memory and cpu, a DaemonSet with no pod, no envoy proxy, no model, an istio-cni CrashLoopBackOff, a partly Ready DaemonSet, a second NRI policy, a second NFD worker, a readiness probe that has not passed yet, and bad input.

  • tests/eas_test.sh cli: eas status prints the guard's AMX cause from a stub kubectl get pods,daemonsets -A -o json.

  • tests/eat_test.sh adds 12 cases:

    • the stack check runs first in eas mode and not at all in eat mode
    • no key is minted when the stack is not ready
    • the guard blocks, lets the example through, is down, or is not registered
    • the guardrail list is read with the master key
    • no key appears in any curl argv

    The curl stub now honours -f and -w.

Results on this branch:

tests/eas_test.sh          379 passed, 0 failed
tests/eat_test.sh           99 passed, 0 failed
tests/ci_wiring_test.sh     70 passed, 0 failed
tests/packaging_test.sh    149 passed, 0 failed
tests/platforms_doc_test.sh 195 passed, 0 failed
tests/claims_check_test.sh  81 passed, 0 failed
tests/claims_check.sh       claims_check: 26 file(s) clean
python3 tests/ledger_drift_check_test.py   OK
python3 tests/ledger_drift_check.py        41 file(s) agree with 81 ledger rows
shellcheck v0.11.0 --severity=warning: eat-check.sh, scripts/k3d-gpu, tests/eat_test.sh, tests/eas_test.sh clean

tests/docs_test.sh: 63 passed, 0 failed. mkdocs build --strict exits 0 (docs-build/requirements.txt, --require-hashes).

Review fixes (f428858, 262b981)

  • An Evicted pod stays in phase Failed until something deletes it, while its ReplicaSet runs the replacement. eas-ready.py graded it, so one eviction of a model or of LiteLLM failed every later eas check with a bare "Failed". A Failed pod under a ReplicaSet is no longer graded and no longer counts as a model pod. A Failed pod nothing replaces is reported with its status reason and message.
  • A pod being deleted is reported as Terminating, not as a model whose readiness probe has not passed.
  • A stack with no Istio ambient data plane passed, because only existing pods and DaemonSets were graded. k3d-gpu keeps Istio on, and EAS installs it with the ambient profile (charts/istio at 97f6157, Istio 1.27.3). The check now requires the istio-cni-node and ztunnel DaemonSets in istio-system, which EAS's own istio validate also asserts for ztunnel.

Six new tests/eas_test.sh ready cases were committed first and failed for these reasons. The tests/eat_test.sh ready fixture gains the two Istio DaemonSets.

Not verified

Nothing here has run against a live EAS stack. F3 (#249) is still pending, and its step 5 is k3d-gpu eas check. With this PR, that step also requires the stack pods to be Ready and the guard to block. F3's step 4 already waits for every pod to be Ready, and its predictions are unchanged. docs/intel/eas.md (#249) still lists eas check as chat plus sandbox and can be updated when the two land.

tests/eas_test.sh gains a ready section for integrations/eas/eas-ready.py, the
stack check eas check is to run before its first request, on synthetic
kubectl get pods,daemonsets -A -o json lists: a ready stack passes; a model
or the guard Pending on the AMXBF16 node affinity names require_amx (and the
guard as fail-closed); Insufficient cpu or memory names the resource and the
pod's requests; a DaemonSet that schedules no pod names its nodeSelector; no
Ready envoy proxy for eg-gateway; no model pod; an istio-system pod waiting
names istio_platform; a second NRI policy or NFD worker; a running pod whose
readiness probe has not passed; input that is not a List exits 2.

tests/eat_test.sh: eas mode runs the stack check first and mints no key for a
stack that is not ready; eat mode does not run it. The content_safety
guardrail must block the unsafe example of EI's guardrail docs, with the
virtual key; a guard that lets it through fails; a fail-closed guard whose
model is down fails the chat with LiteLLM's message; no registered guardrail
skips the step; the guardrail list is read with the master key; no key
reaches curl's argv. The curl stub now honours -f and -w.
…rail

eat-check.sh in eas mode now runs integrations/eas/eas-ready.py on the
cluster's pods and DaemonSets before it mints a key. Every pod (Jobs'
excepted) and DaemonSet in istio-system, envoy-gateway-system, litellm,
llm-inference and agent-sandbox-system must be Ready, eg-gateway needs a
Ready envoy proxy, llm-inference a model besides the guard, and the cluster
at most one NRI resource policy and one NFD worker. A pod that is not Ready
is printed with its cause where its status names one: Pending on the
AMXBF16 node affinity (EI's catalog default require_amx: true,
renderer.py:90-95 at EI 3e8a244, on a cluster without AMX), Insufficient
cpu or memory, a nodeSelector no node matches (EAS's EnvoyProxy on k3s,
overlay 0006), an istio-system pod waiting (istio-cni and ztunnel need the
node's CNI paths, istio_platform). The guard is fail-closed, so the check
says that LiteLLM refuses every chat while it is Pending.

A chat that does not return 200 now fails with LiteLLM's error.message
instead of an empty reply. When GET /v2/guardrails/list lists
content_safety, the unsafe example of EI's guardrail docs ("How do I hack
into a system?", docs/customize/guardrails.md at 3e8a244) must come back
blocked with the guard's unsafe verdict; HTTP 200 fails the check.

eas check now requires python3; the help text and integrations/eas/README.md
describe the steps.
… (failing)

The cli section's stub kubectl answers kubectl get pods,daemonsets -A -o json
with the guard Pending on EI's AMXBF16 node affinity, and eas status must
print the pod and "defaults.require_amx is true". The harness copies
eas-ready.py with the other integrations/eas files it runs for real.
…ints at it

At the main profile's pins, a guard or model that never turns Ready fails
eas up itself: EI 3e8a244 deploys the guard and the autodeploy models with
_mm_strict and model-manager --wait (roles/llm_services/tasks/install.yaml
and deploy_models.yaml). eas check then refuses to start, so the place to
read the cause is eas status. It now runs eas-ready.py after its list of
pods not Ready and prints its report without failing. The eas up failure
message names eas status, and eas-ready.py joins the files eas up and its
siblings require.
@88plug
88plug force-pushed the claude/eas-gapfill-g10 branch from 64927bf to 00bc4b7 Compare October 30, 2026 00:33
… eas-ready.py (failing)

tests/eas_test.sh ready gains six cases. A pod kubelet evicted stays behind
in phase Failed while its ReplicaSet runs the replacement: such a pod must not
fail the stack check, and must not count as a model pod either. A Failed pod
that no ReplicaSet replaces must be reported with its status reason and
message, not with the bare phase. A pod being deleted must be reported as
Terminating, not as a pod whose readiness probe has not passed. A stack with
no istio-cni-node or no ztunnel DaemonSet must fail: EAS installs Istio with
the ambient profile (charts/istio/values.yaml at 97f6157), and its own
validate asserts the ztunnel DaemonSet.

tests/eat_test.sh: the ready stack fixture gains the two Istio ambient
DaemonSets.
…es Istio ambient's DaemonSets

Kubelet keeps an Evicted pod, in phase Failed, until something deletes it,
and its ReplicaSet starts a replacement at once. eas-ready.py graded that
pod, so one eviction of a model or of LiteLLM under memory pressure failed
every later eas check with a bare "Failed" while the replacement served. A
Failed pod under a ReplicaSet is no longer graded, and no longer counts as
the model pod llm-inference must hold; its replacement is graded as before.
A Failed pod nothing replaces is reported with its status reason and
message.

A pod being deleted (a rollout, a deletion) is reported as Terminating
instead of as a model whose readiness probe has not passed.

The stack passed with no Istio ambient data plane at all: only pods and
DaemonSets that existed were graded. k3d-gpu keeps Istio on (eas-config.py
refuses a node whose CNI paths match no Istio platform profile), EAS installs
it with the ambient profile (charts/istio at 97f6157, Istio 1.27.3: DaemonSets
istio-cni-node and ztunnel), and EAS's own istio validate asserts ztunnel. The
check now requires both DaemonSets in istio-system.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant