3.6

Atespaces and names

An atespace groups actors, templates, and tags in the Substrate API, and it is not a Kubernetes namespace, although the two often share a name.

You ran kubectl get ns to find where your actors live. You found a namespace with the name of your demo, with worker pods in it and no actor. Then you created a second atespace for a new team, and its template still ran on the pool of the first team. Both results follow from one fact: Substrate keeps two lists of names, and it connects them with labels only.

When you finish this section, you can create an atespace and name an actor and a template in it. You can also select a worker pool from a template, and find a snapshot in the object store from its URI.

An atespace is not a namespace

Atespace: the group that an actor, an ActorTemplate, and a tag belong to in the Substrate API. An atespace is a row in Postgres. It must exist before you create anything in it, and you can delete it only when it is empty.

ObjectWhere it livesHow you list it
AtespaceSubstrate API, globalkubectl ate get atespaces
ActorSubstrate API, in one atespacekubectl ate get actors -a <atespace>
ActorTemplateSubstrate API, in one atespacekubectl ate get actor-templates -a <atespace>
TagSubstrate API, in one atespacekubectl ate get tags -a <atespace>
Access policySubstrate API, one for each atespacesection 5.2
WorkerPool, worker podsKubernetes, in one namespacekubectl get workerpools -n <namespace>
Worker recordSubstrate API, globalkubectl ate get workers

Our test setup made both kinds of object with one name. That is our choice, and Substrate did not ask for it.

bashone namespace, one WorkerPool, and one atespace, all named by usFull log
kind: Namespace
metadata:
  name: lab-c
…
kind: WorkerPool
metadata:
  name: ballast
  namespace: lab-c
  labels:
    workload: ballast
…
kubectl apply -f pool.yaml
kubectl ate create atespace lab-c 2>&1 | tail -1
Lines from the script that prepared the resume tests. The pool spec and the lines between the three parts are cut.

kubectl apply wrote the namespace and the pool to Kubernetes. kubectl ate create atespace wrote one row through ate-api-server. The quickstart demo does the same with the name ate-demo-counter, so a new user sees one name and assumes one object.

Fig. 3.11an atespace against a Kubernetes namespacetree
An atespace and a Kubernetes namespace hold different objects Two trees side by side. On the left, in the Substrate API and stored in Postgres, the atespace lab-c holds the ActorTemplate ballast-200, the actor b200-1 and the golden tag of the template. On the right, in Kubernetes, the namespace lab-c holds the WorkerPool ballast with the label workload: ballast and its two worker pods. A dotted line joins the workerSelector of the template to the label of the pool, because pool selection uses labels and does not check the namespace. SUBSTRATE API, ROWS IN POSTGRES KUBERNETES, OBJECTS IN ETCD Atespace lab-c kubectl ate create atespace lab-c ActorTemplate ballast-200 workerSelector: matchLabels workload: ballast Actor b200-1 addressed as lab-c/b200-1 Tag b9f97b82 golden tag, named by the template uid Namespace lab-c kubectl create namespace lab-c WorkerPool ballast labels workload: ballast replicas 2, class gvisor ballast-65cf95d875-bzgjd worker pod, 2 CPU, 3 GiB ballast-65cf95d875-f65dl worker pod, 2 CPU, 3 GiB The dotted line is the only link: the selector of the template matches the labels of the pool. The two names lab-c are equal because we chose them so. Substrate does not compare them.
An atespace holds templates, actors, and tags, a namespace holds worker pools, and only a label selector joins them. Read each tree from the top down. The left tree is in Postgres and the right tree is in Kubernetes. The dotted arrow is the workerSelector of the template. Names are from our resume tests.

The atespace is the boundary for access. A role in an atespace covers every actor and every template in it, and no grant exists for one actor. Do not use the name of a deleted atespace again. The documentation warns that a request authorized for the old atespace can reach the new one.

Pool selection crosses namespaces

A template selects its workers with workerSelector.matchLabels. The scheduler compares those labels with the labels on the WorkerPool object. It does not read the labels of the pods, and it does not read the namespace of the pool. Each worker carries a copy of the labels of its WorkerPool object, and the scheduler matches against that copy.

Three rules follow from the code and the documentation.

  • Only equal labels match. No set expressions exist.
  • An actor can add its own selector. The scheduler applies both selectors together, so the selector of the actor only narrows the choice. A change takes effect at the next resume.
  • A template with no selector can run on every pool.

So the namespace of a pool is not a fence. A template in any atespace can name the labels of any pool, and Kubernetes RBAC on that namespace does not stop it. No document at v1.0.0 states this rule. It follows from the code, and our run below confirms it.

We ran it. We created an atespace xa and no Kubernetes namespace of that name. A template in xa selected workload: vpool, the label of a pool in the namespace lab-v.

