docs: stop naming the middleware
An agent talks to URLs that honour a contract: an MCP endpoint, an OIDC token endpoint, an OpenAI-shaped LLM endpoint. Whatever serves those URLs is replaceable, and naming it in an agent's code or docs implies a coupling that does not exist. Every product name is gone from prose, comments and identifiers, replaced by what the agent actually depends on. Where a behaviour of the current middleware leaks into the contract — the second audience scope, the tool ACL — the text now says what the caller must do and why, not who imposes it. One string survives and cannot be fixed here: the broker client an agent must name in its audience scopes is itself named after the middleware. Closing that means renaming it in Keycloak and following the rename through the gateway configuration and LibreChat. Recorded as a blocker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# classic-models-agent-platform
|
# classic-models-agent-platform
|
||||||
|
|
||||||
Shared ground for the classic-models agent estate: templates, conventions, and
|
Shared ground for the classic-models agent estate: templates, conventions, and
|
||||||
the identity plumbing every agent repeats. Plays the role `kong-k8s-platform`
|
the identity plumbing every agent repeats. What `k8s-platform` is to the
|
||||||
plays for the gateways.
|
gateway estate, this is to the agent estate.
|
||||||
|
|
||||||
**Nothing runs from this repo.** It holds the `${VAR}`-parameterised manifests a
|
**Nothing runs from this repo.** It holds the `${VAR}`-parameterised manifests a
|
||||||
domain's Flux `Kustomization` builds, plus the reference documentation below.
|
domain's Flux `Kustomization` builds, plus the reference documentation below.
|
||||||
@@ -10,8 +10,15 @@ domain's Flux `Kustomization` builds, plus the reference documentation below.
|
|||||||
## The estate this plugs into
|
## The estate this plugs into
|
||||||
|
|
||||||
The agents replace LibreChat as the MCP client and orchestrator. Everything
|
The agents replace LibreChat as the MCP client and orchestrator. Everything
|
||||||
below them already exists and does not change: Kong gateways per domain, MCP
|
below them already exists and does not change: an MCP server per domain, an
|
||||||
servers served by Kong's `ai-mcp-proxy`, Keycloak, and the domain APIs.
|
OpenAI-shaped LLM endpoint, Keycloak, and the domain APIs behind them.
|
||||||
|
|
||||||
|
**Name none of the middleware.** An agent talks to URLs that honour a contract.
|
||||||
|
Whatever serves those URLs today is replaceable — swap it and nothing in an
|
||||||
|
agent changes, as long as the contract holds. So write "the MCP server", "the
|
||||||
|
LLM endpoint", "the gateway"; never a product name, in code, comments, commit
|
||||||
|
messages or docs. Where a behaviour of the current middleware leaks into the
|
||||||
|
contract, say what the caller must do and why, not who imposes it.
|
||||||
|
|
||||||
| Agent | Repo | Framework | Mode |
|
| Agent | Repo | Framework | Mode |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -71,9 +78,10 @@ error. Do not build delegation on RFC 8693 `act` — use `azp`.
|
|||||||
### The exchange
|
### The exchange
|
||||||
|
|
||||||
Do what LibreChat already does — a POST per target audience, roughly twenty
|
Do what LibreChat already does — a POST per target audience, roughly twenty
|
||||||
lines. Do not build a sidecar or move the exchange into Kong; both were
|
lines. Do not build a sidecar, and do not push the exchange down into the
|
||||||
considered and rejected as over-engineering, the second also because it would
|
gateway; both were considered and rejected as over-engineering, the second also
|
||||||
leave the agent holding one broad token instead of several narrow ones.
|
because it would leave the agent holding one broad token instead of several
|
||||||
|
narrow ones.
|
||||||
|
|
||||||
```
|
```
|
||||||
POST https://keycloak.sttlab.local/realms/classic-models/protocol/openid-connect/token
|
POST https://keycloak.sttlab.local/realms/classic-models/protocol/openid-connect/token
|
||||||
@@ -93,7 +101,7 @@ access token five minutes.
|
|||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
MCP servers, one per domain, served by Kong:
|
MCP servers, one per domain:
|
||||||
|
|
||||||
| Domain | URL | Audience scopes to request |
|
| Domain | URL | Audience scopes to request |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -103,22 +111,34 @@ MCP servers, one per domain, served by Kong:
|
|||||||
| sales | `https://sales-dev.sttlab.local/sales-mcp` | `mcp-sales-audience kong-token-exchange-broker-audience` |
|
| sales | `https://sales-dev.sttlab.local/sales-mcp` | `mcp-sales-audience kong-token-exchange-broker-audience` |
|
||||||
| workforce | `https://workforce-dev.sttlab.local/workforce-mcp` | `mcp-workforce-audience kong-token-exchange-broker-audience` |
|
| workforce | `https://workforce-dev.sttlab.local/workforce-mcp` | `mcp-workforce-audience kong-token-exchange-broker-audience` |
|
||||||
|
|
||||||
**Two scopes per server, not one.** The gateway's own `openid-connect` plugin
|
**Two scopes per server, not one.** The MCP endpoint exchanges the token again
|
||||||
exchanges the token again before reaching the backend, so the token must also
|
on its own account before reaching the domain API, so the token must also name
|
||||||
name `kong-token-exchange-broker`. Without it the call fails with a bare `401`
|
the broker client that performs that second exchange. Without it the call fails
|
||||||
and the real cause appears only in the gateway log — *invalid status code
|
with a bare `401` and no usable detail — the real cause (*invalid status code
|
||||||
received from the token endpoint (403)*. Measured on catalog: with both scopes
|
received from the token endpoint (403)*) appears only in the server's own log.
|
||||||
the MCP server answers `200`, with `mcp-catalog-audience` alone it answers
|
Measured on catalog: with both scopes the MCP server answers `200`, with
|
||||||
`401`. LibreChat has always requested both.
|
`mcp-catalog-audience` alone it answers `401`. LibreChat has always requested
|
||||||
|
both.
|
||||||
|
|
||||||
LLM inference goes through the Kong AI gateway, never direct to a provider:
|
This is a property of the contract as it stands, not of any agent's design. The
|
||||||
|
broker client is still named after the middleware that introduced it, which is
|
||||||
|
a leak worth closing — see the blockers.
|
||||||
|
|
||||||
|
LLM inference goes through the estate's LLM endpoint, never direct to a model
|
||||||
|
provider:
|
||||||
|
|
||||||
```
|
```
|
||||||
https://llm-dev.sttlab.local/llm/v1/chat/completions
|
https://llm-dev.sttlab.local/llm/v1/chat/completions
|
||||||
```
|
```
|
||||||
|
|
||||||
It applies rate limiting, prompt guards and semantic guards, and routes to
|
It speaks the OpenAI protocol, applies rate limiting and prompt guards, and
|
||||||
OpenRouter. Sending inference anywhere else bypasses all of them.
|
routes onward to a provider. Sending inference anywhere else bypasses all of
|
||||||
|
that, and loses the audit trail with it.
|
||||||
|
|
||||||
|
Authenticate to it with an exchanged token carrying the `llm-proxy` audience,
|
||||||
|
not a static API key. A key proves which application is calling; the token
|
||||||
|
carries `azp` and `sub`, so inference is attributable to the same (agent, end
|
||||||
|
user) pair as every tool call.
|
||||||
|
|
||||||
These hostnames are lab-only with a self-signed certificate. `/etc/hosts`:
|
These hostnames are lab-only with a self-signed certificate. `/etc/hosts`:
|
||||||
|
|
||||||
@@ -158,8 +178,8 @@ limits:
|
|||||||
audience scopes it may request; `a2a_calls` becomes the A2A authorisations.
|
audience scopes it may request; `a2a_calls` becomes the A2A authorisations.
|
||||||
|
|
||||||
The realm actually deployed is `infra-keycloak/k8s/keycloak/configmap-realm.yaml`
|
The realm actually deployed is `infra-keycloak/k8s/keycloak/configmap-realm.yaml`
|
||||||
— note that `kong-v2/classic-models-realm.yaml` is an older partial copy and is
|
— note that the copy under the gateway estate is older, partial, and **not**
|
||||||
**not** authoritative.
|
authoritative.
|
||||||
|
|
||||||
**The realm file is re-imported with `--override true` on every pod start.** An
|
**The realm file is re-imported with `--override true` on every pod start.** An
|
||||||
`import-realm` initContainer replays it before Keycloak serves traffic, so the
|
`import-realm` initContainer replays it before Keycloak serves traffic, so the
|
||||||
@@ -194,14 +214,13 @@ territory, a manager sees all) resolves through
|
|||||||
identity, so the API cannot filter server-side. The realm's users
|
identity, so the API cannot filter server-side. The realm's users
|
||||||
(`admin-user`, `pm-user`, …) are role placeholders, not real employees.
|
(`admin-user`, `pm-user`, …) are role placeholders, not real employees.
|
||||||
|
|
||||||
**PydanticAI's MCP client cannot open a session against Kong's
|
**The broker client is named after the middleware.** Every agent must request
|
||||||
`ai-mcp-proxy`.** It fails with `Failed to initialize server session` and
|
`kong-token-exchange-broker-audience` alongside the server's own audience, so
|
||||||
swallows the cause. Everything else was eliminated: identity, TLS, DNS and the
|
the one thing an agent cannot avoid naming is the very thing it should not know
|
||||||
token are all fine, and the three handshake steps — `initialize` (200 with a
|
about. Closing it means renaming the client and its scope in Keycloak, and
|
||||||
session id), the SSE `GET` (200), `notifications/initialized` (202) — all
|
following that rename through the gateway configuration, LibreChat and each
|
||||||
succeed with curl using the very token the agent minted. This blocks the
|
agent's config. Until then, treat the string as an opaque identifier owned by
|
||||||
Catalogue agent end to end, and with it the Kong AI gateway path, which has
|
the realm.
|
||||||
therefore never been exercised.
|
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user