0024 — Authenticated Query-Embedding Egress for Permission-Aware Retrieval¶
Status: Accepted
Approval: the maintainer's ordered #333 delivery decision made the gateway and network boundary an executable agent-owned slice after this ADR merged; the implementation records that decision here.
Context¶
ADR 0017 makes knowledge-retrieval the single chunk-row ACL enforcement point (§37) and deliberately minimizes its reachable surface: its §49 states the retrieval namespace is default-deny and "the service may reach only DNS, its scoped knowledge database, and separately declared observability sinks." That minimization is load-bearing — the wedge's whole point is that the enforcement service cannot exfiltrate.
Two facts now collide with §49 as written:
- The tool contract requires server-side query embedding. #333 exposes one read-only MCP tool,
search_knowledge, taking a bounded natural-language query; the ACL/classificationWHEREpredicate runs before vector distance ranking. The pgvector store holds fixedvector(1024)bge-m3 embeddings (#331), so the query must be turned into a 1024-dim vector to rank. Accepting a precomputed vector as a tool argument is explicitly out of contract (authorization operands and ranking operands are never caller-supplied), so the embedding must happen server-side, insideknowledge-retrieval. - A sovereign embeddings runtime now exists behind agentgateway. #340 (merged) serves
BAAI/bge-m3atknowledge-embeddings.models.svc.cluster.local:8000reachable only through the agentgateway embeddings listener, credential-free and metered. Ingestion (#332) already embeds through that listener via an authenticatedknowledge-ingestionAPI-key workload (infra/knowledge/base/embeddings-route.yaml).
But §49 permits knowledge-retrieval no path to that listener. There is therefore no legal way to embed the query: making the vector a tool argument, adding an unreviewed lexical fallback, or silently opening an embeddings egress edge would each contradict the accepted tool, schema, NetworkPolicy, or ADR contract. #333 (and its downstream #336) is blocked on this gap, not on runtime availability.
Options considered¶
- Authenticated retrieval → agentgateway embeddings egress edge (recommended). Amend §49 to add exactly one egress destination for
knowledge-retrieval: the agentgateway embeddings listener, reached with a dedicatedknowledge-retrievalAPI-key workload distinct fromknowledge-ingestion. This mirrors the already-accepted ingestion pattern, keeps the credential-free chokepoint and token metering intact, and adds no direct model or database credential to the retrieval pod. - Caller/gateway pre-embeds the query and passes the vector. Rejected. It makes the ranking operand a tool argument (out of contract), moves an untrusted 1024-float blob across the ext-auth boundary, and lets a caller substitute an arbitrary vector to probe the store's geometry independent of its text — expanding, not shrinking, the trust surface.
- Lexical/keyword DB prefilter instead of vector search. Rejected. #331 defines a vector-only store with no reviewed lexical index; adding a full-text path is a separate, unreviewed retrieval-quality and injection surface and does not deliver the intended semantic grounding.
Decision¶
- Amend ADR 0017 §49 to read: the retrieval service may reach DNS, its scoped knowledge database, separately declared observability sinks, and the agentgateway embeddings listener for query embedding only. No other egress is added; kagent and Agent pods still have no direct service or database path.
knowledge-retrievalembeds the query through the agentgateway embeddings listener using a dedicatedknowledge-retrievalAPI-key workload (its own SOPS Secret, distinct fromknowledge-ingestion), authorized by a CEL policy that admits onlyapiKey.workload == "knowledge-retrieval"onPOST /v1/embeddings(andPOST /tokenizeif a token preflight is used). The retrieval consumer route + authorization remain #333's owned scope, exactly as ingestion's route/auth are #332's; #340 continues to own only the runtime, catalog, and proxy-to-model NetworkPolicy edge.- The egress NetworkPolicy admits the retrieval pod to the agentgateway proxy on the embeddings-listener port only — never to the knowledge database of another tenant, another agentgateway listener, kagent, or the public internet. Bound the embedding request bytes and rate exactly as ingestion bounds its listener.
- Everything else in ADR 0017 is unchanged: the gateway-projected identity, the parameterized ACL/classification
WHEREpredicate before ranking, statelessness across delegations (§46), the bridge poll/cancel and public-timeout rules (§47), the single-carrier MCP route (§48), and thepublic/approved_non_publicclassification ceiling (§50).
Consequences¶
- #333 becomes implementable without contradicting any accepted contract; the wedge's security review can proceed on a coherent design.
- The added egress surface is narrow and non-exfiltrating. The new edge reaches only the embeddings listener, which accepts text and returns vectors; it carries no corpus content and no ACL metadata. The ACL/classification prefilter still runs inside
knowledge-retrievalon the database read, which is upstream of and independent from this egress. A compromised retrieval pod could embed arbitrary attacker text (already possible for any caller of the tool) but gains no new path to read or exfiltrate ACL-restricted chunks; the classification ceiling and the fail-closed carrier validation are unchanged. - Metering and the chokepoint invariant hold (D7/D16): the query embedding is one more metered call through agentgateway; no agent or service holds a model credential.
- Cost: every retrieval is now at least one embedding call (bounded query bytes, rate-limited per the retrieval workload). This is inherent to semantic retrieval and is capped by the same per-workload limits ingestion uses.
- If the maintainer rejects this amendment, #333 stays blocked and an alternative (e.g. a signed capability that lets a trusted upstream component embed and hand the retrieval service a bound, non-forgeable query vector) must be designed instead — a larger change than Option 1.
Acceptance record¶
The 2026-07-28 delivery plan for #333 explicitly orders the gateway, kagent, and default-deny network boundary as slice 4 and restores the issue to autonomous execution. The implementation must retain this ADR's separate workload credential, exact listener/path, bounded request, and no-direct-model edge.