A Kubernetes Pod is a useful unit of deployment. An agent session has a different lifetime: it may run briefly, wait for minutes, then need its process and filesystem state back for the next turn. Keeping a dedicated environment resident through every wait can consume capacity that other sessions could use.

Agent Substrate separates those lifetimes. Kubernetes runs a pool of worker Pods; Substrate places sandboxed agent environments onto them, persists their state, and restores them when work arrives. This article follows one request through that system, then examines the components and a Counter demo you can try locally.

TL;DR

  • Kubernetes manages Workers; Substrate manages Actors. Actor state and assignments live behind a separate API backed by PostgreSQL.
  • Workers can serve many Actors over time. Capacity-based concurrency is combined with suspend/resume to reuse compute.
  • atelet and ateom save and restore sandbox state. They use gVisor or microVMs; the snapshot fidelity determines what is preserved.
  • Credentials can stay outside the Actor. An egress gateway and credential provider can inject API credentials into outgoing requests.
  • Check the implemented behavior, not just the design. Authorization enforcement is experimental and disabled by default for ordinary Control RPCs. Idle detection and performance claims also require context.

Implementation details target Agent Substrate v0.5.0. See Components and versions for the full scope.

This walkthrough assumes familiarity with Pods, Deployments and basic Kubernetes networking. Start with the request lifecycle, jump to the kind demo, or use the evaluation questions to assess the fit for your platform.

The design decision: keep the workers, move the sessions

An agent may execute code briefly, wait for inference or an external tool, then wait indefinitely for another user turn. Running untrusted code also creates a need for isolated environments, often with one tenant per sandbox.

The combination matters: a large fleet can contain many more waiting environments than actively executing ones. Reserving CPU and memory for every waiting agent wastes capacity. Snapshot-based multiplexing is intended to let those environments retain state without remaining assigned to running compute.

Source: docs/architecture.md#L17-L23.

What Kubernetes still does—and what Substrate moves elsewhere

The architecture document identifies three pressures behind its design:

Pressure Why it matters for agents
API resource count and write traffic Millions of frequently changing agent records are a different workload from ordinary Kubernetes reconciliation
Activation latency Pod scheduling, reconciliation, network hops and image preparation add overhead to very short executions
State movement Rapidly saving and restoring many environments is a different problem from attaching conventional persistent volumes

These are the project's design judgments for its intended scale, rather than universal Kubernetes limits or a claim that every Pod takes seconds to start.

Kubernetes still handles infrastructure provisioning and worker Pod lifecycles. Substrate introduces a separate control plane for Actor state, assignment and resumption, so an activation does not need to wait for a fresh Pod lifecycle.

Sources: docs/architecture.md#L153, docs/architecture.md#L160-L172.

The important boundary is the activation path: a new turn can restore an Actor onto an existing Worker without provisioning another Pod. Database write throughput, reconciliation and environment preparation all contribute to that choice.

Follow a request into a suspended Actor

The execution layer combines routing, state persistence and sandbox isolation:

Agent Substrate adds a gateway, state persistence and kernel isolation above Kubernetes, compute and storage.

Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).

Two lifecycles, two APIs

Resource Meaning Managed through
ActorTemplate Immutable workload definition, including images and snapshot settings Substrate API and database
WorkerPool Defines a pool that atecontroller reconciles into a Deployment Kubernetes CRD
Atespace Logical grouping and identity scope for Actors; not a Kubernetes namespace Substrate API and database
Actor An instance of an ActorTemplate and the unit of suspend/resume Substrate API and database
Worker A record corresponding to a worker Pod; can host multiple Actors concurrently Substrate API and database

Sources: docs/glossary.md#L10-L23, docs/glossary.md#L38-L52.

An Actor is the stateful environment that belongs to a session. A Worker is the reusable execution capacity. Suspending an Actor releases its assignment; it does not require destroying and recreating the worker Pod. Worker capacity can also permit several Actors to run concurrently.

Restore first, forward second

For an HTTP request targeting a suspended Actor, the activation path has four steps:

  1. The router identifies the Actor from ate-target-actor.
  2. The control plane assigns a Worker and coordinates restoration of the Actor's snapshot.
  3. Once the assignment is ready, the router forwards the request through the Worker's tunnel to the Actor.
  4. The Actor handles the request and returns its response.

For an Actor that is already running, ResumeActor returns the existing assignment. The call is idempotent.

Source: workflow_resume.go.

Returning a response does not automatically suspend the Actor. The application or higher-level system must choose a safe suspension boundary, taking account of outstanding work and network calls.

Suspend and resume

Active agent environments on Workers exchange state with idle environments persisted in storage through suspend/resume.

Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).

