Files
classic-models-agent-flows/workflows/README.md
T
Stéphane Tailland f49f3ec360 fix(workflow): answer when the product does not exist
MCP does not report a tool failure as a JSON-RPC error. It reports it as a
successful result carrying isError: true, with the reason as text. The node
only checked `error`, so it read the failure as data and went on to JSON.parse
the sentence "HTTP call failed with status 404" as a product. The throw is what
made the webhook answer nothing at all.

Now checks result.isError, distinguishes a missing product from any other tool
failure, and guards every parse of a payload it did not construct. Through the
facade, escalate: true becomes a task in state failed rather than a completed
one.

Also corrects this file: the earlier note said the run completed in success
with every node executing. It did not — that was the wrong execution. n8n's
executions list did not return them newest-first as assumed, and the conclusion
drawn from it sent the search to the wrong place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 13:40:19 +02:00

91 lines
3.5 KiB
Markdown

# Workflows
Exported as JSON and committed. The n8n instance is not the source of truth.
## Deploying a change
**n8n does not re-register an active workflow when you PATCH it.** The running
webhook keeps executing the definition it was activated with, so an edit appears
to have no effect — and the failure it keeps producing is the *old* one, which
is a very good way to spend an afternoon debugging something already fixed.
Deactivate, apply, reactivate, in that order:
```
POST /rest/workflows/<id>/deactivate {"versionId": "<current>"}
PATCH /rest/workflows/<id> {"nodes":…, "connections":…, "versionId": "<current>"}
POST /rest/workflows/<id>/activate {"versionId": "<current>"}
```
`versionId` changes at every step, so re-read it each time. And note
`PATCH {"active": true}` returns 200 and leaves the workflow inactive, silently:
activation has its own endpoint.
## approvisionnement-check-availability
Working. `POST /webhook/check-availability` with the end user's token in
`X-Subject-Token`:
```
"Peut-on promettre S10_1949 ?"
→ {"answer":"Yes — 1952 Alpine Renault 1300 (S10_1949) can be promised:
7305 in stock, above the reorder threshold of 50.","escalate":false}
"Peut-on promettre la Vespa ?"
→ {"answer":"No product reference in the question…","escalate":true}
```
It exchanges the token 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`. No `notifications/initialized` is needed.
MCP rather than the REST route, which would have been one 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.
The product code is read with a regex, not a model. This agent's critical path
is rules; a question naming no product escalates rather than being guessed at.
The regex is loose after the underscore on purpose — a reference that looks
valid but does not exist must reach the catalog and come back as "no such
product", because answering "you named no product" would be false.
### The one that took longest
A product code that does not exist returned an **empty body**, and the reason is
worth keeping.
MCP does not report a tool failure as a JSON-RPC `error`. It reports it as a
perfectly successful **result** carrying `isError: true`, with the reason as
text:
```
data: {"jsonrpc":"2.0","result":{"content":[{"type":"text",
"text":"HTTP call failed with status 404: {\"detail\":\"Not found.\"}"}],
"isError":true},"id":2}
```
A client that only checks `rpc.error` therefore reads a failure as data. This
one went on to `JSON.parse` the sentence "HTTP call failed with status 404" as
if it were a product, threw, and the webhook answered nothing at all.
Two lessons, both now in the node:
- **check `result.isError`, not just `error`** — they mean different things: a
protocol failure versus a tool that ran and failed;
- **guard every `JSON.parse` of a payload you did not construct.** The throw is
what turned a wrong answer into no answer, which for an autonomous agent is
the worse of the two.
Now:
```
"Peut-on promettre S99_NOPE ?"
→ {"answer":"There is no product S99_NOPE in the catalog.","escalate":true}
```
and through the A2A facade, `escalate: true` becomes a task in state **failed**
rather than a completed one — the honest shape for "a human has to look at
this".