What Substrate does, and what it leaves to you
Substrate runs many stateful actors on few worker pods and resumes one when a request arrives, and it leaves the suspend call, the pool size, and GPU work to you.
You run one pod for each agent session. Most sessions wait for a person or for a model, and each waiting pod still holds its CPU and its memory. If you delete the pod, the session loses its memory. If you keep the pod, you pay for a process that does nothing.
When you finish this section, you can say what Agent Substrate does about that problem and name the jobs it leaves to you. You can also find the section that proves each of our eight positions.
The mechanism: actors, workers, and snapshots
Substrate separates the session from the pod that runs it.
Actor: one stateful workload with a name, such as my-counter-1. An actor has memory, files, and a root filesystem. It is a row in Postgres, and it is not a Kubernetes object.
Worker: one pod that can run actors inside a sandbox. A WorkerPool is the Kubernetes object that creates worker pods, and the pool has a fixed number of them.
Snapshot: the saved state of one actor. Substrate writes it when the actor is suspended and reads it when the actor is resumed.
Atespace: the named group that an actor, its template, and its policies belong to. An actor is always addressed by its atespace and its name.
A suspended actor uses no worker. It is a snapshot and a row. When a request arrives for it, Substrate chooses a worker with room, restores the snapshot there, and sends the request on. When something suspends the actor again, Substrate writes a new snapshot and frees the worker. Figure 1.1 shows the three worker pods of our demo pool with one actor that runs.
So a pool of three workers can hold hundreds of sessions, as long as few of them run at one time. That is the whole offer. The cost is the time to resume, and section 3.3 measures it against the memory that holds data in an actor.
What Substrate standardizes
Substrate gives you five things with fixed names.
- One API.
ate-api-serverserves theControlgRPC service for atespaces, templates, actors, snapshots, workers, and policies. Thekubectl ateplugin is its client. A second service,WorkerService, is forateletonly. - One lifecycle. An actor has seven states, and five operations move it between them: create, resume, suspend, revert, and delete. Section 3.2 draws them.
- One way to reach an actor. A client sends a request to
atenet-routerwith the headerate-target-actorand a client certificate. The router finds the worker. - Two sandbox classes. An actor runs under gVisor or in a microVM, and the WorkerPool decides which one. We ran both on one machine, and section 4.2 compares them.
- One egress rule. An actor reaches no host until an egress policy names that host. Section 6.2 shows the denial.
The project states that it keeps this API compatible from v1.0.0. That promise covers the API. It does not cover the jobs below.
What it leaves to you
The suspend call
Substrate does not suspend an idle actor. It has no idle timer. An actor that got one request stays in ACTOR_STATE_RUNNING and holds its worker until a caller sends SuspendActor.
We measured this on an x86 server guest with 24 vCPU and 47 GB, in one run. We sent five requests to my-counter-1 and then sent nothing for 900 s. We read the state every 15 s, and all 60 samples were ACTOR_STATE_RUNNING. Raw runs
The quickstart page of the project says that an idle actor is suspended for you. The API guide of the same project says that it is not. Our run agrees with the API guide. If you plan capacity from the quickstart sentence, your pool fills with idle actors.
The program that drives the actor has this job. kagent takes it: in our kagent run, 98 of 98 turns ended with a suspend. Section 7.1 shows that run. If you write your own harness, the suspend call is part of your harness.
The pool size
Substrate has no autoscaler. A WorkerPool has a spec.replicas field and a scale subresource, so a Kubernetes autoscaler can change it. Nothing in Substrate does. When every worker is full, a request for one more actor waits and then fails.
We measured that failure on the same guest, in eight requests to four actors of one run. Each request waited 5.06 s at the median (n=8) and then returned 503 with the text no worker has room for the actor. Section 4.4 explains the wait, and section 4.5 sizes a pool. Raw runs
GPU work
Substrate v1.0.0 does not give an actor a GPU. The API guide marks devices as not supported for now, so this edition has no GPU run. Keep model inference on a serving system, and let the actor call it through an egress policy.
Recovery
Substrate marks an actor as crashed, and then it waits for you. A crashed actor returns 503 on every request until a caller sends RevertActor. In one case it is not marked at all: a process that exits leaves the actor in ACTOR_STATE_RUNNING. Section 3.5 shows both cases.
We measured the exit case on the x86 server guest (24 vCPU, 47 GB, nested KVM), with two actors. Both read ACTOR_STATE_RUNNING for 20 minutes after the exit, until we called a suspend. One of them got 20 requests in that time, and all 20 returned 503. Each dead actor kept its CPU on the worker for all that time. Raw runs
Substrate also does not move an actor off a node that stops.
We measured this on a second x86 server guest (16 vCPU, 23 GB), on a cluster with two worker nodes, one run for each case. A suspended actor had its snapshot in the object store. It stayed in ACTOR_STATE_RESUMING on the stopped node for 5 min 33 s from its first request. It was crashed 5 min 44 s after the stop command. A running actor read ACTOR_STATE_RUNNING for the 10 minutes of our probe. The other node had free workers the whole time, and section 4.3 has the four cases. Raw runs
We measured 13 more cases on the same guest (16 vCPU, 23 GB), once each, to find what an operator can do. No actor left the stopped node by itself. One revert freed an actor in 7.1 s, and only for an actor that was suspended with --mode sync and had got no request. In the other cases a revert during the outage left the actor in ACTOR_STATE_REVERTING until the node was back. A new actor from a tag that we had made before the stop served a request 1.2 s after the create command. So you own the recovery from a node loss, and section 8.2 has the runbook. Raw runs
Substrate also has no opinion about the agent. It does not choose a model, run a tool, or keep a conversation. Those belong to the harness.
Our eight positions
Each position comes from a run on our own clusters. The table is the map of this manual.
We measured every number in this table on two x86 server guests, one run each unless the section says more. The section in the last column names the machine and the count. The guests are different machines, so do not set a number from one row against a number from another row. Raw runs
| Position | The number behind it | Section |
|---|---|---|
| Substrate does not suspend an idle actor | 60 of 60 samples RUNNING in 900 s | 3.3, 7.1 |
| A suspend is asynchronous, and its snapshot is not durable at once | PENDING on one node for 5 minutes | 3.4, 3.5 |
Postgres and ate-api-server are on every request path, and the Kubernetes API server is on none | 103 of 103 requests from a pod served while kube-apiserver was down for 86 s on Envoy, and 106 of 106 in 87 s on agentgateway | 1.2, 8.2 |
| Resume time grows with the memory that holds data in an actor | 0.22 s at 0 MiB, 2.16 s at 1000 MiB of random bytes, 0.23 s at 500 MiB of zero bytes | 3.3 |
| agentgateway is a dataplane option of Substrate, and it does authenticate ingress clients | 38 MB against 91 MB at idle | 6.6, 7.2 |
| An egress policy allows hosts and ports, and a removed rule does not close an open connection | a second request passed on an open pass-through connection, 20 s after the rule was removed. A suspend closed an open connection in 1.3 to 2.2 s | 6.2 for the policy, 6.6 for the open connection |
| A crashed actor is not always marked crashed | RUNNING for 20 minutes after the process exited, in 81 of 81 samples | 3.5 |
| Install from the release images | 162 s on the second guest (16 vCPU, 23 GB). The source build took 266 s on the first guest (24 vCPU, 47 GB), after a failed step. Two machines, so the seconds are indicative only. | 2.1 |
Read the positions as a list of things to check on your own cluster. Your seconds will differ. The states, the error texts, and the order of events must not.
What the project site says, and what we measured
The project site, ate.dev, reached version 1.0 with the release. We read it on 2026-11-12. Its documentation pages are built from the same files as the release tag, and we found no difference in content. Its first page makes short statements, some with numbers and none with conditions. The table sets each statement beside our result.
We measured every result in this table on x86 server guests, one run each unless the section says more. The section in the last column names the machine and the count. Raw runs
| The site says | Our result | Section |
|---|---|---|
| Idle agents suspend | Not seen. An idle actor stayed RUNNING for 900 s. The architecture page says that the caller requests the suspend. | 3.3, 9.1 |
An actor resumes in under 500ms | True for one resume, and for 10 at one time, up to about 200 MiB of data, on gVisor, from the node. 2.16 s at 1,000 MiB. 0.96 s at 200 MiB from the object store. With 50 resumes at one time on one node, the median was 1.2 to 1.3 s, and with 300 it was 7.3 to 7.4 s. | 3.3, 4.2, 4.5 |
| An actor resumes on any ready worker | Not seen for a node that stops. 5 of 5 resumes stayed on the node that held the snapshot. No actor left a stopped node by itself, in 17 cases. One revert at the right moment freed a quiet actor in 7.1 s. After a drain, 3 of 3 suspended actors did resume on the other node. | 4.3, 8.2 |
| The Kubernetes API is off the request path | Confirmed on both dataplanes. 103 of 103 requests from a pod were served while kube-apiserver was down for 86 s on Envoy, and 106 of 106 in 87 s on agentgateway. | 8.2 |
| Kubernetes handles recovery | True for nodes and pods. An actor on a stopped node needed an operator: a tag, a revert at the right moment, or the node back. After an eviction, the returned node stayed empty until we moved pods by hand. | 8.2, 3.5 |
| Memory and files survive a suspend | Confirmed with the memory fidelity. With the other two fidelities, the process starts again. | 3.4 |
| No inbound access without authentication | Confirmed for the two actor ports on both dataplanes. The stats port of the agentgateway router answered /metrics with no credentials. | 6.1 |
| No outbound access without a policy | Confirmed. A name gets NXDOMAIN, and an IP gets a reset or a 403. | 6.2 |
| The actor does not hold the secret | Confirmed for the placeholder swap. An upstream that returns its request headers gives the secret to the actor. | 6.5 |
500+/s suspend and resume activations | Not reproduced on one node. One node with 24 vCPU finished 32 to 39 resumes each second, with 50 to 300 at one time, in two runs. The control plane was not the limit, and our reading puts it in the gVisor restores on the node. A run on more nodes is planned for the next edition, so this is no result for a larger cluster yet. | 4.5 |
10× the density of container runtimes | Planned for the next edition. So far: a suspended actor used no worker and 12.9 MB of node disk, and an idle worker used 16.7 MB of memory. We compared with no container runtime. | 4.5 |
| Millions of agents | Planned for the next edition, on more than one node. So far: with 10,750 actor rows on one node, the database was 20 MB and a single resume took the same 0.36 s. | 4.5 |
| Worker state is scrubbed between runs | Planned for the next edition. The security page lists this guarantee as in progress. | no section |
| JWTs federate with every cloud provider | We decoded one token. Federation is planned for the next edition. | 5.3 |
Three statements match our runs as written. Four hold under a condition that the site does not state. Two did not match what we saw. One we did not reproduce on one node, and four are planned for the next edition. The two that did not match are the ones to plan for. You must send the suspend, and you must act when a node stops.
When this is the wrong tool
Substrate fits sessions that keep state and are idle most of the time. It does not fit an agent that runs all day, because that actor never frees its worker. It does not fit a workload that needs a GPU in the sandbox. Section 7.4 compares the choices, and section 8.5 lists the limits at v1.0.0.
Agent Substrate 1.0.0 · b34fbee · 2026-11-05 · Edition 2026.11