Here, “idle” means state has been persisted and the environment removed from running compute, rather than a process simply sleeping. The illustration is conceptual: it does not imply a fixed one-to-one relationship between Workers and Actors.

When a request arrives, atenet-router asks the control plane to resume the target Actor and resolve its Worker assignment. A suspended Actor is restored from its latest snapshot before traffic is forwarded. It may resume on a different Worker.

The router itself does not decide whether the Actor is running; that decision belongs to the control plane. Suspending the Actor saves state and releases its assignment for reuse.

Source: docs/architecture.md#L193-L204.

What runs where

Component Responsibility Relationship to Kubernetes
ate-api-server (ateapi) Actor lifecycle, scheduling and snapshot coordination Separate API, scheduler and workflow layer backed by PostgreSQL
atecontroller Reconciles WorkerPool and related CRDs Standard controller-runtime controller
atelet / ateom Coordinates and executes sandbox checkpoint/restore Additional execution layer inside worker Pod boundaries
atenet router and egress Resumption-aware ingress and controlled outbound traffic Routing plus Actor lifecycle coordination
podcertcontroller Issues short-lived certificates for component mTLS Signer controller for PodCertificateRequest

The threat-model diagram shows how these components connect:

Substrate architecture showing ate-api-server, atelet, ateom, atenet-router, object storage and PostgreSQL.

Source: docs/threat-model.md#L30-L40.

Repository map: controllers, sidecars and tools

The repository also includes supporting programs:

Directory Role
cmd/ateapi gRPC control plane
cmd/atecontroller Kubernetes controllers
cmd/atelet Node supervisor deployed as a DaemonSet
cmd/ateom-gvisor Drives gVisor inside worker Pods
cmd/ateom-microvm Runs Actors with Cloud Hypervisor microVMs
cmd/objectstore-plugin Snapshot transfer sidecar for node and control-plane operations
cmd/atenet Networking daemon and proxy integration
cmd/dataplane/envoy Envoy image with Rust Dynamic Modules for egress enforcement
cmd/credential-provider/kubernetes-secrets Reference credential provider backed by Kubernetes Secrets
cmd/podcertcontroller Pod certificate signers
cmd/kubectl-ate Actor and other Substrate resource operations
cmd/ate-setup Installation and removal
cmd/benchmarking Synthetic resource-consuming test workloads
tools/setup-gcp GKE, storage and IAM provisioning

The README's tour is not an exhaustive inventory; the sidecar, provider, dataplane and setup entries are supplemented from their own source and documentation.

Sources: README.md#L260-L272, cmd/objectstore-plugin/main.go#L15-L19, cmd/ate-setup/README.md#L3, cmd/dataplane/envoy/README.md.


Resource ownership and assignment

Agent Substrate between the agent application layer and Kubernetes, with sandbox isolation and storage beneath it.

Source: Bringing you Agent Sandbox on GKE and Agent Substrate, Google Cloud Blog (May 20, 2026).

The green sandbox-isolation block is the foundation for the checkpoint and restore operations. Agent Substrate incorporates Agent Sandbox's secure runtime and snapshot capabilities into a separate execution layer.

classDiagram namespace kube-apiserver { class WorkerPool { <<CRD>> } class Deployment class WorkerPod { ateom runsc } } namespace ate-api-server { class ActorTemplate { <<record>> } class Actor { <<record>> status snapshotRefs } class Worker { <<record>> actorAssignments podIP } } ActorTemplate ..> WorkerPool : worker_selector (labels) WorkerPool ..> Deployment : reconciled by atecontroller Deployment "1" *-- "*" WorkerPod : manages Actor "*" --> "1" ActorTemplate : actor_template Actor "0..*" --> "0..1" Worker : runs on Worker "1" --> "1" WorkerPod : maps to

Adapted from docs/architecture.md#L268-L303; the multiplicity reflects the implementation's support for multiple Actors per Worker.

The split between API servers is central. Infrastructure resources stay in Kubernetes, while frequent Actor/Worker assignment changes go through ate-api-server and its state store.

Two resource layers

The architecture gives three reasons for this split:

  • Keep high-frequency Actor updates away from the Kubernetes control plane.
  • Use low-latency lookups and atomic assignments for resumption. The document's 100ms figure is a design target, not a result measured here.
  • Retain Kubernetes RBAC, auditing and policies for infrastructure resources such as WorkerPools and SandboxConfigs.

One detail in that document is stale: it says authorization has not been implemented. The same v0.5.0 tag contains OpenFGA-based authorization. --experimental-enable-authz defaults to false and controls enforcement for ordinary Control RPCs. AccessPolicy RPCs are checked regardless of that flag.

