Agent Substrate Day
What it takes on AWS, which services to use,
and where AWS can help the project
Agent Substrate v1.0.0EKS 1.37Amazon Linux 2023 m8i (nested virt)S3RDS for PostgreSQL 18 Secrets ManagerECRBedrock EFSS3 Files
Every Service is ClusterIP: no public endpoints

| Agent Substrate needs | On AWS | Status in v1.0.0 |
|---|---|---|
| PodCertificate + ClusterTrustBundle | EKS 1.37 (both GA) | works, no flags |
| Snapshot object store | Amazon S3 | works |
| microVM guest assets | S3 asset bucket | works; small patch for S3 |
| PostgreSQL | Amazon RDS for PostgreSQL | works |
| Workload identity | EKS Pod Identity | works |
| Actor image pulls | Amazon ECR | works |
| Nested virt for microVM | Intel 7th/8th gen (m8i here) | works |
| Per-actor volumes (CSI) | Amazon EFS and S3 Files | works; ExternalVolumes preview |
| Egress credential injection | AWS Secrets Manager | works |
| Metrics | Amazon Managed Service for Prometheus | ships a GKE PodMonitoring |
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata: { name: substrate-day,
region: us-west-2, version: "1.37" }
addons:
- name: vpc-cni # + enableNetworkPolicy: "true"
- name: eks-pod-identity-agent
- name: aws-ebs-csi-driver
managedNodeGroups:
- name: system # control plane components
instanceType: m8i.xlarge
disablePodIMDS: true # IMDSv2, hop limit 1
- name: gvisor
instanceType: m8i.2xlarge
disablePodIMDS: true
taints:
- { key: ate.dev/sandboxClass, value: gvisor,
effect: NoSchedule }
- name: microvm
instanceTypes: [m8i.2xlarge]
launchTemplate: { id: lt-… } # nested virt, hop limit 1
taints:
- { key: ate.dev/sandboxClass, value: microvm,
effect: NoSchedule }
gvisor; default SandboxConfig gvisor-default/dev/kvm; atelet advertises it as ate.dev/kvmmicrovm, guest assets from S3Same ActorTemplate shape for both. Pick per workload with sandboxClasses on the WorkerPool.