outputan actor of atespace xa on a worker pod of namespace lab-vFull log
ATESPACE   NAME   TEMPLATE     STATE                 WORKER POD                     …
xa         x1     xa/cross-0   ACTOR_STATE_RUNNING   lab-v/vpool-69595c64bf-p4slt   …
Both lines of the file. The last three columns are cut.

The template built its golden snapshot on that pool, and the actor ran on a worker pod of lab-v.

A selector that matches no pool is the opposite mistake, and Substrate does not refuse it. We created a second template in xa with the label workload: no-such-pool. The create call succeeded. The template never got a golden tag, and its ERROR column stayed empty. A request to an actor of that template failed.

outputa request to an actor whose template matches no poolFull log
2026-11-12T12:51:40.304Z	call-unmatched-selector	503	5.060180	actor xa/x2 unavailable: no worker has room for the actor
One line of the trace.

We measured both templates once on the x86 server guest (24 vCPU, 47 GB, nested KVM). The first request to the actor on the pool of lab-v returned 200 in 0.26 seconds. The request with the wrong selector waited 5.06 seconds for its 503. Raw runs

That is the text of a full pool, word for word. ate-api-server logged the same reason for the template 8 times in 40 seconds of its log, and the template showed no error. When you see no worker has room for the actor, compare the selector with the pool labels before you add workers. Section 8.3 has the row.

kagent shows the convention that fills this gap. It writes the name and the namespace of the pool into two labels, and its templates select both.

yamlthe selector that kagent writes into its templateFull log
workerSelector:
  matchLabels:
    kagent.dev/worker-pool-name: kagent-default
    kagent.dev/worker-pool-namespace: kagent
The last four lines of the file.

The second label looks like a namespace check, and it is a label like every other. A pool in another namespace with the same two labels would match.

The worker list has one filter for each list of names.

outputthe filters of kubectl ate get workersFull log
-a, --atespace string        filter worker pods hosting actors in a specific atespace
…
-n, --namespace string       scope output to a specific Kubernetes namespace
--sandbox-class string   filter by sandbox class (e.g. gvisor, microvm)
-l, --selector string        filter by worker pool labels
Four lines of the help text that the command printed.

Names

Every name in the Substrate API is a DNS label: lowercase letters, digits, and hyphens, at most 63 characters. A name is fixed at create time.

WhatFormExample from our runs
Actor address<atespace>/<name>ate-demo-counter/my-counter-1
Template reference<atespace>/<name>lab-c/ballast-200
Actor uidassigned by the server6f80f612-7547-4f5b-b3ab-831595621806
Worker namethe uid of the worker podc572e96d-dd9b-4500-b0e3-880a98d9f61d
Golden tag namethe uid of the templateb9f97b82-b187-419d-8789-6c8c7b8a6c8c
kagent actorsession-<session id>session-01a1cf43-d760-747a-b907-5e0640126e4e

The actor address is the form that you will read most. Error texts use it, kubectl ate takes its two halves as -a and the name, and the router reads it from a request header. Section 6.1 covers that header.

The uid matters when you delete an actor and create it again with the same name. The new actor gets a new uid. Snapshot paths use the uid, so the new actor cannot resume from the state of the old one.

The snapshot URI layout we saw

Each snapshot in an actor object has a snapshotUri. The atespace and the owner are part of the path.

yamlthe two snapshot URIs of my-counter-1 after its first suspendFull log
snapshotUri: gs://ate-snapshots/atespaces/ate-demo-counter/tags/ad19ee28-1519-432a-bcc5-40cf99cce3ca
…
snapshotUri: gs://ate-snapshots/atespaces/ate-demo-counter/actors/6f80f612-7547-4f5b-b3ab-831595621806/snapshots/8faccdff-15cc-493d-8474-5ebcb1c18d56
Two lines of the file. The first is the golden snapshot, and the second is the snapshot of the actor.
OwnerLayout
A tag<location>/atespaces/<atespace>/tags/<tag uid>
An actor<location>/atespaces/<atespace>/actors/<actor uid>/snapshots/<snapshot uuid>
An actor, on the node/var/lib/ate/snapshots/<actor uid>/local-checkpoint/<snapshot uuid>

We saw three details that the layout does not tell you.

  • On a kind install the location is gs://ate-snapshots/, although the store is the rustfs pod in the cluster.
  • kagent sets a location for each template, and its URIs start with s3://ate-snapshots/kagent/. That field is a preview feature in the upstream project.
  • The last part of a golden URI is the uid of the tag. It is not the name of the tag, which is the uid of the template.

The atespace is in the path for a reason. An object store can grant access by path prefix, so you can give one team read access to the snapshots of one atespace. We did not test such a grant.

Our position: use one name for the atespace and the namespace when one team owns both, as the demos do. Then treat the labels of your pools as placement, and not as a fence. Any atespace can name them, and we found no setting at v1.0.0 that limits a pool to one atespace. Choose the labels on purpose, and check which templates name them.

Agent Substrate 1.0.0 · b34fbee · 2026-11-05 · Edition 2026.11