Sources: docs/architecture.md#L247-L261, cmd/ateapi/main.go#L84-L88, cmd/ateapi/internal/authz/interceptor.go#L28-L60.

ate-api-server: the Actor control plane

ateapi owns Actor lifecycles, schedules them onto Workers and coordinates snapshots. Its gRPC server connects to PostgreSQL, creates Kubernetes clients and configures its mTLS credentials.

Sources: docs/glossary.md#L56-L58, cmd/ateapi/main.go#L509-L521, cmd/ateapi/main.go#L560-L586.

templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv, *templateResyncInterval) templateReconciler.Start(shutdownCtx) workerAssignmentReconciler := controlapi.NewWorkerAssignmentReconciler(persistence, workerCache) workerAssignmentReconciler.Start(shutdownCtx) 

Source: cmd/ateapi/main.go#L326-L330.

Despite their names, these reconcilers are internal Substrate loops rather than controller-runtime reconcilers watching custom resources. ActorTemplateReconciler prepares golden snapshots; WorkerAssignmentReconciler handles Actors lost when ateom restarts.

This component does not map one-to-one to kube-apiserver: it combines Actor-specific API handling, scheduling and workflows.

atecontroller: conventional Kubernetes reconciliation

atecontroller uses ctrl.NewManager and registers four reconcilers, including WorkerPoolReconciler. Its core structure is familiar:

func (r *WorkerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) wp := &atev1alpha1.WorkerPool{} if err := r.Get(ctx, req.NamespacedName, wp); err != nil { if k8errors.IsNotFound(err) { return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("failed to get worker pool %q: %w", req.NamespacedName, err) } if !wp.GetDeletionTimestamp().IsZero() { log.Info("WorkerPool is being deleted") return ctrl.Result{}, nil } if err := r.reconcileWorkerPool(ctx, wp); err != nil { log.Error(err, "Failed to reconcile worker pool") return ctrl.Result{}, err } return ctrl.Result{}, nil } 

Sources: cmd/atecontroller/main.go#L233-L276, cmd/atecontroller/internal/controllers/workerpool_controller.go#L105-L129.

Reconciliation does more than produce a Deployment. The WorkerPool reconciler protects referenced SandboxConfigs with finalizers; a separate SandboxConfigProtectionReconciler releases protection. Updating the recorded SandboxConfig uses two status writes so that status never references a configuration whose protection has already been released.

Owns(&Deployment{}) and Watches(&SandboxConfig{}, ...) connect these resources through normal controller-runtime mechanisms.

Sources: cmd/atecontroller/internal/controllers/workerpool_controller.go#L134-L136, cmd/atecontroller/internal/controllers/workerpool_controller.go#L416-L425.

atelet and ateom: checkpoint and restore

atelet runs on each node as a DaemonSet. ateom runs inside worker Pods and exposes operations such as RunWorkload, CheckpointWorkload and RestoreWorkload.

atelet coordinates worker supervision, image caching and CSI volumes. It also reuses kubelet's image credential-provider mechanism through --image-credential-provider-config.

Sources: docs/architecture.md#L324-L326, cmd/atelet/main.go#L103-L104.

Snapshot upload and download are delegated to objectstore-plugin sidecars over a Unix-socket gRPC API:

Plugin role Operations
NodeProvider, alongside atelet Upload and fetch snapshot files
ControlProvider, alongside ate-api-server Copy and delete whole snapshots

The main processes do not directly transfer snapshots to object storage. This does not mean they hold no credentials at all: mTLS and database authentication remain separate concerns.

Source: docs/architecture.md#L330.

For gVisor, cmd/ateom-gvisor/runsc.go invokes runsc checkpoint and runsc restore. The microVM implementation uses Kata integration and Cloud Hypervisor, with userfaultfd demand paging when restoring memory.

Sources: docs/architecture.md#L328-L338.

Kubelet and the container runtime still run the worker Pod itself. Substrate manages sandboxed Actors inside it. Kubernetes already has a Kubelet Checkpoint API; Substrate's distinction is coordinating persistence, assignment, restore and reconnection as an Actor lifecycle.

atenet: routing that can resume its destination

Ingress requests identify an Actor with the ate-target-actor header, formatted as <atespace>/<actor>. In the Envoy configuration described here, an ext_proc processor calls the control plane's ResumeActor RPC before resolving the destination.

Once the assignment is available, the router opens an mTLS CONNECT tunnel to the Worker's atunnel listener on port 8443. atunnel, hosted by ateom, forwards traffic over the Actor's private veth interface. Worker Pod port 80 is not a direct Actor ingress endpoint.

