v0.1.0-alpha
the agent gateway for Kubernetes
discovery auth routing credentials rate-limits mcp
$ kubectl apply -f agentpolicy.yaml
matchLabelsAgentCards are discovered, not written. The AI Engineer adds a label. GitOps deploys. The operator discovers.
AI Engineer adds label GitOps deploys pod AgentCard CR
kagenti=true, commits Discovery controller watches created automatically
──────────────────────── ────────────────────────── ──────────────────────
A2A agent → GET /.well-known/agent.json → protocols: [a2a]
(name, skills, protocols) skills: [...]
MCP agent → POST /tools/list → protocols: [mcp]
(available tools) skills: [from tools]
REST agent → GET /openapi.json → protocols: [rest]
(endpoints, schemas) skills: [from paths]
The AI Engineer never writes a CRD or runs kubectl. They commit, GitOps deploys, the operator discovers.
AI ENGINEER PLATFORM (GitOps + Operator) PLATFORM ENGINEER
─────────── ──────────────────────────── ─────────────────
1. Push image, add GitOps (ArgoCD/Flux)
kagenti label to syncs manifests to cluster
Deployment, commit |
Discovery controller 2. Define AgentPolicy
fetches agent metadata per tier, commit
creates AgentCard CR to git
| |
AgentCard controller GitOps syncs policy
generates HTTPRoute |
| |
AgentPolicy controller <──────────────────┘
matches cards by labels
generates AuthPolicy, RateLimitPolicy, ConfigMap
|
3. Agent receives traffic Gateway routes via HTTPRoute
(no action needed) Authorino + Limitador + Sidecar enforce everything
The AI Engineer commits a labeled Deployment. GitOps deploys it. The discovery controller creates this CR — no human writes it.
# Auto-generated from /.well-known/agent.json
apiVersion: kagenti.com/v1alpha1
kind: AgentCard
metadata:
name: weather-agent
labels:
tier: standard # from pod labels
domain: weather # from pod labels
spec:
description: "Agent providing weather forecasts and alerts"
protocols:
- a2a
- rest
skills: # extracted from agent card
- name: get-forecast
description: "Returns weather forecast for a location"
- name: weather-alerts
description: "Returns active weather alerts for a region"
servicePort: 8080
The only CRD a human writes. The Platform Engineer defines security rules that select discovered agents by label.
apiVersion: kagenti.com/v1alpha1
kind: AgentPolicy
metadata:
name: standard-tier
spec:
agentSelector:
matchLabels:
tier: standard
ingress:
allowedAgents: [orchestrator, planner]
allowedUsers: ["*"]
external:
defaultMode: deny
rules:
- host: api.openweathermap.org
mode: vault
vaultPath: secret/data/openweathermap
- host: weather.gov
mode: passthrough
rateLimit:
requestsPerMinute: 60
| Mode | How It Works | Use Case |
|---|---|---|
| vault | Sidecar fetches secret from HashiCorp Vault, injects into outbound request header | API keys, static tokens |
| exchange | RFC 8693 Token Exchange — trades inbound JWT for scoped downstream token | OAuth2 APIs (GitHub, cloud) |
| passthrough | Forwards the caller's credentials without transformation | Public APIs, internal services |
| deny | Sidecar blocks (defense-in-depth). Primary enforcement via NetworkPolicy | Default policy, untrusted hosts |
Credential injection happens in the sidecar forward proxy — zero code changes in the agent.
mcp protocol (optional CRD)One AgentPolicy matching N agents generates N sets of downstream resources.
# Premium: OAuth2 token exchange for GitHub API
external:
rules:
- host: api.github.com
mode: exchange
audience: github-api
scopes: [repo, pull_request]
Labeled pod detected
|
v
DiscoveryController
|- Fetch /.well-known/agent.json (A2A) or /tools/list (MCP)
|- Create AgentCard CR with metadata + pod labels
|
v
AgentCardReconciler
|- Add finalizer
|- Build HTTPRoute (host, paths, gateway ref)
|- Create or Update HTTPRoute
|- If "mcp" in protocols → create MCPServerRegistration
|- Update status: Ready condition + generatedHTTPRoute
|
v (watcher triggers)
AgentPolicyReconciler
|- List AgentCards matching policy.spec.agentSelector.matchLabels
|- For each matched card:
| |- Find card's HTTPRoute
| |- If ingress defined → create/update AuthPolicy
| |- If rateLimit defined → create/update RateLimitPolicy
| |- If external defined → create/update sidecar ConfigMap
|- Update status: matchedAgentCards count + generatedResources list
allowedAgents → agent identity in tokenallowedUsers → user identity in tokenexternal.rulesGateway API + MCP Gateway + this operator = a unified agent gateway handling every call pattern.
| Call Pattern | Path | Enforced By |
|---|---|---|
| User → Agent | Client → Gateway → Agent | Authorino (JWT) + Limitador |
| Agent → Agent (A2A) | Agent A → Gateway → Agent B | AuthPolicy (allowedAgents) |
| Agent → MCP Tools | Agent → Gateway → MCP Gateway → MCP Server | MCPVirtualServer (tool filtering) |
| Agent → External API | Agent → Sidecar (vault/exchange) → External | Forward proxy + credential injection |
| Agent → Blocked Host | Agent → NetworkPolicy (network) + Sidecar (app) | NetworkPolicy primary, sidecar defense-in-depth |
Every call — inbound, agent-to-agent, MCP, and outbound — goes through an enforcement point. No direct access.
/mcp endpointMCPServerRegistration when agent declares mcp protocolmcpTools.virtualServerRef ties tool access to tierspremium-tools-vs
# Premium tier agents get MCP tool access
spec:
mcpTools:
virtualServerRef: premium-tools-vs # MCP Gateway filters tools per-agent
defaultMode: denyConfigMapNetworkPolicy blocks at the network. Sidecar injects credentials per host. Both are needed: NetworkPolicy can't do per-host routing, sidecar can be bypassed without NetworkPolicy.
Why not let the platform engineer write HTTPRoutes, AuthPolicies, and RateLimitPolicies directly?
Both discover agents — at different stages and for different purposes.
Registry knows the agent's dependency graph at build time. Access Control enforces what it can actually reach at runtime. Together: full lifecycle governance.
# Install CRDs
make install
# Run the operator
make run
# Deploy sample agents + policies
kubectl apply -f config/samples/
repo → github.com/kagenti/agent-access-control