If a tool function itself isn't idempotent (e.g., calling an external API to place an order), how can an Agent make its own retry logic still safe without modifying that external API?
If the external API itself doesn't provide idempotency-key support (a third-party service's order-placement endpoint is designed as 'every call produces a new order,' without letting you pass an identifier to avoid duplicates), you can't change the other party's API behavior, but you can add an extra protection layer at your own Agent's architecture level — two common approaches.
Method one: deduplicate requests in your own system before calling the external API. Concretely, every time the Agent prepares to initiate an order operation, it first records 'a unique identifier for this operation' and 'a hash of the operation's content' in its own database; before actually calling the external API, it checks whether there's already a record with completely identical content already processed recently — if so, this might be a duplicate trigger (like the earlier retry-strategy article's mention of some step misjudging and causing the same task to trigger twice), and this call is skipped, no duplicate order placed; if not, the external API is actually called, and this call is logged. The essence of this method is adding a self-controlled idempotency layer in front of a 'non-idempotent external API,' transferring the responsibility for idempotency from the external API to your own system.
Method two: restrict retry timing and scope, only retrying when you can confirm 'the previous call definitely failed.' Most situations causing an Agent to accidentally retry a non-idempotent operation stem from 'not being sure whether the previous call succeeded or not' (a network timeout — you don't know if the request never arrived, or arrived but the response never came back). Only retrying when you can clearly judge 'the previous call definitely failed, the other system did nothing at all' (receiving a clear error response code, rather than an ambiguous timeout scenario) can substantially lower the probability of accidentally triggering a duplicate order — but this method isn't foolproof, since an ambiguous scenario like network timeout inherently can't be 100% ruled out, which is why method one (a self-controlled idempotency layer) is usually more reliable and should be prioritized.
Practical recommendation: if the operation involves actual money or irreversible consequences, prioritize method one (building your own idempotency layer), even requiring extra engineering investment; method two can layer on as a supplementary measure, but shouldn't be the only line of defense, since network environment uncertainty inherently can't be fully eliminated.
When designing an idempotency key, should this identifier be generated by the Agent, or by the user initiating the request? What's the difference between the two approaches?
Each approach fits a different scenario, and the core difference is 'how broad the deduplication scope should be for a logically single operation.'
The Agent generating its own idempotency key: fits the scenario of 'the Agent autonomously decides to retry' — e.g., the Agent judges the previous call failed due to a network timeout and decides to retry; at this point the Agent should carry forward the identifier already generated for 'this same operation' originally, rather than generating a new identifier on every retry (if every retry used a new identifier, the idempotency-key mechanism would completely lose meaning, since the backend would treat every retry as a brand-new, never-processed operation). In this scenario, the identifier is generated 'the first time this operation is proposed,' and no matter how many retries follow, the same identifier is carried forward.
The user (or the user-side application) generating the idempotency key: fits the scenario of 'the same user intent possibly triggered multiple times through different channels' — a user clicks an 'execute' button in an application interface, and due to network delay, unsure whether it was successfully submitted, clicks it again. In this case, if the idempotency key is generated on the user side (the application interface) and attached to this instance of 'user intent,' even if these two clicks trigger two independent Agent calls behind the scenes, as long as both calls carry the same user-side-generated identifier, the backend can still correctly judge this is the same intent and execute only once.
The essential difference between the two: an Agent-generated idempotency key protects against duplication at the 'Agent's internal retry logic' layer; a user-generated idempotency key protects against duplication at the broader, higher-level 'user intent' layer, covering both the user's own accidental duplicate operation and the Agent's internal retries (since a user-side-generated identifier theoretically carries through the entire processing chain, no matter how many times the Agent internally retries).
Practical recommendation: if the system architecture has user intent triggered through an explicit interface action (clicking a button), prioritize generating the identifier on the user side, so it simultaneously covers both 'user's duplicate operation' and 'Agent's internal retry' as duplication sources, giving more complete protection scope; if the entire flow is triggered entirely autonomously by the Agent, with no user real-time operation step (like the earlier automated yield-optimization Agent, whose task is triggered by the Agent's own judgment), then the Agent generating its own identifier at the moment the task is created is sufficient, no need for extra user-side identifier-generation logic.
If an Agent system restarts or crashes, and previously recorded idempotency-key information wasn't persisted, does the protection disappear after restart? How should this be designed to avoid this gap?
This is a genuinely real risk. If idempotency-key records only exist in memory (just a runtime variable, not written to a database or other persistent storage), once the Agent system restarts or crashes, all these records are lost entirely — after restart, if a duplicate request arrives that was 'actually already processed before the crash,' the system would misjudge it as a brand-new request and execute it again, exactly the scenario idempotency keys are meant to prevent — if the idempotency-key record itself isn't reliable enough, the protection mechanism is rendered meaningless.
The correct design principle: the idempotency-key record itself must be written to persistent storage, not just kept in memory. Concretely, before every operation is prepared for execution, first write 'this operation's identifier' and 'the operation content about to be executed' to the database synchronously (not waiting until the operation completes to record it — this is critical, and the reason is explained below); once the operation completes, update the 'execution result' into this same record. This way, even if the system suddenly crashes mid-operation, upon restart it can at least query 'this identifier's operation had already begun executing before, but it's uncertain whether it completed' — rather than having no record at all.
Why write the record 'before execution' rather than 'after execution': if you choose to write the idempotency-key record only after the operation completes, there's a time-window risk — if the Agent crashes between 'the operation has actually finished executing' and 'the record of this execution gets written to the database,' upon restart, the database has absolutely no record of this operation (since the record hadn't yet been written), and the Agent would misjudge 'this operation hasn't been executed yet,' executing it again, causing the earlier-mentioned duplicate-charge type of problem. Conversely, writing a 'about to execute' record first, then actually executing the operation, means even if it crashes midway, the database at least has a record that 'this operation was once triggered' — upon restart, it can first verify 'has this operation actually already succeeded in the external system (on-chain or a third-party API)' before deciding the next step, rather than executing it again with no clue at all.
An additional practical consideration: idempotency-key records can't be retained indefinitely (or storage space would keep growing) — usually a reasonable retention period needs designing (24 hours or 7 days, depending on how long a reasonable retry window is for the business logic), and old records beyond this period can be cleaned up, since beyond that time, the probability of a duplicate request is extremely low, and the marginal value of continued retention is low.
In a multi-agent system, if Sub-agent A already successfully executed an operation using an idempotency key, but due to information loss during Handoff, Sub-agent B initiates another request for the same logical operation, can the idempotency-key mechanism still protect against this cross-Agent duplication?
Yes, it can protect against it, but on the premise that 'the idempotency key itself is handed off along with the task,' rather than existing only within Sub-agent A internally — echoing the principle repeatedly emphasized in the earlier Handoff entry that 'the handoff package must include sufficient metadata.'
The key design for cross-Agent idempotency protection: an idempotency key shouldn't be treated as Sub-agent A's internal implementation detail — it should be treated as a property of 'this logical operation' itself, assigned from the very start of the task, and fully passed along to Sub-agent B through the Handoff package. Concretely, if the task itself already generated a unique task identifier at the very beginning (when the Orchestrator dispatches it), this identifier should carry through the task's entire lifecycle — no matter how many Handoffs occur along the way or how many different Sub-agents process it, the same logical operation should carry forward the same identifier.
What happens if the Handoff process genuinely loses this identifier: as in the scenario you described, Sub-agent B, not having received the original identifier, can only generate a new identifier itself and initiate the request. From the backend's perspective (the system actually responsible for executing the transfer), this is a request carrying a brand-new identifier — the idempotency-key mechanism would judge it as a 'never-before-processed new operation' and execute it normally, causing a duplicate-execution problem. This means the idempotency-key mechanism itself hasn't failed — what failed is 'the completeness of the identifier's transmission during Handoff.' The problem is upstream (Handoff information loss), not downstream (the idempotency-key mechanism itself).
How to guard against this scenario: beyond content like 'handoff package completeness testing' that the testing framework mentioned earlier should cover, an additional protection layer independent of the Handoff mechanism can be added — before actually executing an operation, beyond checking 'has this identifier been processed,' also additionally check 'is this operation's specific content (target address, amount for a transfer) highly similar to some operation already processed recently.' Even with a different identifier, if the operation content is nearly identical and the timing close, it should trigger an additional confirmation flow (human intervention, or at minimum a logged alert), rather than accepting it wholesale and executing directly. This content-level similarity check can serve as a supplementary defense line to the identifier mechanism, still providing a layer of protection when the identifier itself fails for some reason (Handoff information loss).
Core principle: idempotency protection in a multi-agent system can't be designed within a single Sub-agent alone — it must be treated as a consistency requirement spanning the entire task lifecycle, across all Handoff steps. This is the inevitable result of extending the earlier Handoff entry's 'handoff package completeness' principle to this specific idempotency scenario — however rigorously designed a mechanism is at a single step, if it doesn't account for information-transmission completeness during multi-agent collaboration, it can still fail in the gaps between collaboration steps.
An idempotency implementation case for an Agent wallet's transfer operation
An Agent responsible for automatically executing on-chain transfers for users designed idempotency as follows: identifier generation timing — when the Orchestrator dispatches the task, a unique task_id (combining a timestamp and a random string) is immediately generated; this task_id serves as the idempotency key, written to the database with status marked pending, this action happening before the on-chain transfer operation is actually called. Pre-execution check: before the Agent prepares to initiate a transfer, it queries the database for whether a record with the same task_id and a status other than pending already exists — if status is already completed, meaning this transfer already succeeded, it directly returns the original execution result without re-sending the transaction; if status is failed_confirmed (meaning the previous attempt was already confirmed as failed, with no trace whatsoever on-chain), only then does it genuinely re-initiate the transaction. Handling ambiguous in-progress state: if the queried status is pending (meaning the previous attempt was cut off midway, uncertain whether it actually occurred on-chain), the Agent doesn't directly assume 'not executed yet' and re-send the transaction — instead, it first queries on-chain, using the previously recorded transaction hash (if one was logged) to confirm whether this transaction already exists on-chain, only re-initiating once confirming this transaction genuinely doesn't exist on-chain, avoiding a duplicate transfer caused by misjudging an ambiguous state. Cross-Sub-agent transmission: if this transfer task involves multiple Sub-agents collaborating (one judging whether to transfer, another executing it), task_id serves as a required field in the Handoff package, explicitly passed to the execution Sub-agent; upon receiving the task, the execution Sub-agent's first step is using this task_id to check the earlier-mentioned pre-execution state, rather than generating a new identifier itself. A problem found in actual operation: during testing, the engineering team simulated a scenario of 'the system crashing and restarting after the transaction was sent but before receiving on-chain confirmation.' They found that if only checking 'does the database have this record' plainly, the system after restart would get stuck not knowing what to do because the record shows pending (an uncertain state). After adding the step of 'actively querying on-chain status to clarify the ambiguous situation,' the system could correctly judge, in this crash-restart scenario, whether to re-initiate the transaction or directly report it had already succeeded, avoiding a predicament only resolvable through human intervention.
Idempotency design's core tradeoff is protection rigor versus extra engineering and storage cost. The more detailed the idempotency-key record (a full snapshot of operation content, an additional content-level similarity check), the broader the range of duplication scenarios it can guard against, but the more database writes, queries, and storage space required, adding a not-insignificant extra burden for a high-frequency-operation system. Another tradeoff is identifier-generation layer (Agent-internal versus user-side): a user-side-generated identifier has broader protection scope (covering both user's duplicate operation and Agent's internal retry), but requires extra identifier-generation logic design on the user side, adding front-end/back-end coordination complexity; a purely Agent-internally-generated identifier is simpler to implement, but has narrower protection scope, unable to cover user-side duplicate triggering. Recommendation: for core flows involving actual money or irreversible operations, even at extra engineering cost, adopt the most rigorous persistent idempotency-key design, and where feasible, have the identifier start generating from the user side, carrying through the entire processing chain; for non-core, low-risk operations (pure information queries or analysis), idempotency design can be omitted or a simplified version adopted, saving engineering resources for the segments that genuinely need rigorous protection.