all_lessons/kubernetes_genai/17lesson 17 / 18

Part V - AI applications

Agents on Kubernetes

Lesson 16 gave a model the ability to read trustworthy context: a retrieval path that pulls facts into the prompt and a provenance trail that says where each fact came from. An agent goes one step further — it can act. It calls tools, books things, writes to systems, and sometimes hands work to other agents. The moment an agent can act, the platform must answer a new set of questions that RAG never had to: on whose behalf is this action taken, with what authority, through which tool, recorded where, and resumable how? This lesson maps those answers onto Kubernetes primitives so that tool access, identity, and state are explicit objects you can audit — not implicit behavior hidden inside the model's loop.

Source coverage
PDF Chapter 8 and Chapter 9: agentic workflows, the Model Context Protocol (MCP), MCP security, agent-to-agent (A2A) communication, and agent state management.
Linear position
Prerequisite: Lesson 16 (RAG on Kubernetes) — a retrieval path with provenance, plus the Deployment / StatefulSet / Job primitives from across the track. You already know that a pod is ephemeral and that anything durable lives outside it.
New capability: Run an agent as a workload with authority — give it its own service identity, bound tool access through MCP or a tool gateway, externalized state with checkpoints, and human gates on dangerous actions — without letting any of those become implicit.
The plan
Five moves. (1) Establish the mental model — an agent is a workload with authority — and name the new surfaces it adds over a stateless service. (2) Separate the three identities the system must never conflate: user identity, the agent's own service identity, and delegated tool authority; show how SPIFFE/SPIRE issues the second. (3) Make the central security failure concrete — token passthrough versus token exchange — with a request-path diagram of the confused-deputy problem. (4) Bind tools through MCP or an explicit tool gateway so capabilities are schema-bound and discoverable, not free-form HTTP. (5) Externalize state with checkpoints and structured A2A logs so a pod restart does not lose a multi-hour run and a multi-agent failure can be reconstructed. Then failure modes, a checklist, and the hand-off into the capstone.

1 · An agent is a workload with authority

A RAG service is, at bottom, a function: text in, text out. It reads from a vector store but it does not change the world. An agent is different. It runs a loop — observe, decide, call a tool, observe the result, decide again — and the tools it calls send email, open pull requests, charge cards, delete files, spin up infrastructure. The defining property is not intelligence; it is authority. An agent is a workload that has been granted the ability to act on systems beyond itself.

Concretely, the loop is the thing that distinguishes an agent from a chatbot. A single-shot model call is a function with no lasting consequence. The agent loop turns that function into a process with memory and side effects:

while not done:
    obs    = read(context, last_tool_result)   # observe
    plan   = model(obs)                         # decide  (model RECOMMENDS)
    if plan.action.needs_gate:                  # costly/irreversible/sensitive?
        wait_for_human_approval()               # explicit workflow state
    token  = exchange(user_token, agent_svid,   # downscope to least privilege
                      scope=plan.action.scope)
    result = tool_call(plan.action, token)      # ACT  (platform DECIDES)
    checkpoint(run_id, step, obs, result)       # persist before next step

Every line of that loop touches a platform surface — the gate, the token exchange, the tool call, the checkpoint. That is why agents cannot be treated as "just another microservice with a clever prompt": each iteration is a place where authority is exercised and state must survive.

That single property is what makes agents a platform problem rather than an application detail. A stateless web service needs a Deployment, a Service, and an HPA. An agent needs all of that plus four surfaces that a stateless service never had:

identity → who is this agent, and on whose behalf is it acting right now? (service identity + delegated authority)
tools → what capabilities can it reach, with what schema and what blast radius? (MCP / tool gateway + NetworkPolicy)
state → where does a long-running loop persist progress so a restart resumes instead of restarting? (KV / database / object storage + checkpoints)
gates → which actions are too costly, irreversible, or sensitive to take without a human? (approval states + audit log)

