Agent workspaces
Each agent whose harness is xpander and that uses a workspace gets its own pod, created on demand and reclaimed when idle. Size for concurrently active workspaces, not total agents.
Idle workspaces use ~50 Mi. An active one uses ~0.5-1 vCPU and up to 1 GiB, bursting to its limits. The volume persists after the pod is reclaimed, so plan 5 GiB per workspace-using agent, not per pod.
The workspace image is ~1 GiB and is pulled on the first start on each node. Budget ~2 GiB of node disk for the image cache.
The executor fleet
The fleet is afleet-executor StatefulSet. Each replica is one PodSecurity baseline pod over one XFS volume: no privileged container, no host namespaces, no user namespaces and no node preparation. The pod holds the executor container, which claims turns, plus one slot-<i> container per turn slot (fleet.turnsMax). Each turn runs in its own slot container, as the agent’s own uid. It has its own processes, /proc, /tmp and memory and CPU limit, behind a seccomp profile, no_new_privs and Landlock. An agent’s directory lives on exactly one executor’s volume, and its turns always run there.
The fleet is always rendered by both charts (there is no enable flag). It starts warm with one executor (
fleet.replicas: 1 on Hybrid, global.fleet.replicas: 1 on Air-Gapped). Harness agents (Claude Code, Codex, OpenCode) run only on the executors, never as a pod per agent. So the chart always keeps at least one: replicas: 0 (or a blank value) renders one executor. The one case that renders zero is a live cluster whose executor volume can never bind (see Idle guard). Agent turns then wait in the queue until it is fixed.Per executor
Defaults per executor pod: 2 / 8 vCPU and 8 / 28 GiB (request / limit), 12 slot containers, a 500 GiB XFS volume.Executor resources, volume, grace and placement
Executor resources, volume, grace and placement
Fleet values (Hybrid chart)
Fleet values (Hybrid chart)
Per turn
Ceilings per turn (fleet.turn). An agent can lower its own, never raise it.
Concurrency caps
There is no per-organization cap.
fleet.orgMaxTurns is removed, and a license’s agent cap does not limit concurrent turns. A values file that still sets it upgrades cleanly, and the key renders nothing.
Reading the runtime logs
One turn shows in two logs, the executor and the Agent Controller:Executor log events, in order
Executor log events, in order
Executor (
fleet-executor-<n>, container executor), in order:deploy/agent-controller). The same turn shows as:
direct turn conversation=<id> task=<id> status=... received_to_enqueued_ms=... enqueued_to_claimed_ms=...: the message became a task and was picked up by the fleet.enqueued_to_claimed_msis where a cold executor shows. The turn runs on the model the conversation picked. The agent record’s model is only the default for new conversations.event=fleet.claimed executor=fleet-executor-0: which executor took it.event=compute_usage.emitted cli=claude-code cpu_seconds=...: the compute record written when the turn ended.
llm-gw, kubectl -n xpander logs -f -l app=llm-gw). Each model call the turn made is one event=llm_gw.call llm_route=gateway_direct status=... org=<id> agent=<id> task=<id> client=... provider=... request_id=<id> line. To see the model a call used, paste its request_id into Request or trace ID and press Enter in Platform > AI governance > Logs, on your install’s own address.
Node prerequisites
All required: a cluster that fails one runs no agent turns.install.sh and the Air-Gapped preflight check each and name the fix. There is no Kubernetes 1.33, kernel 6.3, containerd 2.0 or user.max_user_namespaces minimum, and no node configuration.
Landlock on the executor nodes
Landlock limits each turn to the agent’s own files. It is on by default (fleet.landlock.enabled: true, or global.fleet.landlock.enabled on Air-Gapped). On a node without it, the executor takes no turn: /health answers 503, and its refusal field and the log say the kernel has no Landlock.
Landlock support by platform
Landlock support by platform
fleet.landlock.enabled: false, and Landlock is off on every node.
What stays and what is lost without Landlock
What stays and what is lost without Landlock
/health then shows landlock_enabled: false.
Fleet egress
The executors’ NetworkPolicy (fleet.networkPolicy.enabled, default true) gives agents the same reachability as any other pod, private ranges and in-cluster services included, minus link-local. fleet.networkPolicy.exceptCidrs defaults to ["169.254.0.0/16"] and exceptCidrsV6 to ["fe80::/10"]. Setting either replaces the whole list, so keep the link-local range in it. To close more ranges, use extraExceptCidrs. extraEgress appends raw egress rules. fleet.egress.allowHosts is still accepted for older agent-harness images and is no longer needed.
Autoscaling
An agent runs only on the executor that holds its home, so scaling in would strand agents. Withfleet.keda.enabled: true the chart renders a KEDA ScaledObject that keeps every executor running:
One executor per node, so grow the node group and
maxReplicas together. Without KEDA (the default), set fleet.replicas. On a sealed network, a fixed count sized for peak is recommended.
Safe rollout, drain, and upgrades
A roll never kills a running turn
A rolling update, a KEDA scale-in and a pod delete all sendSIGTERM, which the executor treats as a drain, not a kill:
- The executor stops claiming work, and its heartbeat says
draining. The Agent Controller routes around it: nothing new lands on it, and whatever was queued for it goes back to the shared queue. It still counts as fleet presence, so new turns wait for the replacement instead of starting anywhere else. - The turns in flight keep running for up to
fleet.executor.drainSeconds(3600 s by default). A turn still running when that window ends is stopped the way a user stop is. The CLI is interrupted, its session is kept, and the task endsstoppedwith “The executor is restarting; send the message again to continue where it left off.” Sending the message again resumes the session on the new pod. No task is left hanging as executing. - Each slot container holds its turn until the executor’s health endpoint reports draining with no turn in flight. The pod grace (
fleet.executor.terminationGraceSeconds, 7500 s: the CLI run budget plus five minutes) is a ceiling, never a wait. The pod leaves as soon as its containers exit.
- If your turns run long, raise
drainSecondstoward the run budget. It must stay at least 120 s under the pod grace. The Air-Gapped chart refuses to render otherwise. - Never shorten a termination with
--grace-period=0or--forceon a pod that still has turns. That is the one way to kill a turn. - Plan image rolls for quiet hours when you can. They are safe at any time, only slower under load.
- The drain needs an
agent-harnessimage that carries it. An older executor ignores the setting and exits as before.
Idle guard when the StorageClass is missing
The executor is Ready only when its volume binds on an XFS +prjquota StorageClass. So an install never waits on one that cannot bind, both charts check the live cluster:
What the chart renders, by situation
What the chart renders, by situation
helm template and dry-runs always render the configured replicas (at least one). fleet.idleGuard: "off" removes the lookup for Argo CD, Flux and policy gates, and the executor stays Pending until its class exists. Create the class (or point provisioner at your CSI driver) and the next upgrade brings it up. Meanwhile turns queue.
--wait, --atomic and the installer
helm upgrade --wait (and --atomic) waits for the executor StatefulSet:
- A running turn holds the roll. With
--wait, an upgrade during a long turn can take up to the drain window plus pod start. Give--timeoutat least the drain window (3600 s by default) or upgrade in a quiet period. - An idle executor never stalls an upgrade. With the idle guard above, a cluster without the storage prerequisites renders the executor at zero replicas and the upgrade completes.
install.sh handles both for you:
Image and registry
Image details
Image details
Network egress
Executors hold no LLM provider key. A turn’s model calls go straight to the in-cluster LLM Gateway, which is always on, with the run’s own token. The gateway is the component that egresses to the model provider.Egress paths
Egress paths
169.254.169.254) answers from the pod, so a turn can never read node credentials.
Model providers
Keys are saved in the organization (Platform > AI governance > Models) or on the install (secrets.static), and the LLM Gateway adds them to each call. Per-provider keys, Bedrock authentication and default harnesses: AI vendors.
Next steps
Size your cluster
Footprint, calculator and capacity planning.
Set up an EKS cluster
Nodes and StorageClasses.

