Skip to content

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.

github.com/nanohype/eks-agent-platform ·AGENTS.md


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 behind evalRuntime.*) + the tenant chart consumers use. eks-gitops addons-agent-operator git-sources charts/operator and injects per-cluster IRSA to deliver it onto clusters
  • examples/ — 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/v1alpha1
kind: Platform
metadata:
name: my-app
namespace: eks-agent-platform
spec:
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) | vcluster

A BudgetPolicy CR is required (Platform.spec.budget.name references it):

apiVersion: governance.nanohype.dev/v1alpha1
kind: BudgetPolicy
metadata:
name: my-app
namespace: eks-agent-platform
spec:
platformRef: { name: my-app }
monthlyUsd: '2500'
alertThresholdsPercent: [50, 80, 100]
killSwitchEnabled: true # at 120% the operator detaches the baseline IAM policy

The 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.podIdentityclusterName, 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

  1. Apply a BudgetPolicy CR in the tenant namespace (or alongside Platform — operator handles ordering).
  2. Apply a Platform CR referencing the BudgetPolicy.
  3. The operator reconciles:
    • Namespace tenants-<platform> (with pod-security.kubernetes.io/enforce: restricted label)
    • ResourceQuota + LimitRange defaults
    • Default-deny NetworkPolicy plus egress allow-list (DNS, the model gateway, OTel collector)
    • ArgoCD AppProject scoped to the tenant namespace
    • IAM role <cluster>-<app>-tenant bound to the tenant-runtime SA via an EKS Pod Identity association; attaches baseline Bedrock policy + everything in spec.identity.extraPolicyArns, and reconciles a bedrock-model-scoping inline policy that limits Bedrock invoke to the ARNs spec.identity.allowedModelFamilies / allowedModels expand to (both unset = all model invoke denied)
  4. Status reaches Ready; the app’s ApplicationSet entry can start syncing.

Ship an agent fleet

  1. Confirm the tenant Platform is Ready.
  2. Apply a ModelGateway CR (optional but recommended) declaring the model routes the agents will hit.
  3. Apply an AgentFleet CR referencing the Platform. The operator reconciles a Deployment per agent — the tenant’s own image, under the tenant ServiceAccount — plus the KEDA scaler.
  4. Fleet pods run as the tenant-runtime SA; 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:

  1. Detaches the Bedrock-invoke baseline policy from the tenant’s IAM role
  2. Tags the role with platform.nanohype.dev/suspended=true
  3. The PlatformReconciler observes the tag and stops re-attaching the baseline on subsequent reconciles
  4. Status moves to Suspended with a Suspended condition

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-lint on PR
  • Tests: go test ./internal/...; in-memory fakes for AWS clients (see operators/internal/controller/platform_iam_reconcile_test.go for the pattern)
  • Generated artifacts (CRD manifests, deepcopy code) committed; make manifests regenerates them
  • CRD API groups are org-aligned under the nanohype.dev domain: 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