601bd6343e
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>
76 lines
3.3 KiB
Markdown
76 lines
3.3 KiB
Markdown
# Catalogue — product management agent
|
|
|
|
**Framework:** PydanticAI + FastA2A · **Mode:** conversational · **Client id:** `classic-models-agent-catalogue`
|
|
|
|
Serves the product manager: create and update products and product lines, set
|
|
prices, decide what to discontinue.
|
|
|
|
## Scopes and MCP servers
|
|
|
|
| | |
|
|
|---|---|
|
|
| MCP | catalog (`mcp-catalog-audience`), sales (`mcp-sales-audience`) |
|
|
| Scopes | `catalog:read`, `catalog:write`, `sales:read` |
|
|
|
|
`sales:read` is there to answer "does this product actually sell?" before a
|
|
pricing or discontinuation decision. Read only — this agent never touches orders.
|
|
|
|
## A2A
|
|
|
|
**Calls** — `Approvisionnement.check_availability`, to know remaining stock and
|
|
open orders before discontinuing a reference.
|
|
|
|
**Exposes** — nothing yet. Add skills only when another agent needs them.
|
|
|
|
## Why PydanticAI here
|
|
|
|
Not arbitrary. This is the agent that **writes structured data under
|
|
constraints** — prices, quantities, references, product line membership. Typed
|
|
validation of the model's output earns its place here in a way it would not on a
|
|
purely conversational agent.
|
|
|
|
## FastA2A, not to_a2a()
|
|
|
|
Use FastA2A directly. `Agent.to_a2a()` still works in PydanticAI 1.x but emits
|
|
a deprecation warning and is removed in v2.
|
|
|
|
FastA2A is framework-agnostic, so whatever is built here is also the natural
|
|
basis for the A2A adapter in front of the n8n agents — see
|
|
`classic-models-agent-flows`.
|
|
|
|
## Before writing code
|
|
|
|
Read `classic-models-agent-platform/CLAUDE.md` first. It holds the identity
|
|
model, the MCP and LLM endpoints, the token-exchange recipe and the known
|
|
blockers. This file only covers what is specific to this agent.
|
|
|
|
## Non-negotiables
|
|
|
|
- **Inference goes through the estate's LLM endpoint**, `https://llm-dev.sttlab.local/llm/v1/chat/completions`.
|
|
Calling a model provider directly bypasses the rate limits and prompt guards.
|
|
- **One MCP client per conversation turn**, built with freshly exchanged tokens.
|
|
Do not hold a long-lived MCP connection with a fixed token.
|
|
- **A2A calls return answers, not data.** Expose narrow decision-shaped skills,
|
|
never a generic `query`.
|
|
- Deliverables in English.
|
|
- **Never name the middleware.** The agent talks to URLs that honour a contract:
|
|
an MCP endpoint, an OIDC token endpoint, an OpenAI-shaped LLM endpoint. What
|
|
serves them is replaceable and the agent must not know or care. Write "the
|
|
MCP server", "the LLM endpoint", "the gateway" — never a product name, in
|
|
code, comments, commits or docs.
|
|
|
|
## Running it against the lab from macOS
|
|
|
|
macOS resolves every `.local` name through mDNS before falling back to
|
|
`/etc/hosts`, and that costs a **flat ~5.0s per hostname** — `dscacheutil -q host
|
|
-a name catalog-dev.sttlab.local` shows it, and so does `curl -w
|
|
%{time_namelookup}`. httpx's default connect timeout is 5s, so the very first
|
|
request to each host lands on the boundary and fails intermittently. fastmcp
|
|
reports that as `Failed to initialize server session` and swallows the cause,
|
|
which is why it read as an MCP protocol problem for a long time. `curl --resolve`
|
|
hides it completely — do not use `--resolve` to "prove" DNS is fine.
|
|
|
|
`Settings.http_timeout` (60s) is the fix, applied to the shared httpx2 client,
|
|
to the client fastmcp builds, and to `MCPToolset(init_timeout=...)`. In the
|
|
cluster these names resolve instantly and the timeout never binds.
|