eks-agent-platform
The control plane
The CRDs a tenant is declared in — Platform, AgentFleet, ModelGateway, BudgetPolicy, EvalSuite — and the operator that reconciles them, including the per-tenant AWS identity and access it mints.
What follows is that repo's AGENTS.md — the entry point an agent reads before working on it. It is rendered from the repo itself at build time rather than copied, so it says what the repo says today. Links in it point back at the repo they were written for.
eks-agent-platform — agent entry point
You’re an AI client (or the author of one) about to declare a tenant on an EKS cluster, ship an agent fleet, set a budget, or configure a model gateway. This file gets you running in five minutes. For the wider picture — how this repo fits into the nanohype stack — read the Platform Reference.
What this repo gives you
A Kubernetes-native control plane that lets you declare agent platforms as CRDs and have an operator reconcile the AWS state, namespace boundary, tenant IAM identity, KMS grants, network policies, and runtime resources. Nine CRDs (version v1alpha1) split across three capability groups under the nanohype.dev domain — platform.nanohype.dev (Tenant, Platform), agents.nanohype.dev (AgentFleet, ModelGateway, AgentSandbox, SandboxPool), governance.nanohype.dev (BudgetPolicy, EvalSuite, SLOPolicy):
| CRD | What it owns |
|---|---|
Tenant |
Cluster-scoped aggregate of a team’s Platforms. Rolls up readiness, spend, and suspension state |
Platform |
Tenant Namespace, ResourceQuota, LimitRange, default-deny NetworkPolicy, ArgoCD AppProject, per-Platform IAM role + Pod Identity association + KMS grant + S3 bucket policy |
ModelGateway |
Envoy AI Gateway routes, Bedrock model ID resolution, Guardrails attachment, per-route rate limits |
AgentFleet |
Deployment per agent running the tenant’s own image, KEDA ScaledObject, per-fleet NetworkPolicy, all under the tenant ServiceAccount bound to the tenant IAM role via EKS Pod Identity |
SandboxPool |
Pull-based pool of always-on Managed Agents sandbox workers — a worker Deployment, default-deny NetworkPolicy, and a KEDA-autoscaled metrics bridge keyed on work-queue depth |
AgentSandbox |
Single-use hardened pod for one agent role-session — push-dispatched, Platform-gated, default-deny networked, garbage-collected after a TTL |
BudgetPolicy |
Hourly Athena rollup of CUR + CloudWatch in-flight estimate. Writes spend / percent / conditions to status. Publishes BudgetBreach to EventBridge at ≥120% |
EvalSuite |
Argo CronWorkflow per suite. Gates Argo Rollouts via AnalysisTemplate on status.lastScore |
SLOPolicy |
Multi-window burn-rate evaluation of one objective against Amazon Managed Prometheus. Publishes BurnRateBreach to EventBridge, and holds the tenant’s ArgoCD auto-sync on a page-tier burn |
Plus:
operators/— Go operator binary registering nine reconcilers (one binary, one leader-election lease)charts/— Helm charts for installing the operator (CRDs + Deployment + RBAC + the eval-runtime bundle behindevalRuntime.*) + thetenantchart consumers use.eks-gitopsaddons-agent-operatorgit-sourcescharts/operatorand injects per-cluster IRSA to deliver it onto clustersexamples/— minimal end-to-end CR sets (Tenant + Platform + ModelGateway + AgentFleet + BudgetPolicy) you can copy
Contract surface
The Platform CR is the entry point. Minimum shape (full field reference in docs/crd-reference/v1alpha1.md):
apiVersion: platform.nanohype.dev/v1alpha1kind: Platformmetadata: name: my-app namespace: eks-agent-platformspec: displayName: 'My App' persona: ops # sales-ops | support | finance | ops | founder | eng | marketing | legal | generic tenant: my-team budget: name: my-app # must reference an existing BudgetPolicy CR in the same namespace identity: allowedModelFamilies: [anthropic] # Bedrock families the operator-reconciled tenant role grants invoke on extraPolicyArns: [] # managed IAM policy ARNs to attach on top of the baseline compliance: soc2: true isolation: namespace # namespace (default) | vclusterA BudgetPolicy CR is required (Platform.spec.budget.name references it):
apiVersion: governance.nanohype.dev/v1alpha1kind: BudgetPolicymetadata: name: my-app namespace: eks-agent-platformspec: platformRef: { name: my-app } monthlyUsd: '2500' alertThresholdsPercent: [50, 80, 100] killSwitchEnabled: true # at 120% the operator detaches the baseline IAM policyThe two-role identity picture
A Platform tenant ends up with two IAM roles serving different workload classes. This is intentional, not duplication. Both are bound to their ServiceAccount by an EKS Pod Identity association, never a role-arn annotation — no tenant chart carries a role ARN (the platform-tenant contract forbids it):
| Role | Owner | Bound ServiceAccount (Pod Identity association) | Used by |
|---|---|---|---|
<app>-platform |
landing-zone/components/aws/<app>-platform |
tenants-<team>:<app> |
The application’s chart pods (the one shipped via <app>/chart/) |
<cluster>-<app>-tenant |
This operator | tenants-<team>:tenant-runtime |
AgentFleet pods landing in this Platform’s namespace |
The landing-zone <app>-platform component creates the Pod Identity association binding the app’s ServiceAccount to its role; the chart just pins serviceAccount.name to the app name so the association matches. The operator creates the association for tenant-runtime → <cluster>-<app>-tenant, which carries the baseline Bedrock policy + extraPolicyArns. The tenant role name is cluster-keyed (not env-keyed) so two clusters can host a Platform of the same name without their roles colliding.
The association the operator created is reported on Platform.status.podIdentity — clusterName, namespace, serviceAccount, roleArn. Read it rather than deriving the binding: the trust policy carries no subject (under Pod Identity the (namespace, service-account) constraint lives in the association, so every tenant role’s trust document is byte-identical), and the bound ServiceAccount is not always tenant-runtime — under spec.isolation: vcluster it is the vcluster-translated host name. Anything auditing the binding, in this repo or another, reads that field.
Declare a tenant
- Apply a
BudgetPolicyCR in the tenant namespace (or alongside Platform — operator handles ordering). - Apply a
PlatformCR referencing the BudgetPolicy. - The operator reconciles:
- Namespace
tenants-<platform>(withpod-security.kubernetes.io/enforce: restrictedlabel) ResourceQuota+LimitRangedefaults- Default-deny
NetworkPolicyplus egress allow-list (DNS, the model gateway, OTel collector) - ArgoCD
AppProjectscoped to the tenant namespace - IAM role
<cluster>-<app>-tenantbound to thetenant-runtimeSA via an EKS Pod Identity association; attaches baseline Bedrock policy + everything inspec.identity.extraPolicyArns, and reconciles abedrock-model-scopinginline policy that limits Bedrock invoke to the ARNsspec.identity.allowedModelFamilies/allowedModelsexpand to (both unset = all model invoke denied)
- Namespace
- Status reaches
Ready; the app’s ApplicationSet entry can start syncing.
Ship an agent fleet
- Confirm the tenant Platform is
Ready. - Apply a
ModelGatewayCR (optional but recommended) declaring the model routes the agents will hit. - Apply an
AgentFleetCR referencing the Platform. The operator reconciles a Deployment per agent — the tenant’s own image, under the tenant ServiceAccount — plus the KEDA scaler. - Fleet pods run as the
tenant-runtimeSA; the Pod Identity association the operator created vends the tenant IAM role’s credentials to them.
Kill-switch
When a BudgetPolicy hits 120% of monthlyUsd and killSwitchEnabled: true, an EventBridge rule → Step Functions state machine:
- Detaches the Bedrock-invoke baseline policy from the tenant’s IAM role
- Tags the role with
platform.nanohype.dev/suspended=true - The
PlatformReconcilerobserves the tag and stops re-attaching the baseline on subsequent reconciles - Status moves to
Suspendedwith aSuspendedcondition
Recovery is human-only — an operator clears the suspension tag manually after the breach is resolved. The reconciler does not auto-restore.
Conventions
- Conventional Commits enforced via
commitlint.config.mjs(scope enum:operators,charts,terraform,core,sdk,pricing,client,cli,examples,docs,ci,release,deps,security) - Go:
go fmt,go vet,golangci-linton PR - Tests:
go test ./internal/...; in-memory fakes for AWS clients (seeoperators/internal/controller/platform_iam_reconcile_test.gofor the pattern) - Generated artifacts (CRD manifests, deepcopy code) committed;
make manifestsregenerates them - CRD API groups are org-aligned under the
nanohype.devdomain:platform.nanohype.dev(Tenant, Platform),agents.nanohype.dev(AgentFleet, ModelGateway, AgentSandbox, SandboxPool),governance.nanohype.dev(BudgetPolicy, EvalSuite, SLOPolicy). Finalizers, label/tag keys, and the leader-election lease ID follow the same domain. A Platform’s tenant field carries the owning team’s real name —strategy,growth,reliability,workplace
Pointers
ARCHITECTURE.md— bounded contexts, data flow, load-bearing decisions, the operator-owned-vs-tofu-owned splitdocs/crd-reference/v1alpha1.md— field-by-field CRD reference (generated from godoc)examples/— end-to-end CR sets you can copyREADME.md— install, run, contribute- Platform Reference — the stack-wide view
landing-zone/AGENTS.md— itsagent-iamcomponent provisions the operator’s own role, the tenant permissions boundary, and the IAM path every tenant role is minted under