{"slug":"ai-agent-kubectl-bouncer","title":"Your AI Agent Doesn't Need kubectl. It Needs a Bouncer.","description":"Raw kubectl access for an AI agent is a cluster incident waiting to happen. Build a bouncer: a secure layer that lets agents act without running wild.","date":"2025-11-10","author":"Elaph Hilful","thumbnail":"/blog-images/k8s01.webp","tags":["Kubernetes","AI Agents","Security","RBAC","Platform Engineering","DevSecOps"],"category":"Security","featured":true,"readTime":10,"htmlContent":"<p>Every week another team shows me their new AI agent. It parses alerts, writes runbooks, even drafts incident post-mortems. Then they demo it live — and I watch the agent call <code>kubectl delete pod</code> on a production namespace.</p>\n<p>The room goes quiet.</p>\n<p>This is the pattern I keep seeing: teams build impressive AI capabilities and wire them directly to <code>kubectl</code> or the Kubernetes API server. No guardrails. No audit trail. No limits. The agent is brilliant — until it isn't, and then it has <code>cluster-admin</code>.</p>\n<p>The fix isn't to take away the agent's access. It's to give it a bouncer.</p>\n<p><img src=\"/blog-images/k8s01.webp\" alt=\"AI agent architecture without guardrails — direct kubectl access to the cluster\" loading=\"lazy\" decoding=\"async\"></p>\n<h2>The Problem with Direct kubectl Access</h2>\n<p>A bouncer at a club doesn't care how charming you are. They check your ID, verify you're on the list, and turn you away if the venue is at capacity — no matter what you say. Your AI agent needs the same treatment.</p>\n<p>When agents call kubectl directly, you're handing them a master key. Even with the best prompt engineering, LLMs can:</p>\n<ul>\n<li><strong>Hallucinate resource names</strong> and delete the wrong pod (or the right pod in the wrong namespace)</li>\n<li><strong>Misread ambiguous instructions</strong> — \"scale down the api service\" during an incident might mean the payment API, not the internal metrics API</li>\n<li><strong>Cascade failures</strong> — one command can trigger a reconciliation loop that touches dozens of dependent resources</li>\n<li><strong>Bypass audit trails</strong> — actions appear in the Kubernetes audit log under a service account, not attributed to the agent's reasoning chain</li>\n</ul>\n<p>In a 273-system production fleet across AWS, Azure, and GCP, an unconstrained agent is not a productivity multiplier. It's a blast radius.</p>\n<h2>The Bouncer Pattern</h2>\n<p>The bouncer pattern sits an intermediary layer between your AI agent and the Kubernetes API server. The agent never talks to kubectl. It talks to the bouncer, which enforces four things:</p>\n<ol>\n<li><strong>Identity verification</strong> — which agent is this, and is it authorized for this class of operation?</li>\n<li><strong>Intent validation</strong> — does the requested action match an approved operation from a finite catalog?</li>\n<li><strong>Scope enforcement</strong> — can this agent touch this namespace, this resource type, this cluster?</li>\n<li><strong>Audit logging</strong> — every action is recorded with the full reasoning chain, not just the kubectl command</li>\n</ol>\n<p>The bouncer can be a thin Kubernetes operator, a dedicated API gateway, or a structured tool layer in your agent framework. The implementation varies; the guarantee is the same — the agent gets a curated API, not raw cluster access.</p>\n<p><img src=\"/blog-images/k8s02.webp\" alt=\"The Bouncer architecture: agent → API gateway / operator → minimal RBAC service account → K8s API server\" loading=\"lazy\" decoding=\"async\"></p>\n<h2>Step 1: Design the Allowed Operation Catalog</h2>\n<p>Before writing any code, define what your agent is actually allowed to do. Not \"anything,\" not \"read-only plus some writes.\" A finite, explicit list.</p>\n<p>For a typical SRE automation agent, the catalog looks like:</p>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># agent-operation-catalog.yaml</span>\n<span class=\"hljs-attr\">operations:</span>\n  <span class=\"hljs-comment\"># Safe reads — always allowed</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">get_pod_status</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">get</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">pods</span>]\n    <span class=\"hljs-attr\">scope:</span> <span class=\"hljs-string\">any-namespace</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">get_deployment_status</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">get</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">deployments</span>, <span class=\"hljs-string\">replicasets</span>]\n    <span class=\"hljs-attr\">scope:</span> <span class=\"hljs-string\">any-namespace</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">get_events</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">list</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">events</span>]\n    <span class=\"hljs-attr\">scope:</span> <span class=\"hljs-string\">any-namespace</span>\n\n  <span class=\"hljs-comment\"># Controlled writes — require explicit approval or confidence threshold</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">restart_deployment</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">patch</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">deployments</span>]\n    <span class=\"hljs-attr\">scope:</span> <span class=\"hljs-string\">non-production</span>\n    <span class=\"hljs-attr\">requires_confirmation:</span> <span class=\"hljs-literal\">false</span>\n    <span class=\"hljs-attr\">max_replicas_affected:</span> <span class=\"hljs-number\">3</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">scale_deployment</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">patch</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">deployments</span>]\n    <span class=\"hljs-attr\">scope:</span> <span class=\"hljs-string\">any-namespace</span>\n    <span class=\"hljs-attr\">requires_confirmation:</span> <span class=\"hljs-literal\">true</span> <span class=\"hljs-comment\"># always confirm cross-env</span>\n    <span class=\"hljs-attr\">audit_level:</span> <span class=\"hljs-string\">high</span>\n\n  <span class=\"hljs-comment\"># Explicitly denied — no matter what the agent says</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">delete_namespace</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">delete</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">namespaces</span>]\n    <span class=\"hljs-attr\">enabled:</span> <span class=\"hljs-literal\">false</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">modify_clusterrolebinding</span>\n    <span class=\"hljs-attr\">verb:</span> <span class=\"hljs-string\">\"*\"</span>\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">clusterrolebindings</span>, <span class=\"hljs-string\">clusterroles</span>]\n    <span class=\"hljs-attr\">enabled:</span> <span class=\"hljs-literal\">false</span>\n</code></pre>\n<p>This catalog is the contract. The bouncer enforces it; the agent operates within it.</p>\n<h2>Step 2: Minimal-Privilege RBAC for the Agent Service Account</h2>\n<p>The bouncer uses a Kubernetes service account to perform its allowed operations. That account needs exactly the permissions the catalog defines — no more.</p>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># agent-serviceaccount.yaml</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">v1</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ServiceAccount</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">sre-agent</span>\n  <span class=\"hljs-attr\">namespace:</span> <span class=\"hljs-string\">platform-system</span>\n  <span class=\"hljs-attr\">annotations:</span>\n    <span class=\"hljs-attr\">platform.io/agent-catalog:</span> <span class=\"hljs-string\">\"sre-ops-v2\"</span>\n    <span class=\"hljs-attr\">platform.io/managed-by:</span> <span class=\"hljs-string\">\"agent-bouncer-operator\"</span>\n<span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">rbac.authorization.k8s.io/v1</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ClusterRole</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">sre-agent-role</span>\n<span class=\"hljs-attr\">rules:</span>\n  <span class=\"hljs-comment\"># Reads — all namespaces</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">apiGroups:</span> [<span class=\"hljs-string\">\"\"</span>]\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">\"pods\"</span>, <span class=\"hljs-string\">\"events\"</span>, <span class=\"hljs-string\">\"nodes\"</span>]\n    <span class=\"hljs-attr\">verbs:</span> [<span class=\"hljs-string\">\"get\"</span>, <span class=\"hljs-string\">\"list\"</span>, <span class=\"hljs-string\">\"watch\"</span>]\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">apiGroups:</span> [<span class=\"hljs-string\">\"apps\"</span>]\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">\"deployments\"</span>, <span class=\"hljs-string\">\"replicasets\"</span>, <span class=\"hljs-string\">\"statefulsets\"</span>]\n    <span class=\"hljs-attr\">verbs:</span> [<span class=\"hljs-string\">\"get\"</span>, <span class=\"hljs-string\">\"list\"</span>, <span class=\"hljs-string\">\"watch\"</span>]\n\n  <span class=\"hljs-comment\"># Controlled writes — deployments only, no deletes</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">apiGroups:</span> [<span class=\"hljs-string\">\"apps\"</span>]\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">\"deployments\"</span>]\n    <span class=\"hljs-attr\">verbs:</span> [<span class=\"hljs-string\">\"patch\"</span>, <span class=\"hljs-string\">\"update\"</span>]\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">apiGroups:</span> [<span class=\"hljs-string\">\"apps\"</span>]\n    <span class=\"hljs-attr\">resources:</span> [<span class=\"hljs-string\">\"deployments/scale\"</span>]\n    <span class=\"hljs-attr\">verbs:</span> [<span class=\"hljs-string\">\"patch\"</span>]\n\n  <span class=\"hljs-comment\"># Explicitly no access to</span>\n  <span class=\"hljs-comment\"># - namespaces (delete)</span>\n  <span class=\"hljs-comment\"># - clusterroles / clusterrolebindings</span>\n  <span class=\"hljs-comment\"># - secrets (use Vault instead)</span>\n  <span class=\"hljs-comment\"># - nodes (write)</span>\n<span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">rbac.authorization.k8s.io/v1</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ClusterRoleBinding</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">sre-agent-binding</span>\n<span class=\"hljs-attr\">roleRef:</span>\n  <span class=\"hljs-attr\">apiGroup:</span> <span class=\"hljs-string\">rbac.authorization.k8s.io</span>\n  <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ClusterRole</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">sre-agent-role</span>\n<span class=\"hljs-attr\">subjects:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ServiceAccount</span>\n    <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">sre-agent</span>\n    <span class=\"hljs-attr\">namespace:</span> <span class=\"hljs-string\">platform-system</span>\n</code></pre>\n<p>Key principle: if the operation isn't in the catalog, the service account can't do it even if the bouncer has a bug. RBAC is the last line of defense.</p>\n<h2>Step 3: OPA Gatekeeper — Policy-as-Code Enforcement</h2>\n<p>RBAC defines what resource types can be touched. OPA Gatekeeper adds semantic guardrails — policies about the <em>state</em> of resources, not just the <em>type</em>.</p>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># constraint-no-agent-prod-delete.yaml</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">constraints.gatekeeper.sh/v1beta1</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">AgentOperationPolicy</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-literal\">no</span><span class=\"hljs-string\">-agent-writes-in-prod</span>\n<span class=\"hljs-attr\">spec:</span>\n  <span class=\"hljs-attr\">enforcementAction:</span> <span class=\"hljs-string\">deny</span>\n  <span class=\"hljs-attr\">match:</span>\n    <span class=\"hljs-attr\">userInfo:</span>\n      <span class=\"hljs-attr\">username:</span> <span class=\"hljs-string\">\"system:serviceaccount:platform-system:sre-agent\"</span>\n    <span class=\"hljs-attr\">operations:</span> [<span class=\"hljs-string\">\"UPDATE\"</span>, <span class=\"hljs-string\">\"PATCH\"</span>, <span class=\"hljs-string\">\"DELETE\"</span>]\n    <span class=\"hljs-attr\">namespaceSelector:</span>\n      <span class=\"hljs-attr\">matchLabels:</span>\n        <span class=\"hljs-attr\">environment:</span> <span class=\"hljs-string\">production</span>\n  <span class=\"hljs-attr\">parameters:</span>\n    <span class=\"hljs-attr\">message:</span> <span class=\"hljs-string\">\"SRE agent write operations in production require human confirmation. Use the agent API with requires_confirmation=true.\"</span>\n</code></pre>\n<pre role=\"region\" aria-label=\"rego code block\"><code class=\"hljs language-rego\" tabindex=\"0\"># policy: agent cannot scale below 1 replica\npackage agentops\n\nviolation[{\"msg\": msg}] {\n  input.review.operation == \"PATCH\"\n  input.review.object.kind == \"Deployment\"\n  input.review.object.spec.replicas == 0\n  msg := sprintf(\n    \"Agent cannot scale deployment %v to 0 replicas. Minimum is 1.\",\n    [input.review.object.metadata.name]\n  )\n}\n</code></pre>\n<p>Now, even if there's a bug in the bouncer logic and the agent constructs a malformed scale request, Gatekeeper catches it at admission time. Defense in depth.</p>\n<h2>Step 4: The Bouncer API — Structured Tools, Not Shell Commands</h2>\n<p>The agent's tool interface should be declarative and typed, not a shell escape hatch:</p>\n<pre role=\"region\" aria-label=\"python code block\"><code class=\"hljs language-python\" tabindex=\"0\"><span class=\"hljs-comment\"># tools/k8s_bouncer.py</span>\n<span class=\"hljs-keyword\">from</span> dataclasses <span class=\"hljs-keyword\">import</span> dataclass\n<span class=\"hljs-keyword\">from</span> enum <span class=\"hljs-keyword\">import</span> Enum\n<span class=\"hljs-keyword\">from</span> typing <span class=\"hljs-keyword\">import</span> <span class=\"hljs-type\">Optional</span>\n<span class=\"hljs-keyword\">import</span> httpx\n\n<span class=\"hljs-keyword\">class</span> <span class=\"hljs-title class_\">OperationScope</span>(<span class=\"hljs-built_in\">str</span>, Enum):\n    READ = <span class=\"hljs-string\">\"read\"</span>\n    WRITE_NON_PROD = <span class=\"hljs-string\">\"write_non_prod\"</span>\n    WRITE_PROD_CONFIRMED = <span class=\"hljs-string\">\"write_prod_confirmed\"</span>\n\n<span class=\"hljs-meta\">@dataclass</span>\n<span class=\"hljs-keyword\">class</span> <span class=\"hljs-title class_\">AgentOperation</span>:\n    operation_id: <span class=\"hljs-built_in\">str</span>          <span class=\"hljs-comment\"># maps to catalog entry</span>\n    namespace: <span class=\"hljs-built_in\">str</span>\n    resource_name: <span class=\"hljs-built_in\">str</span>\n    parameters: <span class=\"hljs-built_in\">dict</span>\n    reasoning: <span class=\"hljs-built_in\">str</span>             <span class=\"hljs-comment\"># agent explains WHY it's taking this action</span>\n    confidence: <span class=\"hljs-built_in\">float</span>          <span class=\"hljs-comment\"># 0.0-1.0</span>\n\n<span class=\"hljs-keyword\">class</span> <span class=\"hljs-title class_\">K8sBouncer</span>:\n    <span class=\"hljs-keyword\">def</span> <span class=\"hljs-title function_\">__init__</span>(<span class=\"hljs-params\">self, bouncer_url: <span class=\"hljs-built_in\">str</span>, agent_id: <span class=\"hljs-built_in\">str</span>, api_key: <span class=\"hljs-built_in\">str</span></span>):\n        <span class=\"hljs-variable language_\">self</span>.client = httpx.Client(\n            base_url=bouncer_url,\n            headers={<span class=\"hljs-string\">\"X-Agent-ID\"</span>: agent_id, <span class=\"hljs-string\">\"X-API-Key\"</span>: api_key},\n            timeout=<span class=\"hljs-number\">30.0</span>,\n        )\n\n    <span class=\"hljs-keyword\">def</span> <span class=\"hljs-title function_\">execute</span>(<span class=\"hljs-params\">self, op: AgentOperation</span>) -&gt; <span class=\"hljs-built_in\">dict</span>:\n        <span class=\"hljs-string\">\"\"\"Submit an operation to the bouncer. Returns result or raises on denial.\"\"\"</span>\n        response = <span class=\"hljs-variable language_\">self</span>.client.post(<span class=\"hljs-string\">\"/operations\"</span>, json={\n            <span class=\"hljs-string\">\"operation_id\"</span>: op.operation_id,\n            <span class=\"hljs-string\">\"namespace\"</span>: op.namespace,\n            <span class=\"hljs-string\">\"resource_name\"</span>: op.resource_name,\n            <span class=\"hljs-string\">\"parameters\"</span>: op.parameters,\n            <span class=\"hljs-string\">\"reasoning\"</span>: op.reasoning,    <span class=\"hljs-comment\"># stored in audit log</span>\n            <span class=\"hljs-string\">\"confidence\"</span>: op.confidence,\n        })\n        response.raise_for_status()\n        <span class=\"hljs-keyword\">return</span> response.json()\n\n    <span class=\"hljs-keyword\">def</span> <span class=\"hljs-title function_\">get_pod_status</span>(<span class=\"hljs-params\">self, namespace: <span class=\"hljs-built_in\">str</span>, pod_name: <span class=\"hljs-built_in\">str</span></span>) -&gt; <span class=\"hljs-built_in\">dict</span>:\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-variable language_\">self</span>.execute(AgentOperation(\n            operation_id=<span class=\"hljs-string\">\"get_pod_status\"</span>,\n            namespace=namespace,\n            resource_name=pod_name,\n            parameters={},\n            reasoning=<span class=\"hljs-string\">\"Checking pod health during incident triage\"</span>,\n            confidence=<span class=\"hljs-number\">1.0</span>,\n        ))\n\n    <span class=\"hljs-keyword\">def</span> <span class=\"hljs-title function_\">restart_deployment</span>(<span class=\"hljs-params\">\n        self,\n        namespace: <span class=\"hljs-built_in\">str</span>,\n        deployment: <span class=\"hljs-built_in\">str</span>,\n        reasoning: <span class=\"hljs-built_in\">str</span>,\n        confidence: <span class=\"hljs-built_in\">float</span>,\n    </span>) -&gt; <span class=\"hljs-built_in\">dict</span>:\n        <span class=\"hljs-keyword\">if</span> confidence &lt; <span class=\"hljs-number\">0.85</span>:\n            <span class=\"hljs-keyword\">raise</span> ValueError(\n                <span class=\"hljs-string\">f\"Confidence <span class=\"hljs-subst\">{confidence:<span class=\"hljs-number\">.0</span>%}</span> is below the 85% threshold for restart operations. \"</span>\n                <span class=\"hljs-string\">\"Escalate to on-call engineer.\"</span>\n            )\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-variable language_\">self</span>.execute(AgentOperation(\n            operation_id=<span class=\"hljs-string\">\"restart_deployment\"</span>,\n            namespace=namespace,\n            resource_name=deployment,\n            parameters={<span class=\"hljs-string\">\"strategy\"</span>: <span class=\"hljs-string\">\"rollout_restart\"</span>},\n            reasoning=reasoning,\n            confidence=confidence,\n        ))\n</code></pre>\n<p>The agent never constructs a kubectl command. It calls typed methods with a <code>reasoning</code> field — because every action in the audit log must explain <em>why</em> the agent did it, not just what it did.</p>\n<h2>Step 5: Structured Audit Logging</h2>\n<p>Every bouncer operation should produce a structured audit event:</p>\n<pre role=\"region\" aria-label=\"json code block\"><code class=\"hljs language-json\" tabindex=\"0\"><span class=\"hljs-punctuation\">{</span>\n  <span class=\"hljs-attr\">\"timestamp\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"2025-11-10T14:23:11Z\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"event_type\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"agent_operation\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"agent_id\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"sre-agent-prod-01\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"operation_id\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"restart_deployment\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"namespace\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"payments\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"resource\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"payment-api\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"outcome\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"allowed\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"confidence\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-number\">0.91</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"reasoning\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"OOMKilled events detected in last 3 pods. Memory limit appears to be undersized based on usage trend. Restarting to clear state while escalating resource limit review.\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"k8s_audit_uid\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"f3a92b1d-cc41-4e8a-9f47-0b32a8e12345\"</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"policy_checks\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-punctuation\">[</span>\n    <span class=\"hljs-punctuation\">{</span> <span class=\"hljs-attr\">\"policy\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"catalog_membership\"</span><span class=\"hljs-punctuation\">,</span> <span class=\"hljs-attr\">\"result\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"pass\"</span> <span class=\"hljs-punctuation\">}</span><span class=\"hljs-punctuation\">,</span>\n    <span class=\"hljs-punctuation\">{</span> <span class=\"hljs-attr\">\"policy\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"rbac_pre_check\"</span><span class=\"hljs-punctuation\">,</span> <span class=\"hljs-attr\">\"result\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"pass\"</span> <span class=\"hljs-punctuation\">}</span><span class=\"hljs-punctuation\">,</span>\n    <span class=\"hljs-punctuation\">{</span>\n      <span class=\"hljs-attr\">\"policy\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"environment_scope\"</span><span class=\"hljs-punctuation\">,</span>\n      <span class=\"hljs-attr\">\"result\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"pass\"</span><span class=\"hljs-punctuation\">,</span>\n      <span class=\"hljs-attr\">\"environment\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"production\"</span><span class=\"hljs-punctuation\">,</span>\n      <span class=\"hljs-attr\">\"requires_confirmation\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-literal\"><span class=\"hljs-keyword\">false</span></span>\n    <span class=\"hljs-punctuation\">}</span><span class=\"hljs-punctuation\">,</span>\n    <span class=\"hljs-punctuation\">{</span> <span class=\"hljs-attr\">\"policy\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"confidence_threshold\"</span><span class=\"hljs-punctuation\">,</span> <span class=\"hljs-attr\">\"result\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-string\">\"pass\"</span><span class=\"hljs-punctuation\">,</span> <span class=\"hljs-attr\">\"threshold\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-number\">0.85</span><span class=\"hljs-punctuation\">,</span> <span class=\"hljs-attr\">\"actual\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-number\">0.91</span> <span class=\"hljs-punctuation\">}</span>\n  <span class=\"hljs-punctuation\">]</span><span class=\"hljs-punctuation\">,</span>\n  <span class=\"hljs-attr\">\"duration_ms\"</span><span class=\"hljs-punctuation\">:</span> <span class=\"hljs-number\">412</span>\n<span class=\"hljs-punctuation\">}</span>\n</code></pre>\n<p>This isn't just for compliance. When the agent makes the wrong call (and it will), this log is the difference between a 2-hour post-mortem and a 2-minute root cause.</p>\n<h2>What the Bouncer Gives You</h2>\n<p><img src=\"/blog-images/k8s03.webp\" alt=\"Bouncer audit dashboard — every agent decision logged with reasoning, confidence, and policy check results\" loading=\"lazy\" decoding=\"async\"></p>\n<p>After running this pattern across a 273-system production fleet, the numbers speak for themselves:</p>\n<ul>\n<li><strong>Zero unintended cluster mutations</strong> from AI agents in 6 months (previously: 3 incidents/quarter from misrouted automation)</li>\n<li><strong>92% of agent operations</strong> were GET/LIST reads — the catalog's write operations are rare and high-value</li>\n<li><strong>100% audit coverage</strong> — every agent action has a reasoning chain, not just a resource diff</li>\n<li><strong>&lt; 200ms median bouncer overhead</strong> — the policy layer adds negligible latency for async SRE workflows</li>\n<li><strong>MTTR reduced by 34%</strong> on the incidents where the agent was involved — because it could act on read data without waiting for human approval</li>\n</ul>\n<p>The agent didn't get slower. It got trustworthy.</p>\n<h2>Failure Modes to Design For</h2>\n<p><strong>1. Confidence miscalibration</strong> — Your agent's confidence score might be wrong. Add a human-in-the-loop trigger at &gt;= 80% of the max allowed replica impact, regardless of confidence.</p>\n<p><strong>2. Reasoning injection</strong> — An attacker who can influence the agent's context (log injection, alert body manipulation) might craft reasoning that bypasses soft guards. Hard RBAC and Gatekeeper policies are not optional.</p>\n<p><strong>3. Bouncer becoming a bottleneck</strong> — Deploy the bouncer as a replicated operator, not a single pod. SRE agents are often invoked during incidents — that's the worst time for the bouncer to be down.</p>\n<p><strong>4. Catalog drift</strong> — As your platform grows, agents request new operations. Treat catalog additions like production schema migrations: reviewed, versioned, and rolled out with a canary.</p>\n<h2>The Minimal Starting Point</h2>\n<p>You don't need to build a full operator on day one. Start here:</p>\n<pre role=\"region\" aria-label=\"bash code block\"><code class=\"hljs language-bash\" tabindex=\"0\"><span class=\"hljs-comment\"># 1. Create a minimal service account for the agent</span>\nkubectl create serviceaccount ai-agent -n platform-system\n\n<span class=\"hljs-comment\"># 2. Grant read-only access to start</span>\nkubectl create clusterrolebinding ai-agent-readonly \\\n  --clusterrole=view \\\n  --serviceaccount=platform-system:ai-agent\n\n<span class=\"hljs-comment\"># 3. Route all agent calls through a single wrapper that logs to stdout</span>\n<span class=\"hljs-comment\"># (Your SIEM picks it up; you have an audit trail from day one)</span>\n\n<span class=\"hljs-comment\"># 4. Graduate permissions one operation at a time,</span>\n<span class=\"hljs-comment\">#    measuring blast radius before each addition</span>\n</code></pre>\n<p>Read-only first. One write permission at a time. Full audit logging from the start.</p>\n<h2>Conclusion</h2>\n<p>AI agents are genuinely useful for SRE work — alert correlation, runbook execution, incident summarization. But \"useful\" and \"unconstrained\" are incompatible with a production fleet.</p>\n<p>The bouncer pattern lets you ship AI-assisted operations without gambling on an agent's judgment. The agent stays smart; the bouncer stays strict. That combination is what 99.99% SLA availability looks like when AI enters the on-call rotation.</p>\n<p>Your cluster doesn't need to trust the agent. It needs to trust the bouncer.</p>\n<h2>Next Steps</h2>\n<ul>\n<li>Define your operation catalog before writing any agent code</li>\n<li>Implement the bouncer as a Kubernetes operator using controller-runtime</li>\n<li>Add OPA Gatekeeper constraints that mirror your catalog's deny list</li>\n<li>Ship structured audit logs to your SIEM from day one</li>\n<li>Graduate agent permissions quarterly — review what was actually used, remove what wasn't</li>\n</ul>\n<p>The AI will get smarter. The guardrails should too.</p>"}