Module 2: Idempotency: Making Repeats Not Duplicate
7. Idempotency for a native AI Agent's actions
Description
By the end of this lesson you'll be able to make the tools an AI Agent node calls on its own idempotent, so an agent's loop —which can decide to call the same tool twice, or give slightly different decisions on each run— doesn't execute the same effect (charging, sending, creating) more than once. You're going to understand the two new duplicate sources that show up with agents —the loop's retry and the model's non-determinism— and why an agent's idempotency key has to be derived from the stable input, never from what the model decides.
This matters because agents change the risk equation. Up to now, effects were triggered by a node you placed, in an order you defined. With an AI Agent, it's the model deciding which tool to call and when, and that decision isn't perfectly predictable: it can call a tool it already called, it can retry after an error it doesn't see, it can classify the same order differently twice. Everything you learned in this module still holds, but now there's an autonomous actor pressing the buttons. If those buttons aren't idempotent, the agent is going to duplicate them, and not in a way you can predict by reading the workflow.
Connection to the module: this lesson applies everything before it to a new context. The key (lesson 3), the upsert (lesson 4), the header (lesson 5), and the check-then-act warning (lesson 6) don't change; what changes is who invokes the effect. At Cumbre, order-triage's AI Agent classifies the order and decides to call the tool that charges. You're going to make that tool idempotent so that, no matter how many times the agent invokes it, ORD-2041 gets charged once. Lesson 8's project closes the module with the full flow, agent included.
What changes when the actor is an agent
Let's quickly review what the AI Agent node in n8n is, because the rest of the lesson leans on this.
An AI Agent is a node you connect a language model and a set of tools to. You give it a task —"classify this order and process it"— and the agent, instead of following a fixed order you programmed, reasons about the task and decides, step by step, which tools to use. Each tool is an action the agent can invoke: querying inventory, looking up the customer, charging, sending an email. The agent calls a tool, looks at the result, reasons again, calls another, and so on until it considers the task done. That "reason → call a tool → observe → reason" cycle is the agent loop.
In order-triage, Cumbre's AI Agent receives the order, and among its tools it has one we'll call charge_customer —charges the customer— and another send_confirmation —sends the email—. The agent classifies the order and decides to invoke charge_customer. So far, so good.
The problem is that an agent introduces two new duplicate sources, adding to module 1's three (provider retry, double click, n8n retry):
Fourth source: the agent loop retries the tool. The agent can call charge_customer, not be sure whether it worked —maybe the response was slow, or the agent "forgot" in its reasoning that it already called it— and call it again within the same execution. It isn't an n8n retry or a provider retry; it's the model's own reasoning deciding to invoke twice. An agent is, at bottom, a very capable but sometimes forgetful assistant, and a forgetful assistant you told "charge this order" can, a moment later, second-guess itself and charge it again "just in case."
Fifth source: the model's non-determinism. A language model isn't a deterministic function: give it the same input twice and it can give you slightly different outputs. It classifies ORD-2041 as "high priority" in one run and "medium priority" in another; it extracts the amount as 1780 once and as 1780.00 another time; it drafts the email with different wording. This has a critical consequence for idempotency that we're going to develop: if your idempotency key depended on what the agent decides, it would change between runs, and the idempotency would break —exactly lesson 3's unstable-key mistake, but now the instability is introduced by the model—.
Both sources push toward the same conclusion: the tools an agent can invoke have to be idempotent on their own, so it doesn't matter how many times the agent calls them, and without depending on the agent "remembering" not to repeat. You can't trust the model to behave; you have to make its behavior not matter. It's the same philosophy as the whole module —don't prevent the repetition, make it harmless— applied to an actor that repeats for new reasons.
The golden rule: the key comes from the input, not from the agent's decision
Here's the concept that makes an agent idempotent, and it's the most important thing in the lesson.
An agent action's idempotency key has to be derived from something stable and prior to the agent: the order that came in, its order_id, its content. Never from what the agent produces —its classification, its text, its reasoning—, because that's non-deterministic and changes between runs.
Think of it this way. Order ORD-2041 is a fixed fact: it arrived with that order_id, that customer_id, that amount. That doesn't change no matter how many times the agent processes it. The agent's classification ("high priority") is an opinion that can vary. If you build the charge's idempotency key from the fixed fact (order_id), the key is the same across every run and the idempotency works. If you build it from the variable opinion, the key wobbles and you duplicate.
In practice, this means the idempotency_key you already computed in lesson 3 —from the order, before the agent did anything— is exactly the one the agent's tool should use. You computed it early, right after the webhook, precisely for this: to have it ready, derived from stable input, before the model injects its non-determinism. The agent can classify however it wants; the charge's key is still ORD-2041's.
The rule, in one sentence to tattoo on yourself: an effect's identity is defined by the event that motivated it, not by the decision of the agent that triggered it.
How to make an agent's tool idempotent
An agent's tool, in n8n, is usually a sub-workflow or an action node (an HTTP Request as a tool, a database node, a sub-workflow called as a tool). Making it idempotent is applying, inside the tool, the techniques you already know, taking the key from the stable input. There's no new magic; it's the same three tools as always, encapsulated where the agent can't avoid them.
The conceptual structure is this:
AI Agent
│ decides: "charge ORD-2041's customer"
│ invokes the tool, passing it the order idempotency_key
▼
charge_customer tool (idempotent internally)
│ receives idempotency_key = ORD-2041's stable key
│ makes the POST /charges with the Idempotency-Key header
▼ (the gateway deduplicates atomically)
The three ways to shield the tool, depending on the effect, are the ones you already master:
If the tool calls an API with an idempotency header (like charge_customer against the gateway): the tool sends Idempotency-Key with the order's key. Whether the agent calls it once or five times, the gateway creates a single charge. It's lesson 5, encapsulated in the tool.
If the tool writes to your database: the tool does an ON CONFLICT upsert on the order's key. Whether the agent calls it as many times as it wants, there's a single row. It's lesson 4, encapsulated.
If the tool triggers an effect with no header or upsert possible (an email, a stubborn API): the tool first wins the permission with an atomic upsert on your own table —lesson 6's pattern— and only triggers the effect if it won. It's lesson 6, encapsulated.
In all three cases, the pattern is the same: the idempotency lives inside the tool, not in the agent. The agent is free to invoke; the tool is what guarantees invoking twice doesn't duplicate. Never ask the agent to "remember not to repeat" —it will forget—; make repeating have no consequences.
Worked example: idempotent charge_customer
Let's shield Cumbre's charging tool. Remember the flow: the AI Agent classifies and can invoke charge_customer, maybe more than once.
Step 1 — The key is already ready. Before the agent, lesson 3's Code node already computed ORD-2041's idempotency_key from the order. That key travels with the item and is available to the agent and its tools. Don't recompute it inside the tool, and above all don't derive it from the agent's classification.
Step 2 — The tool uses the key, doesn't invent it. The charge_customer tool (whether a sub-workflow or an HTTP Request used as a tool) makes the charge, passing the idempotency header with the order's key:
POST /charges
Idempotency-Key: {{ ORD-2041's idempotency_key, the one from lesson 3 }}
{ "customer_id": "CUST-118", "amount": 1780, "currency": "MXN" }
Notice what we didn't do: we didn't use the amount the agent "extracted" or the priority it "decided" to build the key. We used the key derived from the original order. If the agent, on one run, read the amount as 1780.00 instead of 1780, the key wouldn't change, because it doesn't depend on the agent's reading.
Step 3 — Test the agent's double call. This is what's new relative to lesson 5. Before, we tested by triggering the workflow twice (the webhook's retry). Now, on top of that, you have to consider the agent calling charge_customer twice within a single execution. To provoke it in a test, you can give the agent an ambiguous instruction that tempts it to retry, or simply observe its real executions; the point is to verify the result.
What to expect: regardless of whether the charge was triggered by two workflow executions or by two agent invocations in the same execution, checking the gateway shows a single charge for ORD-2041. The idempotency header, with the stable key, absorbs every repetition —wherever it comes from—. The agent can be as forgetful as it wants; there's one charge.
Put in a table, so you see the three repetition sources converge on the same safe result:
| How the charge repeated | Without header (fragile) | With header + stable key |
|---|---|---|
| The workflow ran twice (webhook retry) | 2 charges | 1 charge |
| The agent invoked the tool twice in one execution | 2 charges | 1 charge |
| n8n retried the node after a network failure | 2 charges | 1 charge |
The right-hand column is the goal: it doesn't matter why the charge repeated; the stable key in the header collapses it to one. You're not defending against a specific duplicate source, but against all of them at once, because the defense is in the effect's identity, not in the cause of the repetition.
That's the goal: a tool that's safe to invoke N times, so the agent's autonomy —which is its virtue— doesn't turn into a duplicate risk.
About the model: pick a current one, but it's not what makes it idempotent
A necessary note, because it's easy to get confused. The AI Agent node needs a language model connected, and you should pick one that's current as of when you build your workflow. Models get updated and retired frequently; a model that was the standard a year ago may be discontinued today. When you configure the agent, open n8n's model selector and choose one from what's available at that moment —check the current list, don't copy a model name from an old tutorial, because it could be retired and your workflow would fail to find it—.
That said, here's what matters for this lesson: the model choice has nothing to do with idempotency. A newer, bigger, or smarter model doesn't make your charging tool idempotent. The idempotency comes from the stable key and the mechanism inside the tool —the header, the upsert—, not from the model. In fact, it's the other way around: since no model is perfectly deterministic, idempotency has to come from the structure surrounding the agent, not from the agent. Don't expect "a better model" to solve the duplicate; the idempotency engineering you put around it solves it. The model decides what to do; your design guarantees doing it twice doesn't hurt.
Non-determinism beyond the duplicate
An honest nuance is worth stating, because idempotency doesn't cure everything non-determinism brings, and promising it would would mislead you.
Idempotency guarantees the effect doesn't duplicate: a charge, an email, a record. It does that very well. But it doesn't guarantee the agent's decisions are consistent across runs. If the agent classifies ORD-2041 as "high priority" today and "medium priority" tomorrow, the charge's idempotency doesn't fix that inconsistency —they're different things—. The charge will be a single one (good), but the assigned priority can vary (a different problem).
Why mention it here? So you don't walk away thinking "I make the tools idempotent and that's it, the agent is reliable." Idempotency is a necessary but not sufficient layer. The consistency of an agent's decisions is a different topic —it involves how you write its instructions, how you constrain its outputs, how you validate what it produces— and belongs to the ecosystem's AI agents guide, not this one. Here we solve what this module promises: that effects don't duplicate. It's a real, valuable piece of an agent's reliability puzzle, and it's the one you're supposed to master now.
The boundary, stated clearly: this lesson makes the agent's actions safe to repeat. It doesn't make the agent's decisions deterministic. The first is idempotency; the second is agent design, and it lives in a different guide.
There's a design consequence worth taking away from here, because it's going to guide you every time you give an agent a tool: only give an agent tools that are safe to repeat. Think of it as equipping a very capable but impulsive assistant. You don't hand it a tool that charges irreversibly and trust its prudence; you hand it a charging tool that, internally, is already idempotent, so its impulsiveness can't cause harm. When you build an agent's tool catalog, the admission question for every tool is the same as this whole module's: "what happens if the agent invokes it twice?" If the answer is "it duplicates," that tool isn't ready to hand to an agent until you make it idempotent. It's a simple, powerful design filter.
A harder case: when the agent composes several effects
Up to now we looked at one tool at a time. But Cumbre's AI Agent doesn't trigger a single effect: it classifies and then can charge (charge_customer), and send the email (send_confirmation), and write the record to the CRM. Three effects, orchestrated by the agent's reasoning. This adds a layer worth seeing, even though its full solution belongs to module 5.
The key point: each effect needs its own idempotency, with its own key per effect type. A single blindly-shared "order key" isn't enough, because charging and sending the email are different effects that get deduplicated separately. If the agent retries and only repeats the email (not the charge), you want the email to deduplicate without blocking anything about the charge. That's why, in this lesson's exercise 3, the deduplication table uses (idempotency_key, kind): the order's key identifies the event, and kind distinguishes "charge" from "email" from "CRM record." Each effect has its own idempotency entry, all anchored to the same order.
And there's a new danger the agent makes more likely: one effect can succeed and another fail within the same invocation. The agent charges fine, but the email fails. If it retries, does it repeat the charge (which already went out) while retrying the email? This is where per-effect idempotency saves the day: since the charge is idempotent on its own, retrying the whole sequence doesn't charge again —the header absorbs the second attempt— and it does complete the missing email. Each effect advances toward "done once" independently of the others.
The fine-grained coordination of "decide everything first and execute the effects separately, each one idempotent" has a name —the outbox pattern— and it's a central topic of module 5. Don't build it here. But recognize the shape of the problem: an agent that composes several effects is a mini coordination system, and this module's rule applies to each effect separately —stable key from the input, idempotency inside each tool, one deduplication entry per effect type—. Master a single idempotent effect here; module 5 teaches you to coordinate several without them stepping on each other.
Common mistakes
Deriving the idempotency key from the agent's output (conceptual). What happens: someone builds the charge's key from something the agent produced —the priority it classified, the summary it drafted, the amount it extracted from the text— thinking "this way the key reflects what the agent decided." Since the agent is non-deterministic, that key changes between runs, and idempotency breaks: two runs of the same order generate two keys and two charges. Why it happens: it seems natural for the effect's key to come from whoever decides the effect (the agent). But the agent is exactly the unstable part of the system. How to spot it: trace where the key's value comes from; if anywhere in its origin there's a model output (classification, generated text, extraction), it's contaminated by non-determinism. How to fix it: derive the key from the stable input —the order_id, the order's content as it arrived— before the agent acts. The effect's identity is defined by the event, not by the agent's decision.
Trusting the agent "won't call the tool twice" (conceptual). What happens: the agent is given a tool with a non-idempotent effect and it's trusted that its reasoning avoids invoking it extra times —maybe it's even instructed "don't charge twice"—. The agent, being a model, sometimes invokes it twice anyway, and duplicates. Why it happens: the agent gets treated as if it were deterministic code that follows instructions to the letter; it isn't. An instruction in the prompt is a strong suggestion, not a guarantee. How to spot it: if your defense against the duplicate is a sentence in the agent's instructions ("call this only once"), you have no defense. How to fix it: make the tool idempotent internally (stable key + header or upsert), so invoking it twice has no consequences. The idempotency lives in the tool, not in the agent's good behavior. Design assuming the agent is going to repeat.
Expecting a better model to solve the duplicate (conceptual). What happens: duplicate charges show up with an agent, and the reaction is "let's switch to a newer/bigger model, this one makes mistakes." The new model also duplicates, because the problem was never the model. Why it happens: the duplicate gets attributed to the agent's "quality," when it's a structural problem: the tool isn't idempotent. How to spot it: ask yourself whether the duplicate would disappear with an idempotent tool even if the model stayed the same. If yes (and it almost always is), the model isn't the cause. How to fix it: invest the effort in the tools' idempotency, not in switching models. No model is deterministic, so none gives you idempotency; you build that around the agent. Choose a current model for its other qualities, but don't ask it to solve a systems engineering problem.
Exercises
Exercise 1 — Where does the key come from? For each proposed idempotency key for the charge the agent triggers, say whether it's correct or not and why:
(a) The key is derived from the order_id of the order that came in through the webhook.
(b) The key is derived from the priority classification the agent assigned.
(c) The key is a randomUUID() the tool generates every time the agent invokes it.
(d) The key is derived from a hash of customer_id and amount exactly as they came in the original order.
See solution
(a) Correct. order_id is a fixed fact of the input, stable across runs and invocations. It's the ideal natural key. The charge's identity is defined by the order, and order_id identifies it.
(b) Incorrect. The priority classification is an agent output, and the agent is non-deterministic: it can classify "high" on one run and "medium" on another. The key would change between runs and you'd duplicate. Never derive the key from what the agent decides.
(c) Incorrect. randomUUID() generates a new value on every invocation —it's lesson 3's unstable-key mistake, worsened because here the agent can invoke the tool several times in a single execution, and each invocation would have its own UUID—. Zero idempotency.
(d) Correct. It's a synthetic key derived from the stable input (customer_id and amount as they arrived, not as the agent interpreted them). It's the same across every run and invocation. It's valid, though if an order_id exists, that one's preferable for being readable.
Why this works: all four are decided with the golden rule —does the key come from the stable input or from the agent's decision? (a) and (d) come from the input; (b) comes from the agent's decision; (c) comes from randomness. Only the ones anchored in the order's fixed fact are stable.
Exercise 2 — The agent that charges twice. Cumbre's team observes that, in some executions, the agent invokes charge_customer twice within the same workflow execution, and the customer receives two charges. The tool does a POST /charges with no idempotency header. Why does it happen, and what's the fix? Would instructing the agent "charge only once" help?
See solution
Why it happens: the agent's loop decided to invoke charge_customer twice —maybe it wasn't sure whether the first invocation worked, or its reasoning led it to repeat—. Since the tool does a POST /charges with no idempotency header, every invocation creates a new charge. The tool's non-idempotency turns the agent's double invocation into a double charge.
The fix: make the tool idempotent. Add the Idempotency-Key header with ORD-2041's stable key (the one from lesson 3). That way, the first invocation creates the charge and the second, with the same key, retrieves the existing one without creating another. The agent can invoke charge_customer as many times as it wants; there's a single charge.
Would instructing "charge only once" help? Not reliably. An instruction in the prompt is a suggestion, not a guarantee: the agent is a non-deterministic model and will sometimes ignore it. It can reduce the frequency of the double charge, but it doesn't eliminate it, and "almost never duplicates" isn't acceptable when money's involved. The real defense is structural —the idempotent tool—, not behavioral. Design assuming the agent is going to repeat, and make repeating not matter.
Why this works: you separated the cause (non-idempotent tool) from the robust solution (idempotency in the tool) from the illusory solution (asking the agent to behave). The whole module insists on the same thing: don't control the behavior of whoever repeats; neutralize the repetition.
Exercise 3 — Design an idempotent sending tool. Cumbre's agent has a send_confirmation tool that sends the confirmation email via an API that does not offer an idempotency header. The agent sometimes invokes it twice. Design the tool so the customer receives a single email, using what you learned in lessons 6 and 7.
See solution
Since the email API offers no idempotency header and an email is irreversible (it can't be un-sent), the protection has to live on your side, atomically winning the permission to send before sending. The send_confirmation tool, internally, does:
1. Atomic upsert on your own table:
INSERT INTO sent_emails (idempotency_key, kind)
VALUES ('<ORD-2041's key>', 'confirmation')
ON CONFLICT (idempotency_key, kind) DO NOTHING
RETURNING idempotency_key; -- returns something ONLY if it inserted
2. If: did the upsert return a row? (did THIS invocation win the permission?)
- YES → call the email API and send
- NO → another invocation already sent → do nothing
The key (idempotency_key) is derived from ORD-2041 —the stable input—, not from anything the agent produces. The uniqueness constraint on (idempotency_key, kind) is the atomic referee: of two concurrent agent invocations (or two concurrent workflow executions), only one wins the upsert and sends; the other collides with the uniqueness, gets no row back, and doesn't send. The email goes out once.
Design notes:
kinddistinguishes "confirmation email" from other emails for the same order, so sending the confirmation doesn't block a different, legitimate email.- Since the email is irreversible, it's essential to win the permission before sending, not after. Recording the send after the
POST(as in lesson 6's exercise 1) would let the second email slip through. The upsert goes first. - This is exactly the deduplication ledger module 4 formalizes. Here you applied it in miniature, inside an agent tool.
Why this works: you joined lesson 6 (your own atomic referee when there's no header) with lesson 7 (the key comes from the input, the idempotency lives in the tool). For an irreversible effect invoked by an autonomous actor, that combination is the only solid defense.
Summary and next step
In this lesson you brought idempotency into agent territory. You saw that an AI Agent node introduces two new duplicate sources —the agent loop, which can invoke the same tool twice within one execution, and the model's non-determinism, which gives slightly different outputs between runs— on top of the three you already knew. You learned the golden rule that makes an agent idempotent: the key derives from the stable input (the order_id, the order), never from what the agent decides, because the decision is the system's unstable part. And you saw the solution isn't asking the agent to behave —it will forget— but making the tools idempotent internally, encapsulating the header (lesson 5), the upsert (lesson 4), or your own atomic referee (lesson 6) where the agent can't avoid them. You closed with two honesties: that the model choice has nothing to do with idempotency —no model gives it to you for free, you build it around it—, and that idempotency protects effects but doesn't make the agent's decisions consistent, which is a different guide's topic.
Before moving on to lesson 8 you should be able to: name the two duplicate sources an agent adds; explain why the key should never be derived from the model's output; and design an idempotent tool for an effect the agent invokes, with and without an available API header.
You now have every piece of the module: the definition (2), the key (3), the upsert (4), the header (5), the trap to avoid (6), and the application to agents (7). Lesson 8 introduces nothing new: it pulls them together. You're going to take a Cumbre flow that inserts a record and calls an API —the fragile order-triage that opened the module—, assign it its idempotency key, turn the insertion into an upsert, shield the API call, and —most importantly— prove that re-running it twice leaves a single record and a single effect. It's the deliverable you can defend in an interview: the demonstration, not the promise, that your workflow survives the duplicate.
Resources
- AI Agent node — n8n Docs — the
AI Agentnode: how the model and tools connect to it, and how its reasoning loop works. This lesson's foundation. - Tools in n8n AI Agents — n8n Docs — what a tool is for an agent and how it's built (sub-workflow, HTTP Request as a tool, action nodes); the place where you encapsulate the idempotency.
- Chat models — n8n Docs — the model selector you connect to the agent; check the current list there when you build your workflow and avoid model names from old tutorials, which could be retired.
- Idempotent requests — Stripe API reference — the header you encapsulate inside the agent's charging tool; verify the current name and conditions.