Layout: this gist is flat. Recreate the tree by replacing
__with/in filenames (e.g.scripts__install.sh->k8s/scripts/install.sh):mkdir -p k8s && for f in *; do [ "$f" = README.md ] && d=k8s/README.md || d="k8s/${f//__//}"; mkdir -p "$(dirname "$d")"; cp "$f" "$d"; done; chmod +x k8s/scripts/*.sh
Run your own agents (Hermes, OpenClaw, or any CLI agent) as AX Tasks on Agent Substrate on GKE: gVisor-sandboxed, suspend-to-GCS, resume on any node. Always-on services (LLM gateway, databases) stay plain Kubernetes workloads next to them.
Everything here is scripted, idempotent, and pinned by digest. install.sh
ends with verification gates. If any gate fails, the install fails.
| Layer | What runs | Durable state |
|---|---|---|
| GKE 1.36, gVisor node pool (nested virt) | created by Substrate's tools/setup-gcp |
— |
Agent Substrate v0.5.0 |
ate-system: api-server, controller, atelet, atenet router/egress, credential provider, postgres |
Postgres PVC; actor snapshots in GCS |
AX 0f085aa (the pair AX's go.mod pins) |
ax-system: ax-server, redis, WorkerPool |
Redis StatefulSet + PVC (AOF) |
| Your agents | AX Tasks from your own image | /workspace volume, snapshotted to GCS on suspend |
- A GCP project where you can create GKE clusters, GCS buckets, Artifact
Registry repos, and IAM bindings (
roles/owneron a sandbox project is simplest). - A Linux build host (not your laptop's distro Docker) with:
docker-ce+docker-buildx-pluginfrom download.docker.com. Distrodocker.iolacks buildx, and the Substrate image build fails with exit 125.- Go ≥ 1.27.1, git, make,
gettext-base(for envsubst),gcloud,kubectl,gke-gcloud-auth-plugin. - On Ubuntu 24.04 that is one apt line plus a Go tarball (see "Build host" below).
cp k8s/env.example k8s/env.local # set PROJECT_ID, CLUSTER_NAME, region
./k8s/scripts/install.sh # ~25 min first runOr run the steps one at a time. Each one is safe to re-run:
| Step | Does |
|---|---|
00-prereqs.sh |
checks tools; clones Substrate + AX at the pinned refs |
10-cluster.sh |
APIs, AR repo, cluster + bucket + Workload Identity (setup-gcp bootstrap); checks node kernel ≥ 6.8 and that the beta cert APIs are served |
20-substrate.sh |
builds Substrate images into your AR and installs ate-system |
30-images.sh |
builds AX + agent images, mirrors third-party images into AR, writes k8s/images.lock (digests) |
40-ax.sh |
deploys AX with durable Redis and narrowed RBAC |
50-verify.sh |
gates (below) |
99-teardown.sh |
deletes cluster, bucket, repo (asks first) |
- AX upstream e2e suite (8 tests) against your cluster: task lifecycle, guest exec, workspace git/files, suspend/resume persistence, atespace isolation.
- Durability: a task writes 20 MB to
/workspaceand is suspended. Then every worker pod is deleted. On resume the task lands on a new worker, restored from GCS, and the sha256 must match. - Control-plane durability: the Redis pod is deleted and ax-server restarted. Task records must survive.
- Agent images: Hermes and OpenClaw tasks start in the sandbox and run their CLI.
AX always starts /usr/local/bin/ax-task-runner as PID 1. The runner prepares
/workspace, runs spec.command, and serves health and guest-exec. So an
agent image is your base + the runner binary:
ARG RUNNER_IMAGE # AX_DEFAULT_TASK_IMAGE from images.lock
FROM ${RUNNER_IMAGE} AS runner
FROM nousresearch/hermes-agent:v0.22.5@sha256:... # or ghcr.io/openclaw/openclaw:<ver>, or your own
COPY --from=runner /usr/local/bin/ax-task-runner /usr/local/bin/ax-task-runner
ENV HERMES_HOME=/workspace/.hermes # keep agent state on the durable volume
ENV HERMES_DOCKER_EXEC_AS_ROOT=1 # hermes only: skip the s6 privilege drop (fails in gVisor)
WORKDIR /workspace
ENTRYPOINT ["/usr/local/bin/ax-task-runner"]The image needs git and a POSIX shell. Ready-made ones are in
k8s/agents/{hermes,openclaw,custom}. Add yours under k8s/agents/<name>/
and build it with AGENTS="hermes openclaw <name>" ./k8s/scripts/30-images.sh.
Run it:
# task.yaml
apiVersion: ax.io/v1alpha1
kind: Workspace
metadata: {name: my-ws, atespace: default}
spec:
git: [{repo: https://github.com/you/your-repo.git}]
---
apiVersion: ax.io/v1alpha1
kind: Task
metadata: {name: my-agent, atespace: default}
spec:
image: "<AGENT_..._IMAGE from images.lock>"
command: ["hermes", "-z", "summarize README.md"]
workspace: {name: my-ws, path: /workspace}
debug: true # enables `ax ssh`; turn off for unattended tasksax apply -f task.yaml
ax resume task my-agent
ax ssh my-agent -- ls /workspace
ax suspend task my-agent # snapshot /workspace to GCS, free the worker
ax resume task my-agent # restore on any worker- Never put secrets in
spec.env. They are stored in AX's Redis and baked into the per-task ActorTemplate. - Preferred: Substrate egress credential injection. The agent sends a
placeholder
Authorizationheader. The egress gateway swaps in the real value from a Kubernetes Secret (or Secret Manager). The secret never enters the sandbox or a snapshot. See Substratedocs/egress-credential-injection.md. Grant each atespace namespace access in thek8s-credential-provider-namespace-policyConfigMap (default-deny). - Simplest: an in-cluster LLM gateway (e.g. LiteLLM as a Deployment) holding the provider keys. Agents get only a scoped gateway key.
| Limit | Impact | What we do |
|---|---|---|
| AX tasks snapshot with VOLUMES fidelity | /workspace survives suspend; processes and /tmp do not |
keep agent state under /workspace; agents must cold-start cleanly |
| AX hardcodes the gVisor sandbox class | no microVM via AX | fine for CLI agents |
| AX's default snapshot bucket points at someone else's bucket | snapshots fail or leak if unset | AX_SNAPSHOTS_BUCKET is always set by 40-ax.sh |
| AX's e2e Redis is an ephemeral Deployment | lose the pod, lose all task records | replaced with StatefulSet + PVC + AOF |
| AX's ClusterRole reads all Secrets | over-broad | narrowed to get on gemini-api-secret |
| A freshly started worker needs ~30 s before it accepts actors | resume right after a worker restart returns ResourceExhausted: no worker has room |
keep ≥ 1 spare worker; retry resume (scripts do) |
Upstream Hermes image's hermes shim drops root via s6-setuidgid |
gVisor forbids setgroups: s6-applyuidgid: unable to set supplementary group list |
k8s/agents/hermes sets HERMES_DOCKER_EXEC_AS_ROOT=1 (no s6 tree under AX; the sandbox is the boundary) |
| Node kernel must be ≥ 6.8 (undocumented) | on 6.1, every resume fails: creating overlay superblock: invalid argument |
10-cluster.sh checks it |
| GKE 1.36 beta cert APIs are create-time only | install hangs at "Waiting for ClusterTrustBundles" | let setup-gcp create the cluster |
Docker's containerd image store reports the upstream index digest in RepoDigests for a re-tagged multi-arch image, but docker push uploads only the local platform |
pinning that digest -> ImagePullBackOff: not found |
digests are read back from the registry (imagetools inspect); third-party images are mirrored registry-to-registry (imagetools create, digest identical to upstream) |
| Neither project publishes images | — | build into your AR, pin digests (images.lock) |
sudo install -m0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin \
git make gettext-base jq build-essential google-cloud-cli google-cloud-cli-gke-gcloud-auth-plugin kubectl
curl -fsSL https://go.dev/dl/go1.27.1.linux-amd64.tar.gz | sudo tar -C /usr/local -xz && export PATH=/usr/local/go/bin:$PATH