Sources: docs/architecture.md#L344-L354.

The Kubernetes analogy is Service/Ingress/Gateway routing with an extra orchestration step: restore the destination if necessary before forwarding to it.

podcertcontroller: Pod certificate signers

podcertcontroller provides three signers:

Signer Purpose
servicedns.podcert.ate.dev/identity Service DNS certificates backed by a local CA
podidentity.podcert.ate.dev/identity Pod identity certificates equivalent to Kubernetes service account identity
postgres.podcert.ate.dev/identity PostgreSQL login certificates for ate-api-server

Source: cmd/podcertcontroller/main.go#L15-L22.

It watches certificates.k8s.io/v1 PodCertificateRequest objects. The API became GA in Kubernetes v1.37:

PodCertificateRequest: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.37"), Default: true, PreRelease: featuregate.GA}, // LockToDefault in 1.38 }, 

Source: kube_features.go.

The API lets kubelet request certificates and project them into Pods. A signer must still process those requests. The well-known kubernetes.io/kube-apiserver-client-pod signer described in the API comments is unimplemented in this version.

Substrate fills its own needs with the three signers above. Its README calls the controller a “polyfill” for the missing signers.

Sources: certificate API types, README.md#L268-L269.

Keep session state and API credentials separate

Isolation, identity and credentials are separate controls

The agent environment combines identity-based ingress and egress, secrets management, kernel isolation and rapid suspend/resume.

Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).

Kernel isolation does not by itself protect external API credentials. That also requires the gateway and credential-provider configuration described below.

The security document distinguishes Met, In Progress, Future and Out of Scope. Selected principles in v0.5.0 are:

Principle Meaning Documented status
Always Sandboxed Run Actors in gVisor or Cloud Hypervisor microVMs rather than relying on ordinary Linux containers alone Met
Zero Ambient Credentials Do not implicitly give Actors infrastructure permissions or external API keys Met; distinct from demos that explicitly pass keys in environment variables
No Access by Default Restrict Actor access to the control plane, separate from required infrastructure traffic Loopback/private netns: Met; DNS filtering: In Progress
Fail-Closed Policy-Enforced Egress Gateway Control outbound traffic through an egress proxy Gateway: Met; fail-closed behavior: In Progress, with UDP passthrough noted

Sources: docs/security.md#L30-L33, docs/security.md#L41, docs/security.md#L59, docs/security.md#L75-L77.

Warning: The same document lists Atespace authorization as In Progress, and node blast-radius containment and storage encryption as Future. The security architecture is not evidence that every control is fully implemented. Ordinary Control RPC authorization still requires the experimental flag.

Inject credentials outside the Actor

With egress credential injection, an Actor sends an HTTPS request containing a placeholder header. The gateway obtains the real credential and substitutes it outside the sandbox:

  1. The Actor sends the request with a placeholder value.
  2. The egress gateway terminates TLS and matches an EgressPolicy header-replacement rule.
  3. The credential provider authorizes access and reads the secret.
  4. The gateway replaces the header and makes a separate TLS connection to the destination.

The Actor must trust the gateway's CA. If the request does not contain the header to replace, credential lookup and replacement do not occur.

This keeps the API key outside Actor memory and snapshots. It does not prevent misuse of an allowed API; destination restrictions and application authorization still matter.

Sources: docs/egress-credential-injection.md#L1-L15, docs/egress-credential-injection.md#L41-L54.

The reference kubernetes-secrets provider starts with an empty deny-all policy. Readable namespaces must be explicitly allowed for each Atespace. Actors do not read Kubernetes Secrets themselves.

Source: README.md#L132-L136.

EgressPolicy defines destinations and header replacement

An EgressPolicy specifies allowed traffic and header replacement rules. The credential provider separately decides whether the Actor can use the referenced secret. The API defines the policy as an unordered set of allows: traffic matching no rule is denied.

Source: pkg/proto/ateapipb/ateapi.proto#L609-L610.

For example, allow HTTPS to api.example.com and replace its Authorization header:

rules: - https: hostnames: ["api.example.com"] effects: replaceHeaders: - header: Authorization prefix: "Bearer " credentialUri: ate-secret://k8s.io/default/ns1/example-api/token 

Source: docs/egress-credential-injection.md#L69-L76.

The policy is associated with an Actor rather than embedded in its ActorTemplate:

# Create an egress policy. kubectl ate create egress-policy <actor-name> -a <atespace> -f policy.yaml 

Source: cmd/kubectl-ate/README.md#L247-L253.

