En n8n-arbejdsgang, der kalder en model, ser færdig ud, når normalforløbet virker én gang. I produktion opstår problemerne ved den anden levering af den samme webhook, ved en timeout, der udløser et genforsøg, efter at det første kald allerede er lykkedes, og ved et udkast, der sendes automatisk, fordi ingen har ansvaret for godkendelsen.
Det følgende er sikkerhedslaget omkring arbejdsgange med AI: idempotens, regler for genforsøg, menneskelige kontrolpunkter og logning. Det hører sammen med din første AI-agent i n8n og gennemgangsmønstrene i design af menneskelig kontrol i processen.
Hvis du aktiverer genforsøg på en node, der måske allerede har oprettet en CRM-note, sendt en besked eller lagt en e-mail i kø, kan du gentage sideeffekten, når resultatet er ukendt. Betragt enhver ekstern skrivning som en handling, der ikke må gentages, før udbyderens idempotens- eller afstemningsadfærd er dokumenteret.
Hvorfor AI-trin har brug for anden fejlhåndtering
Almindelige HTTP- og modelkald kan fejle med statuskoder, timeout, misdannede svar eller en ukendt lagringstilstand. Trin med modeller tilføjer blandt andet disse fejltyper:
- Timeout ved langsom lokal inferens (lokale OpenAI-kompatible slutpunkter).
- Parsefejl, når modellen returnerer prosa i stedet for JSON.
- Bløde fejl: gyldigt JSON, der er forkert.
- Delvis succes: modellen svarede, men en senere skrivning fra et værktøj fejlede.
Et blindt genforsøg løser nogle timeoutfejl, men forstærker de andre. Adskil genforsøg på transportniveau (sikre, hvis serveren aldrig registrerede arbejdet) fra forretningsmæssige genforsøg (kun sikre med en idempotensnøgle).
n8n giver operatører mulighed for at køre fejlede kørsler igen fra kørselshistorikken (n8n’s dokumentation om kørsler). Funktionen beviser ikke, at en sideeffekt er sikker at gentage; arbejdsgangen har stadig brug for den atomiske reservation, afstemning og outbox-mekanisme, der beskrives nedenfor.
Idempotens starter med en atomisk reservation
Vælg en stabil nøgle så tidligt, som triggeren tillader:
| Trigger | Kandidat til nøgle |
|---|---|
| Webhook fra formular/CRM | lead_id / ticket_id fra afsendersystemet |
| Normaliseret Message-ID | |
| Planlagt kørsel over en kø | (job_id, logical_period) eller rækkens primærnøgle |
| Manuel gentagelse | Den eksisterende nøgle; en egentlig rettelse eller erstatning er en ny forretningshændelse, der eksplicit knyttes til den oprindelige |
Implementér ikke SELECT key efterfulgt af INSERT key, og brug ikke en regnearksrække som lås. To n8n-arbejdsprocesser kan begge observere »mangler« og fortsætte. Brug en unikhedsbetingelse i databasen og én atomisk sætning; PostgreSQL dokumenterer entydighedsbegrænsninger som den mekanisme, der garanterer nøglers unikhed (PostgreSQLs dokumentation om begrænsninger).
Minimal PostgreSQL-form (tilpas typer, opbevaring og migreringer til dit eget system):
CREATE TABLE workflow_runs (
idempotency_key text PRIMARY KEY,
state text NOT NULL CHECK (state IN (
'processing', 'awaiting_human', 'approved',
'completed', 'failed_retryable', 'failed_terminal'
)),
payload_hash text NOT NULL,
lease_owner uuid,
lease_expires_at timestamptz,
version bigint NOT NULL DEFAULT 0,
result jsonb,
updated_at timestamptz NOT NULL DEFAULT now()
);
Generér et tilfældigt lease_owner-UUID for hver n8n-kørsel. Reservér en ny nøgle, eller overtag kun en reservation, der udtrykkeligt må gentages eller er udløbet, i én sætning:
INSERT INTO workflow_runs (
idempotency_key, state, payload_hash, lease_owner, lease_expires_at
)
VALUES ($1, 'processing', $2, $3, now() + interval '5 minutes')
ON CONFLICT (idempotency_key) DO UPDATE
SET lease_owner = EXCLUDED.lease_owner,
lease_expires_at = EXCLUDED.lease_expires_at,
state = 'processing',
version = workflow_runs.version + 1,
updated_at = now()
WHERE workflow_runs.payload_hash = EXCLUDED.payload_hash
AND (workflow_runs.state = 'failed_retryable'
OR (workflow_runs.state = 'processing'
AND workflow_runs.lease_expires_at < now()))
RETURNING idempotency_key, lease_owner, version;
Nul returnerede rækker betyder, at en anden kørsel ejer nøglen, eller at kørslen allerede har nået en tilstand, der ikke må gentages: Hent dens status, og gør ingenting eller returnér det tidligere resultat. Hvis den samme nøgle ankommer med en anden payload_hash, skal du stoppe og undersøge sagen; hvis ændret forretningsinput stiltiende behandles som den samme hændelse, skjules fejl i afsendersystemet.
Reservationen skal være tidsbegrænset og må kun fornyes af sin ejer. Hver tilstandsovergang bruger compare-and-set:
UPDATE workflow_runs
SET state = $4, version = version + 1, updated_at = now()
WHERE idempotency_key = $1
AND lease_owner = $2
AND version = $3
AND lease_expires_at > now()
RETURNING version;
Hvis ingen række returneres, har denne kørsel mistet ejerskabet og må ikke handle. Fastlæg den første reservationsperiode ud fra den målte arbejdstid, forny den før udløb, sæt et loft over den samlede levetid, og udløs en alarm ved gentagne overtagelser. En tidsbegrænset reservation forhindrer efterladt arbejde i at blokere for evigt; den gør ikke en ikke-idempotent ekstern afsendelse sikker.
Webhooklevering og kørsler i arbejdsprocesser sker normalt mindst én gang. En reservation i databasen gør samtidigt ejerskab entydigt. Den skaber ikke levering præcis én gang for e-mail, betaling eller CRM på tværs af en netværksgrænse; det kræver en idempotensnøgle længere nede i kæden eller en outbox-kø og en afsendelsesproces, der kan afstemme et ukendt resultat.
Regler for genforsøg på AI-noder
Brug en kort matrix, og byg den ind i arbejdsgangen i stedet for at overlade den til fælles hukommelse:
| Fejl | Nyt forsøg? | Noter |
|---|---|---|
| HTTP 429 / 503 fra modelserveren | Som regel, når handlingen er sikker at gentage | Respektér Retry-After, hvor den findes; brug eksponentielt stigende pauser med loft og tilfældig spredning, og alarmér ved vedvarende pres |
| Timeout med ukendt resultat | Kun hvis kaldet er en læseoperation eller bruger en nøgle | Foretræk et statusopslag frem for blind gentagelse |
| Ugyldigt JSON fra modellen | Begrænset ny prompt (1-2) | Send derefter videre til et menneske sammen med det rå output |
| Fejl i forretningsvalidering (forkert enum, tomt udkast) | Ingen skjult løkke med genforsøg | Ret prompten eller skemaet, eller eskalér |
| 409-konflikt i CRM’et længere nede | Verificér, før det behandles som succes | Hent eller afstem ressourcen, og bekræft, at den samme idempotensnøgle og den ønskede tilstand vandt |
| 500 fra CRM’et efter usikker skrivning | Undersøg sagen; gensend ikke e-mail automatisk |
Sæt et endeligt loft over antallet af iterationer i agentnoder. En mekanisme med genforsøg omkring en agent, der allerede kører værktøjer i en løkke, kan få både tokenforbruget og antallet af gentagne værktøjskald til at vokse voldsomt.
Fastlæg timeout for lokale slutpunkter ud fra målt latenstid; læg ikke »prøv tre gange à 60s« oven på en synkron kundewebhook.
Menneskelige kontrolpunkter, der blokerer sideeffekter
Et menneskeligt kontrolpunkt er ikke en Slack-besked med »til orientering«. Det er en tilstand, hvor ingen kundesynlig eller uigenkaldelig handling udføres, før der foreligger en udtrykkelig godkendelse.
Tre mønstre, der virker i n8n:
1. Godkend-før-handling
AI-node → validér skema → skriv udkast + nøgle til lageret → opret en engangs-godkendelsesudfordring → kun en autentificeret, ikke-udløbet godkendelsestransaktion kan lægge afsendelsen i kø.
2. Handl-med-fortrydelsesvindue
Læg afsendelsen i kø med forsinkelse og et vindue til at fortryde. Brug det kun, når handlingen er tilstrækkelig reversibel til, at en sen fortrydelse betyder noget.
3. Godkend-ved-undtagelse
Handl kun automatisk i snævre, reversible tilfælde, hvor deterministiske regler for egnethed og kalibreret evalueringsdokumentation lever op til en godkendt tærskel; udtag stikprøver, overvåg dem, og eskalér eller afstå ved usikkerhed. En models selvrapporterede konfidens er ikke en håndhævende kontrol.
Tilpas valget af kontrolpunkt til konsekvensen efter den samme beslutningsmodel som i design af menneskelig kontrol i processen. Kunde-e-mail, refusioner, ændringer i konti eller CRM og almindelige driftsmæssige økonomiske handlinger forbliver godkend-før-handling, indtil målt dokumentation og politik tillader andet. Medicinsk behandling, juridisk rådgivning, reguleret finansiel rådgivning, beslutninger om børns sikkerhed og bygnings- eller konstruktionsmæssige beslutninger kræver en kvalificeret fagperson; automatisering må forberede eller viderestille materiale, men må ikke erstatte den gennemgang.
Eksempel på en tjekliste på godkendelseskortet:
- Idempotensnøgle
- Link til kildeposten
- Modellens output (udkast, mærkat og scorer)
- Eventuelle valideringsfejl
- Godkenderens identitet, der skal logges
- Udløbstidspunkt for den ventende tilstand
Godkendelseslinks fungerer som adgangsoplysninger
Send aldrig https://n8n.example/webhook/approve?id=ticket-42&action=approve. Enhver, der gætter, videresender, scanner eller afspiller den URL igen, kan handle. Generér mindst 256 bits kryptografisk tilfældigt tokenmateriale, send kun det uigennemsigtige token over HTTPS, og gem kun dets SHA-256-hash sammen med:
- kørslens nøgle og den tilladte beslutning;
- den tiltænkte godkender/målgruppe eller SSO-politik;
- et absolut udløbstidspunkt;
consumed_at, beslutningen og godkenderens identitet;- en begrænsning om engangsbrug.
En GET-anmodning bør vise en bekræftelsesside, ikke ændre tilstand. Indsend beslutningen med POST efter autentifikation og med CSRF-beskyttelse. I enkle tilfælde kan aktuelle n8n-noder sætte kørslen på pause og bede om godkendelse; n8n anbefaler selv Wait-noden til mere komplekse godkendelser (n8n’s Gmail-godkendelsesoperation). Kontrollér den faktiske semantik for autentifikation, udløb, videresendelse og revision i netop den node og version, du tager i brug; en knap i en e-mail er ikke automatisk egnet til en betalingsgodkendelse eller juridisk godkendelse.
Opret en godkendelsespost, der er knyttet til den uforanderlige forretningsmæssige idempotensnøgle:
CREATE TABLE approvals (
approval_id uuid PRIMARY KEY,
idempotency_key text NOT NULL REFERENCES workflow_runs(idempotency_key),
token_hash bytea NOT NULL UNIQUE,
allowed_decisions text[] NOT NULL,
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
decision text,
approver_subject text,
created_at timestamptz NOT NULL DEFAULT now()
);
Hash det rå token i applikationen, og send kun digestet som $1. Forbrug det atomisk:
UPDATE approvals
SET consumed_at = now(), decision = $2, approver_subject = $3
WHERE token_hash = $1
AND consumed_at IS NULL
AND expires_at > now()
AND $2 = ANY (allowed_decisions)
RETURNING idempotency_key;
Nul returnerede rækker betyder udløbet, ugyldigt, allerede brugt eller forkert beslutning: send ikke. Kør denne sætning inde i en transaktion, som derefter låser den tilsvarende workflow_runs-række, kontrollerer, at den stadig er awaiting_human, opdaterer den til approved og indsætter den unikke outbox-række. Rul hele transaktionen tilbage, hvis et af trinnene fejler. Ved handlinger med store konsekvenser skal du kræve indlogget SSO plus kontrol af rolle og funktionsadskillelse; det er ikke nok at være i besiddelse af et link i en e-mail.
Lad ikke modellen vælge
auto_reply, og følg derefter valget uden en tærskel, som arbejdsgangen håndhæver. Prompts foreslår; noder håndhæver.
Transaktionel outbox til eksterne effekter
At forbruge godkendelsen, ændre kørslens tilstand og registrere den tilsigtede eksterne effekt bør ske i én databasetransaktion. Send ikke fra inde i godkendelses-webhooken. En minimal outbox-begrænsning:
CREATE TABLE effect_outbox (
effect_id uuid PRIMARY KEY,
idempotency_key text NOT NULL REFERENCES workflow_runs(idempotency_key),
effect_type text NOT NULL,
target text NOT NULL,
payload jsonb NOT NULL,
state text NOT NULL CHECK (state IN ('pending', 'sending', 'completed', 'unknown', 'failed')),
lease_owner uuid,
lease_expires_at timestamptz,
provider_id text,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (idempotency_key, effect_type, target)
);
En arbejdsproces til outbox-køen reserverer ventende rækker med tidsbegrænset ejerskab (PostgreSQLs FOR UPDATE SKIP LOCKED er udviklet til kølignende forbrugere; se dokumentationen for låseklausulen), kalder udbyderen med den samme idempotensnøgle, hvor det understøttes, gemmer udbyderens eksterne id og markerer derefter rækken som færdig med compare-and-set.
Hvis arbejdsprocessen får timeout, efter at udbyderen muligvis har accepteret en ikke-idempotent handling, skal du markere effekten som unknown og afstemme med udbyderen, før du prøver igen. En lokal databasetransaktion kan for eksempel ikke garantere, at en SMTP-meddelelse sendes præcis én gang. Automatisk gensendelse efter et ukendt resultat er netop sådan, en kunde kan modtage samme e-mail flere gange.
Logning, der overlever en hændelse
n8n’s kørselshistorik er en begyndelse, men er ikke i sig selv et revisionsarkiv. Log én struktureret hændelse pr. nøgle for AI-trin:
- Tidsstempel og arbejdsgangens version eller commit-id, hvis du versionsstyrer arbejdsgange
- Idempotensnøgle og triggerkilde
- Redigeret input-hash eller tilladte felter (ikke rå hemmeligheder)
- Godkendt klasse af udbyder eller slutpunkt samt model- og revisionsidentitet; undgå at eksponere interne værter eller legitimationsoplysninger i bredt tilgængelige logdata
- Godkendte, minimerede felter fra modellens output eller en kontrolleret henvisning; lagring af råt output kræver en særskilt beslutning om formål, adgang og opbevaring
- Valideringsresultat
- Gate-beslutning og aktør
- Skrivninger længere nede i kæden med eksterne id’er
- Fejlklasse og antal genforsøg
Gem ikke private chain-of-thought-dumps »til fejlsøgning« i en delt kanal. Gem beslutningsresuméer og værktøjsargumenter, som du ville være villig til at få revideret.
Kørselens logdata indeholder ofte personoplysninger fra sager og e-mails. Fastlæg opbevaring, adgang og maskering, før du slår detaljeret logning til på AI-noder i produktion. Lokale modeller fritager dig ikke for GDPR-lignende ansvarlighed, når du behandler personoplysninger.
Når noget går galt, skal du kunne svare på: Behandlede vi denne nøgle? Sendte vi? Hvem godkendte? Hvilken modelversion skrev udkastet?
Referenceforløb for et kundeemne eller en sag
- Webhooken modtager nyttelasten → validér skemaet (samme type kontrolpunkt som i din første AI-agent i n8n).
- Beregn nøgle og hash af nyttelasten → reservér atomisk en tidsbegrænset
processing-periode. - Kald AI-noden eller agenten med en kontrakt om struktureret output.
- Validér JSON (enum, påkrævede felter, maksimal længde).
- Hvis det stadig er ugyldigt efter begrænset reparation →
failed_terminal+ alarm til et menneske. - Hvis det er gyldigt og handlingen er højrisiko → CAS til
awaiting_human; opret en hashet, udløbende godkendelsesudfordring til engangsbrug. - Ved autentificeret godkendelses-POST → forbrug udfordringen atomisk, opdatér tilstanden, og indsæt den unikke outbox-effekt.
- Afsendelsesprocessen reserverer outbox-rækken, kalder udbyderen med den samme nøgle, hvor det understøttes, gemmer udbyderens id og markerer både effekt og kørsel som færdige med CAS.
- Ved afvisning → markér som terminal med begrundelse; læg ingenting i kø.
- Ved duplikeret levering → returnér det tidligere resultat eller rapportér den aktuelle tilstand; gentag aldrig model- eller afsendelsesstien stiltiende.
Valgfrit: Overlad vurderingstung udarbejdelse til Hermes via API-serveren med bearer-godkendelse, eller vælg bevidst den separate webhook-adapter, når dens hændelsesindgang og konfigurerede levering passer til arbejdsgangen. I begge tilfælde beholder n8n eller forretningssystemet de varige nøgler, kontrolpunkter og forbindelser. Se den illustrative overdragelse fra n8n til Hermes via webhook.
Tvungne gentagelser uden at bryde idempotensen
Operatører kommer til at køre fejlede kørsler igen fra n8n’s brugerflade. Det er fornuftigt, medmindre genkørslen stiltiende opretter endnu en CRM-note, fordi nøglen stadig står som completed efter en delvis succes, eller endnu værre sender samme e-mail igen, fordi nøglen aldrig blev gemt.
Definér en eksplicit protokol for gentagelser:
- Genopretning, der må gentages — kun
failed_retryableeller en udløbetprocessing-reservation kan overtages med den atomiske reservation ovenfor. Den samme forretningsnøgle bevares. - Gentagelse af terminale eller færdige kørsler er forbudt — nøgler i
failed_terminal,awaiting_human,approvedogcompletedreturnerer deres tidligere eller nuværende tilstand og starter ikke forfra. - Bevidst rettelse eller erstatning — opret en ny forretningshændelse med sin egen idempotensnøgle udstedt af afsendersystemet, knyt den til den oprindelige nøgle og det eksterne resultat, notér operatør og begrundelse, og send den gennem en frisk godkendelses- og outbox-sti. Opfind ikke et ad hoc-suffiks, og ændr ikke den oprindelige kørsel på stedet.
Vis protokollen på godkendelseskortet, så operatører på nattevagt ikke skal opfinde politik under pres.
Målepunkter for observerbarhed
Du har ikke brug for en fuld observerbarhedsplatform fra dag ét. Følg ugentligt:
- Andelen af duplikerede webhooks (samme nøgle set to gange)
- Ventetid ved kontrolpunktet (p50 / p95, mærket som dine egne målinger)
- Andelen af valideringsfejl efter AI-noden
- Forholdet mellem automatiske handlinger og menneskeligt godkendte
- Antal gange alle genforsøg blev opbrugt
En pludselig stigning i valideringsfejl giver anledning til at undersøge ændringer i model, prompt, skema, inputfordeling eller integration. En stigning i dubletter giver anledning til at undersøge gentagen levering fra afsendersystemet, fejlede reservationer, genafspilninger eller tvetydige resultater fra udbyderen; målepunktet alene stiller ikke diagnosen.
Nødstopsknap og ejerskab
Håndhæv et nødstop, der som udgangspunkt afviser handlinger ved grænsen for sideeffekter eller i afsendelsesprocessen, ikke kun i arbejdsgangens første node: AI_ACTIONS_ENABLED=false skal forhindre enhver ekstern afsendelse, også når en kørsel genoptages midtvejs eller springer en tidlig gren over. Test den deaktiverede tilstand mod både køede og igangværende effekter, definér, hvad der stadig logges, og udpeg en autoriseret ejer, der kan betjene og kontrollere funktionen.
Definér desuden:
- Hvem der må godkende
- Hvem der må gennemtvinge en gentagelse, og hvordan en erstatningshændelse får en ny nøgle udstedt af systemet opstrøms, som knyttes til originalen uden et selvvalgt suffiks
- Hvad »færdig« betyder for supportens servicemål, mens kontrolpunktet venter
Tjekliste før idriftsættelse
- Idempotensnøgle valgt og gemt før AI-kaldet
- Ti samtidige leveringer af den samme nøgle giver præcis én aktiv reservation
- Genopretning efter udløbet reservation og CAS-afvisning af en forældet ejer er testet
- Regler for genforsøg er dokumenteret for hver fejlklasse
- Godkendelsestokenets hash, udløb, SSO/rolle, POST/CSRF og engangsbrug ved replay er testet
- Det menneskelige kontrolpunkt indsætter en outbox-række; det kan ikke kalde afsendelsesnoden direkte
- En timeout hos udbyderen efter mulig accept går i
unknownog gensender ikke automatisk - Strukturerede logdata indeholder nøgle, validering, godkender og eksterne id’er
- Nødstopsknappen er testet
- Der er fastlagt en opbevaringspolitik for logdata
AI-noder gør sig fortjent til deres plads, når de opfører sig forudsigeligt under fejl. Idempotens forhindrer genforsøg i at give et falsk billede af forløbet. Menneskelige kontrolpunkter forhindrer forkert output i at blive præsenteret som kendsgerninger for kunden. Logning gør begge påstande efterprøvelige.



