Files
classic-models-agent-catalogue/CLAUDE.md
T
Stéphane Tailland aace433d7e refactor: request the neutrally-named token-exchange broker
The broker client was renamed in Keycloak so that the contract an agent has to
honour no longer names the middleware serving it. This is the last consumer to
move; the gateways and LibreChat went first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 07:41:36 +02:00

3.5 KiB

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 — one read-only skill, describe_catalog: what exists in the catalog, how it is priced and positioned, and whether it sells. Skills are decision-shaped, never a generic query — add one when another agent has a specific question, not in advance.

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.