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
|
||||
|
||||
Shared ground for the classic-models agent estate: templates, conventions, and
|
||||
the identity plumbing every agent repeats. Plays the role `kong-k8s-platform`
|
||||
plays for the gateways.
|
||||
the identity plumbing every agent repeats. What `k8s-platform` is to the
|
||||
gateway estate, this is to the agent estate.
|
||||
|
||||
**Nothing runs from this repo.** It holds the `${VAR}`-parameterised manifests a
|
||||
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 agents replace LibreChat as the MCP client and orchestrator. Everything
|
||||
below them already exists and does not change: Kong gateways per domain, MCP
|
||||
servers served by Kong's `ai-mcp-proxy`, Keycloak, and the domain APIs.
|
||||
below them already exists and does not change: an MCP server per domain, an
|
||||
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 |
|
||||
|---|---|---|---|
|
||||
@@ -71,9 +78,10 @@ error. Do not build delegation on RFC 8693 `act` — use `azp`.
|
||||
### The exchange
|
||||
|
||||
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
|
||||
considered and rejected as over-engineering, the second also because it would
|
||||
leave the agent holding one broad token instead of several narrow ones.
|
||||
lines. Do not build a sidecar, and do not push the exchange down into the
|
||||
gateway; both were considered and rejected as over-engineering, the second also
|
||||
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
|
||||
@@ -93,7 +101,7 @@ access token five minutes.
|
||||
|
||||
## Endpoints
|
||||
|
||||
MCP servers, one per domain, served by Kong:
|
||||
MCP servers, one per domain:
|
||||
|
||||
| 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` |
|
||||
| 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
|
||||
exchanges the token again before reaching the backend, so the token must also
|
||||
name `kong-token-exchange-broker`. Without it the call fails with a bare `401`
|
||||
and the real cause appears only in the gateway log — *invalid status code
|
||||
received from the token endpoint (403)*. Measured on catalog: with both scopes
|
||||
the MCP server answers `200`, with `mcp-catalog-audience` alone it answers
|
||||
`401`. LibreChat has always requested both.
|
||||
**Two scopes per server, not one.** The MCP endpoint exchanges the token again
|
||||
on its own account before reaching the domain API, so the token must also name
|
||||
the broker client that performs that second exchange. Without it the call fails
|
||||
with a bare `401` and no usable detail — the real cause (*invalid status code
|
||||
received from the token endpoint (403)*) appears only in the server's own log.
|
||||
Measured on catalog: with both scopes the MCP server answers `200`, with
|
||||
`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
|
||||
```
|
||||
|
||||
It applies rate limiting, prompt guards and semantic guards, and routes to
|
||||
OpenRouter. Sending inference anywhere else bypasses all of them.
|
||||
It speaks the OpenAI protocol, applies rate limiting and prompt guards, and
|
||||
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`:
|
||||
|
||||
@@ -158,8 +178,8 @@ limits:
|
||||
audience scopes it may request; `a2a_calls` becomes the A2A authorisations.
|
||||
|
||||
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
|
||||
**not** authoritative.
|
||||
— note that the copy under the gateway estate is older, partial, and **not**
|
||||
authoritative.
|
||||
|
||||
**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
|
||||
@@ -194,14 +214,13 @@ territory, a manager sees all) resolves through
|
||||
identity, so the API cannot filter server-side. The realm's users
|
||||
(`admin-user`, `pm-user`, …) are role placeholders, not real employees.
|
||||
|
||||
**PydanticAI's MCP client cannot open a session against Kong's
|
||||
`ai-mcp-proxy`.** It fails with `Failed to initialize server session` and
|
||||
swallows the cause. Everything else was eliminated: identity, TLS, DNS and the
|
||||
token are all fine, and the three handshake steps — `initialize` (200 with a
|
||||
session id), the SSE `GET` (200), `notifications/initialized` (202) — all
|
||||
succeed with curl using the very token the agent minted. This blocks the
|
||||
Catalogue agent end to end, and with it the Kong AI gateway path, which has
|
||||
therefore never been exercised.
|
||||
**The broker client is named after the middleware.** Every agent must request
|
||||
`kong-token-exchange-broker-audience` alongside the server's own audience, so
|
||||
the one thing an agent cannot avoid naming is the very thing it should not know
|
||||
about. Closing it means renaming the client and its scope in Keycloak, and
|
||||
following that rename through the gateway configuration, LibreChat and each
|
||||
agent's config. Until then, treat the string as an opaque identifier owned by
|
||||
the realm.
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user