Note: The Claude Code demo discussed later directly places ANTHROPIC_API_KEY in the ActorTemplate environment. That differs from gateway injection. In either case, the Actor needs an egress policy permitting its HTTPS destination, such as api.anthropic.com.

The complete request path

With routing and credential injection in place, an HTTP-triggered agent can resume and call an external LLM as follows. This combines the documented flows; Counter itself does not call an LLM.

sequenceDiagram actor User as User / higher-level system participant Router as atenet-router participant API as ate-api-server participant Atelet as atelet participant Tunnel as ateom / atunnel participant Agent as Actor participant Egress as atenet-egress participant Cred as credential provider participant LLM as External LLM API User->>Router: HTTP request (ate-target-actor) Router->>API: ResumeActor alt Actor is suspended API->>Atelet: Restore request Atelet->>Tunnel: RestoreWorkload Tunnel-->>Atelet: Restore complete Atelet-->>API: Complete else Actor is running Note over API: Return existing assignment end API-->>Router: Worker assignment Router->>Tunnel: Forward through mTLS CONNECT Tunnel->>Agent: Forward request Agent->>Egress: HTTPS (placeholder header) Egress->>Cred: Request credential Cred-->>Egress: Return API key after authorization Egress->>LLM: Replace header and send over a separate TLS connection LLM-->>Egress: Response Egress-->>Agent: Response Agent-->>Tunnel: Result Tunnel-->>Router: Response Router-->>User: Response Note over User,API: A higher-level system can request SuspendActor to save state and release the assignment

Sources: Resume sequence, credential injection. Cold boot without a snapshot and other branches are omitted.

The Actor-to-gateway and gateway-to-provider connections use separate TLS sessions. The gateway must terminate the first to replace headers in the second.

Try the Counter demo with kind

The Counter workload makes state preservation visible: each request increments counters in process memory, a file and the root filesystem. The example below uses gVisor and includes recorded output from a local kind run.

Set up the cluster and demo

Building v0.5.0 requires Go 1.27.0 and a working Docker-based development environment. The microVM option additionally requires /dev/kvm and suitable virtualization support.

Sources: go.mod#L1-L5, hack/create-kind-cluster.sh#L74-L87.

Use a dedicated checkout and cluster name. The creation script deletes and recreates an existing cluster with the same name, so choose one that does not overlap with an environment you want to keep.

git clone --branch v0.5.0 --depth 1 https://github.com/agent-substrate/substrate.git substrate-v0.5.0 cd substrate-v0.5.0 export KIND_CLUSTER_NAME=ate-overview # create cluster and local registry (IPv4; IP_FAMILY=dual|ipv6 overrides) hack/create-kind-cluster.sh # install ate, PostgreSQL, rustfs hack/install-ate-kind.sh --deploy-ate-system --credential-provider='{"name":"k8s.io"}' # install counter demo hack/install-ate-kind.sh --deploy-demo-counter # install kubectl-ate go install ./cmd/kubectl-ate # create a counter actor in the demo's atespace (--template names the # actor template, resolved in the actor's atespace) kubectl ate create actor my-counter-1 -a ate-demo-counter --template counter # port-forward the network router to bind to local port `8000` kubectl port-forward -n ate-system svc/atenet-router 8000:80 

Source: README.md#L111-L129.

The creation script sets up a local registry and a kind cluster with kindest/node:v1.37.0. It explicitly enables certificate-related feature gates, including ClusterTrustBundleProjection; some comments still describe v1.36 requirements.

Source: hack/create-kind-cluster.sh#L104-L113.

The system installation uses ko to build and push its Go components and deploys PostgreSQL, RustFS, CRDs and SandboxConfig resources. The Counter installation adds its WorkerPool and ActorTemplate and waits for a golden snapshot.

WorkerPool and ActorTemplate

The demo's WorkerPool includes the following settings. The installer resolves template placeholders and ko:// references before applying the configuration.

apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: counter namespace: ate-demo-counter labels: workload: counter spec: replicas: 3 workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor sandboxClasses: - name: gvisor template: resources: limits: cpu: "1" memory: 1Gi requests: cpu: 250m memory: 1Gi 

Source: demos/counter/counter.yaml.tmpl#L26-L63.

An ActorTemplate is not a Kubernetes object. This YAML maps to Substrate's protobuf fields and is passed through kubectl ate create actor-template:

metadata: atespace: ate-demo-counter name: counter workerSelector: matchLabels: workload: counter containers: - name: counter image: ko://github.com/agent-substrate/substrate/demos/counter wakeupProbe: httpGet: Source:https://dev.to/yosshi_/an-overview-of-agent-substrate-501d

Related posts