ActorTemplate
ActorTemplate declares the shape of an actor: which container image(s),
which entrypoint, which sandbox runtime, which volumes, and where snapshots
go. From a single template you create many actors (instances).
The spec
apiVersion: ate.dev/v1alpha1kind: ActorTemplatemetadata: name: my-agentspec: pauseImage: gcr.io/gke-release/pause@sha256:... # required, must be @-pinned sandboxClass: gvisor # gvisor (default) | microvm containers: - name: app # required, DNS-1123 label image: ghcr.io/.../my-agent@sha256:... # required, must be @-pinned command: [/bin/my-agent] env: - name: LOG_LEVEL value: info # literal value - name: OPENAI_API_KEY valueFrom: secretKeyRef: { name: my-secret, key: OPENAI_API_KEY } readyz: # optional HTTP readiness probe httpGet: { path: /readyz, port: 80 } volumeMounts: - { name: data, mountPath: /var/data } volumes: - name: data durableDir: {} # persists across resume; snapshot-able snapshotsConfig: location: gs://my-bucket/some/prefix # required onPause: Full # Full (default) | Data onCommit: Full # must be a subset of onPause workerSelector: # optional placement gate matchLabels: { tier: gpu }status: phase: Ready goldenSnapshot: gs://my-bucket/some/prefix/<id>/<ts-rand>/spec.pauseImage and spec.snapshotsConfig are required; each
containers[].name and containers[].image are required too. Both
containers[].image and pauseImage must contain @ (digest-pinning) -
changing an image invalidates existing snapshots.
What changed
The old spec.workerPoolRef and spec.runsc fields are gone:
- Placement is now selector-based.
spec.workerSelectoris aLabelSelectorthat gates which worker pools this template’s actors may use. It is AND’d with the actor’s ownworker_selector(which can only narrow the set further, never widen it) and withspec.sandboxClass. IfworkerSelectoris nil, all pools are eligible. Templates no longer name a pool - see WorkerPool for the decoupling. - Sandbox binaries moved out of the template. Instead of an inline
runscblock,spec.sandboxClassselects a runtime family (gvisor, the default, ormicrovm), and the concrete binaries are resolved by the worker pool via a SandboxConfig.
Containers
Each containers[] entry supports:
command[]/args[]- override the image entrypoint, mirroring Kubernetes semantics:commandoverrides the imageENTRYPOINTandargsoverrides itsCMD. Ifcommandis set, both the imageENTRYPOINTandCMDare ignored and the process argv iscommand + args. Unlike Kubernetes,$(VAR)references are not expanded.env[]- either a literalvalueor avalueFrom.secretKeyRef(a key from aSecretin the template’s namespace). Exactly one of the two must be set. Literal values are not$(VAR)-interpolated.readyz- an optional HTTP readiness probe (httpGet.path, default/readyz, andhttpGet.port). When set, the actor is not considered ready - andRun/RestoreRPCs do not return success - until the endpoint returns 200.volumeMounts[]- mount aspec.volumes[]entry by name at a clean absolute path.
Volumes
spec.volumes[] currently supports one source type, durableDir (a
DurableDirVolumeSource): a directory on rootfs that persists across resumes
and participates in snapshots. At most one DurableDir volume is allowed per
template, and a container may mount at most one. DurableDir volumes are
not supported when sandboxClass is microvm.
Snapshots
spec.snapshotsConfig has three fields:
location(required) - the object-store prefix snapshots are written to.onPause(SnapshotScope, defaultFull) - what a Pause keeps on the node.onCommit(SnapshotScope, defaultFull) - what a Suspend uploads to snapshot storage.onCommitmust be a subset ofonPause.
SnapshotScope is Full (process memory plus the rootfs delta, including
DurableDir volumes) or Data (only DurableDir volumes; rootfs cold-boots
on resume).
A real example: hello-substrate
Here’s a live ActorTemplate from the kagent namespace - created by
kagent’s SandboxAgent controller when you declare a tiny “hello world”
declarative agent. It’s the minimum-viable end-to-end shape:
apiVersion: ate.dev/v1alpha1kind: ActorTemplatemetadata: name: hello-substrate namespace: kagent labels: app.kubernetes.io/managed-by: kagent kagent.dev/sandbox-agent: hello-substrate ownerReferences: - apiVersion: kagent.dev/v1alpha2 kind: SandboxAgent name: hello-substrate controller: truespec: sandboxClass: gvisor pauseImage: gcr.io/gke-release/pause@sha256:bcbd57ba... containers: - name: kagent image: localhost:5001/kagent-dev/kagent/golang-adk@sha256:1fbfae31... command: [/app, --host, 0.0.0.0, --port, "80"] env: - name: KAGENT_CONFIG_JSON valueFrom: { secretKeyRef: { name: hello-substrate, key: config.json } } - name: KAGENT_AGENT_CARD_JSON valueFrom: { secretKeyRef: { name: hello-substrate, key: agent-card.json } } - name: OPENAI_API_KEY valueFrom: { secretKeyRef: { name: kagent-openai, key: OPENAI_API_KEY } } - name: KAGENT_NAME value: hello-substrate - name: KAGENT_URL value: http://kagent-controller.kagent:8083 snapshotsConfig: location: gs://ate-snapshots/kagent/hello-substratestatus: phase: Ready conditions: - type: Ready status: "True" reason: Ready message: Actor template is ready for use goldenActorID: 75d4bbdf-5ce4-481c-8e7c-0c79cb87f334 goldenSnapshot: gs://ate-snapshots/kagent/hello-substrate/75d4bbdf-.../2026-06-09T03:24:14Z-BBBH4RYLIHT3XDAFMKMABLHIUS takeGoldenSnapshotAt: "2026-06-09T03:24:14Z"A few things worth pointing out:
- Owner reference up to a
SandboxAgent. A user didn’t write this YAML by hand - they declared akagent.dev/v1alpha2 SandboxAgentnamedhello-substrate, and kagent’s controller projected it down into thisActorTemplate(plus aSecretholding the agent’s config). Delete the SandboxAgent and the ActorTemplate is garbage-collected. - No
workerPoolRef. This template omitsworkerSelector, so its actors are eligible for anygvisorworker pool in the cluster - pool choice is selector-based, not a hard-coded reference. See WorkerPool. containers[0]is the agent binary.localhost:5001/...golang-adkis kagent’s Go ADK runtime, pulled from the in-cluster registry. The digest pin is what makes the golden snapshot below valid - bump the image and you invalidate the snapshot.envis mostly secret refs.config.jsonandagent-card.jsoncome from aSecretthat kagent also manages.OPENAI_API_KEYcomes from a sharedkagent-openaiSecret viavalueFrom.secretKeyRef. None of this is baked into the image; the container reads it at startup.snapshotsConfig.locationis per-template. All actors of this template - and the golden snapshot itself - live undergs://ate-snapshots/kagent/hello-substrate/.status.goldenSnapshotis set. That’s the artifact the bootstrap below produced. New actors of this template start from it instead of cold-booting the container.
What atecontroller does with it
Runs the golden snapshot bootstrap described in detail at
Golden snapshot. The golden boot happens in the
reserved ate-golden atespace (see the Atespace page):
stateDiagram-v2 [*] --> Initial Initial --> ResumeGoldenActor: CreateActor() ResumeGoldenActor --> WaitGoldenActor: ResumeActor()<br/>(stamp TakeGoldenSnapshotAt) WaitGoldenActor --> Ready: requeue fires, SuspendActor() Ready --> [*]
A failed phase is declared in the CRD type but the reconciler never assigns
it - errors return up and trigger a requeue. The “wait” is not a blocking
sleep: the reconciler stamps status.takeGoldenSnapshotAt and comes back
later. The default warmup is 20s, but templates whose containers all declare a
readyz probe skip that wait - ResumeActor only returns once readyz
reports 200.
The result: status.goldenSnapshot points at a fresh, just-booted
snapshot of the template’s image. Future actors of this template can
restore from it in milliseconds instead of cold-booting.
What the golden snapshot is for
In the resume workflow, when an actor has no snapshot of its own (because
it’s never run yet), snapshot selection falls back to the template’s
golden snapshot (unless boot is requested). So every brand-new actor of this
template starts from the same pre-initialized state, not from cold boot.
Relationship to other concepts
| Concept | Cardinality |
|---|---|
| ActorTemplate → WorkerPool | many-to-many, selector-gated (no pinned reference) |
| ActorTemplate → Actor | one-to-many (template is the “class”) |
| ActorTemplate → GoldenSnapshot | one-to-one |
Related
- Actor - the per-instance entity.
- WorkerPool - selected by
sandboxClass+workerSelector, not a hard reference. - SandboxConfig - where the sandbox binaries come from.
- Snapshot - golden vs. latest, Full vs. Data.
- Golden snapshot bootstrap - the reconciler’s flow.
- atecontroller - the reconciler.