Agent Router is the agents' identity gateway, with role and data class encoded in the token audience and an in-pod proxy holding the tokens
Status: Accepted Date: 2026-09-26 Deciders: Smana (Platform Owner) Related Spec: SP1 — Agent runtime & identity
Context
An agent run must call models and read-only MCP tools under its own identity (programme D3), without
ever holding a provider key, and internal data must never reach a SaaS model (OD-13). Envoy Gateway
1.9.2 validates JWTs against a remote JWKS and copies claims into headers, but matches claims
exactly and cannot address the nested kubernetes.io claim. OpenHands reads its LLM key once per
conversation, and under gVisor a rotated projected token never reaches a reader inside the pod (SP1
spike Q2), so each token lives until its run’s deadline (R2, C3).
Decision Drivers
- A token copied out of a sandbox must die with its run, at the run’s deadline (R2)
- “
internalnever reaches Z.ai” must hold by construction, not by a rule evaluated after routing - No provider key in namespace
agents - One harness-neutral localhost contract
Considered Options
Option 1: Agent Router (Envoy AI Gateway) on its own agent-router Gateway, one listener per class
Pros:
- JWT validation,
claimToHeaders, early header removal, MCPRouteoauthand per-tool authorization, and API-key injection are all in the pinned Agent Router 1.1.0 / Envoy Gateway 1.9.2 schemas - The listener rejects the other class’s token before routing; Z.ai routes attach to
publiconly
Cons:
- Offline validation: a copied token is valid until
exp, the run’s deadline (R2) - EG cannot prefix-match
sub: a Kyverno audience reservation and a data-plane CNP admitting onlyagentspods close that gap
Option 2: agentgateway at the agent boundary
Pros:
- CEL authorization might express the
subprefix (UNVERIFIED); an RFC 8693 client
Cons:
- A second gateway product beside the one the platform runs; its distinct OSS feature is unused by autonomous agents (programme D11)
Option 3: One listener, per-route SecurityPolicies
Cons:
- Two routes on one listener both match
x-ai-eg-model, and a route-level policy runs only after the route is chosen: the class boundary would depend on filter order
Option 4: The run’s token passed as the harness’s API key
Cons:
- The harness, which the agent drives through a shell, would hold the token, so one prompt injection exfiltrates it. Tokens are run-long under R2 either way; the proxy keeps them out of the agent’s reach
Decision Outcome
Chosen option: “Agent Router on a dedicated agent-router Gateway, one listener per class”, with
audiences agent-router.<role>.<dataClass> and an in-pod Envoy identity-proxy (credential_injector
fed by file SDS) as the only token holder. A third listener, sts (:8082), fronts octo-sts and accepts
exactly the four octo-sts/<owner>/<repo>/<role> audiences from this cluster’s issuer, on both
clouds. octo-sts’s trust policies can match aws-0’s EKS issuer only by pattern, since it changes on
every rebuild, and that pattern admits any EKS cluster in the region; gcp-0’s GKE issuer is matched
exactly (owner decision, 2026-09-26).
Rationale: It is the only shape where the class boundary and the key boundary are both structural, using controllers the platform already runs.
Consequences
Positive
- Agents and humans use separate Gateways and separate provider keys (C1)
- Every harness gets the same contract:
127.0.0.1:4000(public),:4002(internal),:4001(octo-sts)
Negative
- The harness can still use its credential through localhost; the boundary is what the credential can reach, not whether the agent can call it
- Identity reaching MCP backends is confirmed from source:
x-ar-agent, via the MCPRoute’ssecurityPolicy.oauth.claimToHeaders(C5); SP2 no longer needs the fallback for this path - Tokens live until the run’s deadline, not 600 s (R2): under gVisor kubelet’s rotation raises no inotify, so the file watch never reloads. A Lua filter re-reading the token per request would keep 600 s (a re-read does see the new file) and was not taken: more proxy code for a window that only matters after a sandbox compromise
Neutral
- SP4 owns the model mapping behind each listener and the budget rules on this Gateway
Implementation Notes
infrastructure/base/agent-router/, infrastructure/base/agent-runtime/identity-proxy-configmap.yaml,
Kyverno agent-audience-reservation. Secrets through security/base/agent-secrets/ only.
Re-check trigger (2026-10-01)
Agent Router 1.1.0 speaks MCP 2025-06-18 only. 2025-11-25 support is requested in agent-router#1575, and nothing tracks 2026-07-28; today only version negotiation keeps clients and servers working. agentgateway already supports 2026-07-28. Reopen this decision if either:
- an MCP server or harness on the platform drops 2025-06-18, or
- Agent Router has not added 2025-11-25 support by 2026-12-15.
Option 2’s “UNVERIFIED” sub-prefix claim is now verified from source (agentgateway v1.5.0 registers
CEL startsWith, and its RBAC tests match on jwt.sub). A time-boxed PoC replacing this Gateway
with agentgateway is running on gcp-0; its result decides whether this ADR is superseded. Envoy
Gateway citations above are corrected to 1.9.2, the main pin since 2026-09-30. Evidence: the
ecosystem re-check
and the gap matrix.