create-cluster.sh and deploy.sh underneathMode 3 runs on images from the proposal branch, ghcr.io/csantanapr/substrate. The installer turns it on.
apiVersion: ate.dev/v1
kind: WorkerPool
spec:
networkTransport:
runtimeClassName: isolated-sandbox # from EKS Auto Mode
CpuOptions/dev/kvm and vmx# launch template data for the microvm node group
{
"CpuOptions": { "NestedVirtualization": "enabled" },
"MetadataOptions": {
"HttpTokens": "required",
"HttpPutResponseHopLimit": 1 },
"BlockDeviceMappings": [{ "Ebs": {
"VolumeSize": 200, "Encrypted": true } }]
}
# find instance types that support it
aws ec2 describe-instance-types --filters \
Name=processor-info.supported-features,\
Values=nested-virtualization
# on a microvm node
crw-rw-rw- 1 root kvm 10, 232 /dev/kvm
kernel 6.18.56-125.164.amzn2023.x86_64
objectstore-plugin sidecar moves every snapshotATE_STORAGE_BACKEND=s3 with Pod IdentityBlock Public AccessBucketOwnerEnforceddefault encryptionTLS-only bucket policyabort incomplete uploads
# a golden (tag) snapshot in S3
atespaces/ate-demo-counter/tags/<uid>/
manifest.json
pages.img.zstd # process memory
pages_meta.img.zstd
fs/pages.img.zstd # durable volumes
rootfs/fscheckpoint.pb.zstd
rootfs/multitar.img.zstd
rootfs/pages.img.zstd # root filesystem writes
# asset bucket (read-only for atelet)
microvm-assets/amd64/<sha256> # cloud-hypervisor
microvm-assets/amd64/<sha256> # virtiofsd
microvm-assets/amd64/<sha256> # guest kernel
microvm-assets/amd64/<sha256> # guest rootfs
# trust policy, scoped to this account and cluster
{ "Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"],
"Condition": {
"StringEquals": { "aws:SourceAccount": "<account>" },
"ArnLike": { "aws:SourceArn": "arn:aws:eks:…:cluster/substrate-day" } } }
aws eks create-pod-identity-association \
--namespace ate-system --service-account atelet \
--role-arn arn:aws:iam::<account>:role/substrate-day-us-west-2-atelet
ECR pull goes on atelet's Pod Identity role, not the node role
| ServiceAccount | Allowed |
|---|---|
ate-system/ate-api-server | snapshot bucket: get, put, delete, list |
ate-system/atelet | snapshot bucket: get, put (no delete) · asset bucket read · ECR pull |
llm-gateway/llm-gateway | bedrock:InvokeModel* on its pinned inference profiles |
| actors | nothing: no role, no keys, no IMDS |
sslmode=verify-full against the RDS CA bundle# install: logins read from Secrets Manager
export ATE_API_POSTGRES_OWNER_CONNECTION_STRING=\
"postgres://substrate_owner_user:…@<rds>:5432/atepg\
?sslmode=verify-full\
&sslrootcert=/run/postgres-server-ca/server-ca.pem"
export ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING=…
ate-setup deploy ate-system \
--image-repo ghcr.io/agent-substrate/substrate \
--image-tag v1.0.0 \
--snapshot-location s3://<snapshots>/ \
--ateapi-postgres-server-ca-file global-bundle.pem \
--sandbox-classes gvisor,microvm
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: { name: ate-db, namespace: ate-system }
spec:
refreshInterval: 1m
secretStoreRef: { kind: SecretStore, name: aws } # Pod Identity
target:
name: ate-api-server-secret-envvars # what ate-setup made
creationPolicy: Merge # keep its other keys
template:
data:
ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING: >-
postgres://{{ .user }}:{{ .pw | urlquery }}@<rds>:5432/…
data:
- secretKey: user
remoteRef: { key: <cluster>/ate-readwrite, property: username }
- secretKey: pw
remoteRef: { key: <cluster>/ate-readwrite, property: password }
# the owner login: the same, with ate-owner
---
apiVersion: reloader.external-secrets.io/v1alpha1
kind: Config
spec:
notificationSources: [{ type: KubernetesSecret }]
destinationsToWatch:
- { type: Deployment, deployment: { names: [ate-api-server] } }
PostgreSQLMultiUser Lambdarotate-secret now# EgressPolicy on the actor
rules:
- https:
hostnames: ["api.github.com"]
effects:
replaceHeaders:
- header: Authorization
prefix: "Bearer "
credentialUri: ate-secret://secretsmanager.amazonaws.com/
accounts/<id>/regions/us-west-2/
secrets/ate/team-a/github/stages/AWSCURRENT
---
# provider policy: atespace -> secrets, default deny
policies:
- atespace: team-a
allowedSecrets:
- { account: "<id>", region: us-west-2, namePrefix: ate/team-a/ }
ate/*, tag ate.dev/injectableAWSCURRENT cached 30s, no redeploySet disablePodIMDS: true (hop limit 1) on every node group, so the gateway signs with its own role, never the node instance role.
SigV4 signs each request's body hash, so signing lives in a proxy that holds the role.
# StorageClass: one access point per actor
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap # s3files-ap for S3 Files
fileSystemId: fs-…
directoryPerms: "700"
uid: "<uid>" # EFS_UID: the actor's user
gid: "<gid>"
basePath: /ate
---
# CSIDriverConfig: ate-api-server finds the controller
driverName: efs.csi.aws.com
controllerEndpoint:
tcp://efs-csi-controller-ate.kube-system.svc…:50052
tls:
enabled: true
usePodIdentity: true
serverName: efs-csi-controller-ate.kube-system.svc
---
# ActorTemplate
volumes:
- name: data
external: { template: { capacityBytes: 1073741824,
storageClassName: efs-ate } }
containers:
- volumeMounts: [{ name: data, mountPath: /home/counter }]
| Storage | Sandbox | Zones |
|---|---|---|
| EFS | gVisor | us-west-2c → us-west-2b |
| EFS | microVM | us-west-2a → us-west-2b |
| S3 Files | gVisor | us-west-2b → us-west-2c |
| S3 Files | microVM | us-west-2b → us-west-2a |
A captured run: same access point, new zone
SUSPENDED, the worker is freekubectl ate create actor demo-gvisor \
-a ate-demo-counter --template counter
kubectl ate proxy actor demo-gvisor \
-a ate-demo-counter 8001:80 &
curl localhost:8001
kubectl ate suspend actor demo-gvisor -a ate-demo-counter
curl localhost:8001 # resumes
Condensed from a run of this demo on the cluster above
chat message → gateway → atenet-router → actor →
atenet-egress → LLM gateway → Bedrock → reply
Condensed from a run of this demo on the cluster above
ate-setup assumes GKE for any real clustersetup.sqlate-setup deploy --component: one piece at a timeate-setup render: manifests to your own tooling--ateapi-authz-bootstrap-owners: your operatorsAgent Substrate runs on EKS today.
Help us make it boring:
plugins, neutral install seams, per-actor credentials.
github.com/agent-substrate/substrateagent-substrate-plugins