The mental model for the whole lesson: Kubernetes already gives you the substrate for all four — workload identity, network boundaries, persistent volumes and external stores, events and Jobs, and admission/network policy. The agent framework (LangGraph, CrewAI, an ADK runtime, the model server's own tool loop) must fit inside those boundaries, not invent its own shadow versions of them. When a framework keeps identity in an environment variable, state in the chat transcript, and tool access in a hard-coded list, it has reimplemented the platform badly. The job of this lesson is to push each surface back onto the primitive that already exists for it.

The one rule that governs everything below
The model is never the authority boundary. The model can recommend an action — "delete these files", "issue this refund", "call this internal API". Whether that action actually happens is decided by tool schemas, IAM scopes, NetworkPolicy, service identity, approval states, and audit logs — platform machinery the model cannot talk its way past. Every failure mode in this lesson is a case of letting the model, or a token the model carries, become the boundary.

2 · The three identities you must keep separate

The most common and most dangerous mistake in agent platforms is collapsing three distinct identities into one credential. Name them and keep them apart:

User identity
Who started the request. A human (or upstream service) authenticated at the gateway — proves who is asking. Carries the user's own permissions, which are usually broad.
Service identity
The agent's own workload identity — what the agent process is, independent of any user. On Kubernetes this is a SPIFFE ID / SVID (below) or a cloud workload identity bound to the pod's ServiceAccount.
Delegated tool authority
What this agent is allowed to do on whose behalf when it calls a specific tool — a narrowly scoped, short-lived token minted for one tool call, not the user's full credential.

These answer three different questions — who is asking, what is acting, and what is it allowed to do right now — and conflating any two of them is how authority leaks. If the agent simply reuses the user's identity for everything, the agent inherits the user's full permissions (over-broad). If it uses only its own service identity for everything, it loses the user context needed for per-user authorization and audit. The correct shape keeps all three and combines them at the moment of a tool call: this user, through this agent, is permitted this narrow capability.

SPIFFE/SPIRE: issuing the agent's service identity

SPIFFE (Secure Production Identity Framework For Everyone) is a standard for naming workloads — a SPIFFE ID looks like a URI, e.g. spiffe://prod.example/ns/agents/sa/research-agent. SPIRE is the reference implementation that runs as a server plus a per-node agent: it attests a pod (verifies it really is the workload it claims, using the node, its ServiceAccount, and selectors) and then issues it a short-lived SVID (SPIFFE Verifiable Identity Document) — typically an X.509 certificate or JWT that rotates every few minutes. The point: the agent pod gets a cryptographic, automatically rotated identity tied to what it is, not a long-lived secret baked into an image. A downstream tool or another agent can verify the caller's SPIFFE ID via mTLS before deciding anything. This is the Kubernetes-native way to make "service identity" a real, verifiable thing rather than a string in a config map.

3 · The central failure: token passthrough vs. token exchange

Here is the security mistake that defines this lesson. A user authenticates at the gateway with a token that carries their full permissions. The agent needs to call a tool on the user's behalf. The lazy implementation simply passes the user's token through to the tool — and to the next tool, and to any sub-agent. This is token passthrough, and it is a textbook confused deputy: the agent, a deputy acting for the user, now wields the user's entire authority for a task that needed a sliver of it. If the agent is compromised, if a tool is malicious, or if the model is prompt-injected into calling the wrong tool, that broad token is right there to be abused. The agent ends up with more authority than the task — or even the user, in that narrow context — should have.

The fix is token exchange: at the moment of a tool call, the agent presents the user token and its own service identity to a token service (an OAuth 2.0 Token Exchange endpoint, RFC 8693), which downscopes them into a fresh, least-privilege, short-lived token good for exactly one tool and one operation. The user token never reaches the tool; the tool only ever sees a narrow capability. Compare the two request paths:

PASSTHROUGH (confused deputy)
  user --[full user token]--> gateway --[same full token]--> agent
       --[same full token]--> tool A
       --[same full token]--> tool B        <-- every hop holds full authority

EXCHANGE (least privilege)
  user --[user token]--> gateway (authn) --> agent (own SPIFFE identity)
       agent + user token --> token-exchange service
                          --> [scoped: tool=email, action=send, ttl=60s] --> tool
       different tool call --> token-exchange --> [scoped: tool=calendar, read, ttl=60s] --> tool
                                                  ^ user token never leaves the boundary
Worked contrast — what a leak costs
Say the user token carries 40 OAuth scopes (it is a broad SSO token: mailbox read/write, calendar, drive, billing, admin). Under passthrough, a single prompt-injection that makes the agent call a malicious tool hands that tool all 40 scopes for the token's full lifetime (often an hour). Under exchange, the same injected call yields a token with 1 scope (email:send), valid 60 seconds, usable against one tool endpoint — and the token-exchange service logged exactly which agent identity requested it for which user. The blast radius drops from "the user's entire account for an hour" to "send one email in the next minute", and you have an audit record either way. That delta is the entire reason token exchange exists.

A note on placement and scaling. Token exchange adds one round-trip per tool call to the token service — typically a few milliseconds against a cached or co-located issuer, negligible next to the tool call and the model inference it sits between. The cost is operational, not latency: you must run and trust a token-exchange service (or your cloud's STS) and configure each tool to accept only its own narrow scope. That is the trade — a small standing service in exchange for collapsing the blast radius. The token service and the SPIRE server are themselves privileged workloads; lock them down with NetworkPolicy and run them in their own namespace, because whoever can mint tokens or identities owns the whole agent fleet.

4 · Bind tools through MCP or an explicit gateway

An agent's tools should not be ad-hoc HTTP calls scattered through application code. MCP — the Model Context Protocol — is a standard for schema-bound tool and capability discovery and invocation: an MCP server advertises a typed list of tools (name, input schema, output schema, description), the agent discovers them at runtime, and every call is validated against that schema. The value on a platform is that the set of capabilities is explicit and inspectable: you can list what an agent can reach, version it, and put a policy in front of it, instead of grepping code for `requests.post`.

On Kubernetes, an MCP server is just another workload — a Deployment behind a Service, reachable only by the agents that need it (lock the path down with NetworkPolicy so a compromised agent cannot reach tools outside its job). The agent calls the MCP server; the MCP server holds the scoped tool credentials minted by token exchange and makes the actual outbound call. That gives you a single chokepoint to enforce schema validation, rate limits, allow-lists, and audit logging — the "explicit tool gateway" pattern. The MCP tool list is itself an attack surface: a malicious or over-broad tool description can steer the model, so the catalogue of advertised tools needs the same review discipline as any other privileged interface.

The single-chokepoint shape also makes the network policy trivially expressible. The agents call the MCP gateway and nothing else outbound; the gateway is the only workload with egress to the actual tool endpoints. A minimal NetworkPolicy that pins the gateway as the sole talker to a tool looks like this:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: tools-via-gateway-only, namespace: agents }
spec:
  podSelector: { matchLabels: { role: tool-backend } }
  policyTypes: ["Ingress"]
  ingress:
  - from:
    - podSelector: { matchLabels: { app: mcp-gateway } }  # ONLY the gateway
    # agents have no path here; a compromised agent cannot reach the tool directly
Where MCP security and the three identities meet
The schema tells the agent how to call a tool; it says nothing about whether it may. Authorization still belongs to the platform: the MCP gateway checks the agent's service identity (SPIFFE), looks at the user the call is on behalf of, and presents the downscoped token from §3. MCP makes capabilities discoverable and typed; it does not replace the authority boundary.

5 · Externalize state: checkpoints and reconstructable A2A

An interactive agent that answers in two seconds can keep its working state in memory. A long-running agent — a research run that takes twenty minutes, an automation workflow that spans hours waiting on a human approval — cannot. Pods are ephemeral: a node drain, an OOM kill, a rolling update, or a spot reclaim restarts the pod at any time. If the agent's loop state lived only in memory, the entire multi-step run is lost and must start over — re-paying for every tool call and model token already spent.

The fix is the same one RAG taught for vector stores: state lives outside the pod, and the agent checkpoints after each meaningful step. A checkpoint is a durable snapshot of the run — its step index, accumulated observations, pending tool calls, and any human-gate status — written to an external store keyed by a run ID. On restart, the agent loads the latest checkpoint and resumes from the next step. Pick the store by access pattern (a production system usually uses all three):

StoreFitsCost / risk
KV store (e.g. Redis)Hot loop state and checkpoints looked up by run/session ID; fast resume.Weak cross-run querying; treat as cache + recent checkpoints, back it with durable storage.
Relational DBAudit trail, retention, analytics, cross-session queries, who-did-what.More schema and privacy work; not where you put 200 MB intermediate blobs.
Object storageLarge artifacts and checkpoint blobs (intermediate files, generated docs).High latency per object; index it from the DB, do not list-scan it on the hot path.
run R-9af1, checkpoint after step 4
{
  run_id:   "R-9af1",  user: "u-204",  agent_svid: "spiffe://.../research-agent",
  step:     4,
  status:   "awaiting_human_approval",      # <-- a real workflow state, not a chat line
  observations: [ ...step 1-4 results... ],
  pending_tool_call: { tool: "spend", args: {amount: 4200}, needs_gate: true },
  audit:    [ {step:3, tool:"search", scoped_token:"...", ts:...}, ... ]
}
# pod restarts -> load latest checkpoint for R-9af1 -> resume at step 5 (or the gate)

On Kubernetes this maps cleanly: an interactive agent is a Deployment behind a Service; a long-running agent is often a Job (or a worker consuming a queue) that checkpoints to the external store; an ambient/triggered agent is an event-driven worker or CronJob. A human-in-the-loop gate becomes an explicit workflow stateawaiting_human_approval above — that the run sits in until an approval event arrives, not a chat message the model might ignore. Put these gates wherever an action is irreversible, costly, or policy-sensitive (deletes, spend above a threshold, anything touching production).

Checkpointing alone is not enough; resume must be idempotent. Consider the worked failure: the agent calls the "charge card" tool, the charge succeeds, and the pod is killed before it can write the checkpoint recording that success. On restart it loads the last checkpoint, sees the charge as still pending, and charges again — a double-charge born of a crash, not a model error. The fix is to make the side-effecting step carry an idempotency key (the run ID + step index), so the tool deduplicates a repeated call, and to record the intent to act before acting and the outcome after. This is exactly why the checkpoint stores pending_tool_call as a distinct field: on resume the agent first asks "did this already happen?" rather than blindly re-issuing it. Externalized state buys resumability; idempotent steps buy correct resumability.

A2A must be reconstructable

A2A — agent-to-agent communication — is what happens when a coordinator agent delegates a sub-task to a specialist agent. Mechanically it is ordinary service-to-service communication, but with stronger contracts: discover the peer's capability, send a typed task, track its status, collect the result, and preserve the original user's request context across the hop. The failure to avoid is opacity: if delegated calls are unlogged, a multi-agent failure cannot be reconstructed afterward — you see a bad final answer and have no way to know which agent, on which sub-task, with which inputs, went wrong. So A2A rides on the same primitives as everything else: each agent exposes a Service, NetworkPolicy limits who may call whom, the caller's SPIFFE identity tells the receiver who is asking, the downscoped token (not the user's full token) authorizes the sub-task, and a shared trace ID + structured message log ties every hop back to the originating user request. Without those, a multi-agent system is just distributed hidden state.

