feat: enable catalog writes, with the conversation history they require

The gateway already authorised writes for a product_manager — ai-mcp-proxy
filters tool discovery by consumer group, so create-product is simply absent
from a read-only user's turn. Only the system prompt was holding the agent
back, and it now describes how to write instead: restate the record, ask for
confirmation, never invent a missing field, report the identifier, and stop on
a refusal rather than working around it.

That flow cannot terminate without history. Each turn built a fresh Agent with
no message_history, so the user's "yes" arrived at an agent that had no idea
what it was agreeing to. History is now kept per A2A contextId in a bounded
in-memory map — enough for confirm-then-act, deliberately not durable, and
documented as needing sticky routing beyond one replica.

Verified end to end: pm-user creates S99_TEST1 across two turns and the row
appears in MySQL; ro-user asking for the same thing is told no write tool
exists, because for that token none does. Both test rows removed afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-28 17:39:51 +02:00
parent 0bc06512f5
commit 6ffab873e2
4 changed files with 63 additions and 7 deletions
+22 -1
View File
@@ -58,10 +58,31 @@ quietly.
| | |
|---|---|
| Reads | catalog (products, product lines), sales (read-only, to judge whether a product sells) |
| Writes | **none yet** — the agent says so when asked |
| Writes | catalog: `create-product`, and only after the user confirms the exact record |
| Calls | nothing yet. `Approvisionnement.check_availability` comes with discontinuation decisions |
| Exposes | `describe_catalog` |
Who may write is decided by Kong, not by this agent. `ai-mcp-proxy` carries a
per-tool ACL — `create-product` allows the `admin` and `product_manager`
consumer groups — and the group comes from `realm_access.roles` in the end
user's own token. The filtering happens at **discovery**, not just at call time:
```
pm-user catalog ['create-product', 'get-product', 'list-products']
ro-user catalog ['get-product', 'list-products']
```
So a read-only user's turn has no write tool in it at all, and no prompt
injection can conjure one. The agent holds no standing write access of its own;
the system prompt's confirm-before-writing rule is a courtesy on top of that,
not the control.
Writing needs conversation history — the agent restates the record, the user
says yes, and "yes" only means something if the previous turn is still there.
History is kept in memory, keyed by A2A `contextId`, bounded, and never
persisted. More than one replica therefore needs sticky routing on `contextId`
or a shared store.
Skills are decision-shaped on purpose. An agent exposing a generic `query` has
become a data proxy and defeats the per-domain scoping the estate is built on,
so skills get added when another agent has a specific question — not in advance.