feat: an A2A facade for the n8n agents, and Approvisionnement's first workflow
The adapter is written once and parameterised, so both n8n agents get the same A2A surface and differ only in the workflow behind the webhook. It owns the protocol and nothing else: no Keycloak client, no credentials, and the end user's token forwarded in a header so a workflow that logs its input does not log the token with it. escalate: true fails the task rather than completing it, because an autonomous agent meeting a rule that needs approval must hand it back, not decide. Serving this from the gateway was the repo's stated preference and does not reach: A2A returns a Task with a lifecycle, and a proxy cannot synthesise that from a webhook's single reply. The check_availability workflow does the token exchange itself, using the agent's client secret from the pod environment so the committed JSON holds no secret, then talks MCP in two calls — initialize to obtain the session id from a response header, then tools/call. It reads the product code with a regex rather than a model: this agent's critical path is rules, and a question naming no product escalates instead of being guessed at. MCP rather than the REST route, which would have been one HTTP call instead of two: the per-tool ACL by consumer group lives on the MCP route only. Going through REST would give an autonomous agent broader access than a conversational one, which is exactly backwards. Status: the adapter is tested (13 tests) and the identity chain is verified by hand, but the workflow fails at its token exchange and is not yet working — see the note in workflows/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# Workflows
|
||||
|
||||
Exported as JSON and committed. The n8n instance is not the source of truth.
|
||||
|
||||
## Importing
|
||||
|
||||
n8n's REST API, with an owner session cookie. The activation endpoint is
|
||||
separate from the update one and requires the current `versionId`:
|
||||
|
||||
```
|
||||
POST /rest/workflows # import
|
||||
POST /rest/workflows/<id>/activate # {"versionId": "<current>"}
|
||||
```
|
||||
|
||||
`PATCH /rest/workflows/<id>` with `{"active": true}` returns 200 and leaves the
|
||||
workflow inactive, silently. Use the activate endpoint.
|
||||
|
||||
## approvisionnement-check-availability
|
||||
|
||||
**Not working yet.** The `Exchange token` node fails, and what it fails with is
|
||||
worth recording:
|
||||
|
||||
```
|
||||
Keycloak 500, an HTML login page as the body
|
||||
NullPointerException: Cannot invoke UserSessionModel.getAuthenticatedClientSessionByClient
|
||||
because "userSession" is null
|
||||
at V1TokenExchangeProvider.exchangeClientToOIDCClient
|
||||
```
|
||||
|
||||
Two things stand out. Keycloak answers a malformed token exchange with a **500
|
||||
and a rendered login page**, which tells a client nothing. And it took the **V1**
|
||||
exchange path, not the Standard one every other agent in this estate uses
|
||||
successfully.
|
||||
|
||||
What has been eliminated, each by hand against the same running Keycloak:
|
||||
|
||||
- the client and its secret — Keycloak logs
|
||||
`kc.clientId: classic-models-agent-approvisionnement`, so it authenticated,
|
||||
which also proves `$env` reaches the node
|
||||
- the subject token — the *same* token replayed with curl exchanges fine (200)
|
||||
- omitting `subject_token_type` — still 200
|
||||
- omitting `openid` from the scope — still 200
|
||||
- using `audience=` instead of a scope — a clean 400, not this
|
||||
|
||||
So n8n is sending something the reproduction attempts are not. The next step is
|
||||
to capture the request as the node actually emits it: point the node at an
|
||||
in-cluster echo service and read the body. That was set up and the run did not
|
||||
reach it, which is itself unexplained and where this stopped.
|
||||
Reference in New Issue
Block a user