6 · One tool call, all three identities at once

Pull the pieces together with a single concrete call. A user asks a research agent to "email the Q3 summary to finance." Trace the identities and tokens at the moment the agent invokes the email:send tool:

1. user u-204 authenticates at gateway        -> user token (40 scopes, ttl 1h)
2. gateway forwards request to research-agent  -> agent runs as spiffe://.../research-agent
3. agent decides: send_email(to=finance, body=summary)
4. agent -> token-exchange(user=u-204, agent_svid, scope="email:send", aud="email-tool")
                                               -> scoped token (1 scope, ttl 60s)
5. agent -> mcp-gateway  (mTLS: gateway verifies agent's SPIFFE ID)
6. mcp-gateway validates args vs email:send schema, checks allow-list,
   presents scoped token to email-tool        -> email sent
7. checkpoint(run, step, intent+outcome, scoped_token_id) -> audit row
   trace_id ties steps 1-7 to user u-204's original request

Read off the three identities: the user is u-204 (step 1), the service identity is the agent's SPIFFE ID (steps 2, 5), and the delegated tool authority is the 60-second single-scope token (step 4). At no point does the user's 40-scope token leave the gateway boundary, no action runs without a schema-validated, policy-checked, audited path, and the whole chain is reconstructable from one trace ID. If a single one of these is missing — passthrough at step 4, no SPIFFE check at step 5, no checkpoint at step 7 — the corresponding failure mode below is what you get.

Failure modes and checklist

Failure modes

  • Token passthrough (confused deputy). The agent forwards the user's full-scope token to tools and sub-agents, so a compromise or prompt-injection wields the user's entire authority. Signal: tool-side audit logs show the user's broad token instead of a narrow, short-lived scoped one; one injected call can reach unrelated APIs.
  • Lost state on restart. A long-running agent keeps loop state in memory, so a node drain or OOM kill restarts the run from zero. Signal: duplicated tool side-effects, re-billed model spend, and runs that never complete on preemptible nodes; no checkpoint rows for in-flight run IDs.
  • Opaque A2A. Inter-agent calls are unlogged and carry no trace context, so a multi-agent failure cannot be reconstructed. Signal: a wrong final answer with no per-hop record of which sub-agent received which inputs; traces dead-end at the coordinator.
  • Model as authority boundary. An action runs because the model "decided" to, with no rule, scope, or gate in front of it. Signal: an irreversible or high-cost action that has no approval-state transition and no IAM/NetworkPolicy check in its path.
  • Tool catalogue sprawl. The MCP/tool list grows unreviewed, so a single over-broad or malicious tool description widens the blast radius. Signal: agents can reach tools unrelated to their job; no allow-list, no NetworkPolicy scoping the MCP server.

Implementation checklist

  • Are user identity, the agent's service identity (SPIFFE/SVID), and delegated tool authority kept as three separate things — never one shared credential?
  • Does every tool call go through token exchange that downscopes to a least-privilege, short-lived token, so the user's full token never reaches a tool?
  • Are tools bound through MCP or an explicit gateway with schema validation, allow-lists, and NetworkPolicy — not ad-hoc HTTP in app code?
  • Does the agent checkpoint long-running state to an external store keyed by run ID, so a pod restart resumes instead of restarting, with an audit trail?
  • Are human-in-the-loop gates explicit workflow states at every irreversible/costly/policy-sensitive action — not chat messages the model can skip?
  • Do A2A calls carry SPIFFE identity, a scoped token, and a shared trace ID, with every hop logged so failures can be reconstructed?
  • Is there one place that can answer "what can this agent reach, and on whose behalf?" — or is authority scattered and implicit?

Checkpoint exercise

Try it
Design the request path for a "travel-booking agent" that a user invokes to book a flight under a spending cap. Draw the three identities at the point it calls the booking tool: what proves the user, what proves the agent, and what token actually reaches the tool (and with what scope and TTL)? Mark where token exchange happens, which step is a human-in-the-loop gate (hint: the spend), and what gets written to the checkpoint right before that gate so a pod restart resumes correctly. Then state the one platform check that stops the booking even if the model is convinced it should proceed.

Where this points next

This lesson deliberately stayed on the platform side of the boundary — identity, tools, state, gates. The orchestration side — how the agent loop, routing, tool selection, and multi-agent coordination are actually designed — is the subject of a whole separate track. If you want the application-level depth behind the patterns named here, the Agentic Systems track covers the agent design patterns in full, and its MCP lesson goes deeper on the protocol than the platform framing above. Read this lesson as "where those patterns land on Kubernetes" and that track as "how to design the patterns themselves."

You now have the four agent surfaces mapped onto Kubernetes primitives: service identity via SPIFFE/SPIRE, least-privilege tool access via token exchange and MCP, externalized checkpointed state, and explicit human gates. Lesson 18 — Capstone: Production GenAI Platform — assembles every layer of the track into one reference platform: GPU scheduling and serving from Parts II-III, autoscaling and routing, RAG from lesson 16, and the agent identity/state machinery from this lesson, wired together with the observability and policy that make it operable. Treat the capstone as the place where these isolated patterns become a single system you could put on call for.

Takeaway
An agent is a workload with authority, and authority is a platform problem, not a framework detail. Keep three identities separate — the user, the agent's own service identity (SPIFFE/SPIRE-issued SVID), and the delegated tool authority for one call — and combine them only at the moment of a tool call. Never pass the user's full token through to tools (the confused-deputy leak); use token exchange to downscope to a least-privilege, short-lived, single-tool token, so a compromise's blast radius shrinks from "the whole account for an hour" to "one action in the next minute". Bind tools through MCP or an explicit gateway so capabilities are schema-bound, discoverable, and policed. Persist long-running state to an external store with checkpoints so a pod restart resumes instead of restarting, make human-in-the-loop gates explicit workflow states at irreversible/costly actions, and give A2A calls identity, scoped tokens, and trace context so failures can be reconstructed. Above all: the model recommends, the platform decides.

Interview prompts