Last reviewed: 2026-08-04
Direct answer
A CometAPI response containing a tool call is an instruction for your application, not proof that an action has already run. The model selects a tool and supplies structured arguments; your executor decides whether to edit a file, start a build, open an issue, or change another system. CometAPI’s function-calling guide makes this boundary explicit: application code performs the actual function.
That boundary is where CometAPI tool call idempotency belongs. Before a write-capable tool runs, the orchestrator should assign a stable operation ID, validate and canonicalize the arguments, calculate an argument digest, and reserve an execution record. A retry with the same operation ID and the same digest should receive the stored outcome instead of running the tool again. The same ID with different arguments should be rejected as a conflict. A request that finds an unresolved pending record should enter reconciliation rather than immediately repeating the effect.
Do not treat a timeout as evidence that the tool failed. The action may have completed while its response was lost. A retry can originate in the client, a worker restart, a job runner, or an operator; this design does not assume that CometAPI itself performs the retry. The goal is to make every path converge on one recorded intent and one reusable outcome.
Who this is for
This pattern is for teams whose coding agents can create or modify state. Typical tools include file writers, patch applicators, branch or pull-request creators, CI dispatchers, ticketing integrations, deployment controls, database migration runners, and notification senders. Duplicate execution can leave repeated comments, overlapping builds, conflicting patches, or two external resources created from one approved action.
It is especially relevant when an agent runs in a queue, spans several services, or continues after a network timeout. Before adding retries, also set explicit timeout rules for long agent runs so the caller distinguishes a bounded wait from a confirmed failure.
Purely read-only tools usually have a smaller side-effect risk, although duplicate calls can still waste capacity or return inconsistent snapshots. The strongest controls should go first around tools that write, publish, trigger, delete, deploy, or notify.
Key takeaways
- Give each approved action a caller-owned operation ID. Do not rely only on the argument hash, because two intentionally separate actions can have identical arguments.
- Store an argument digest beside the operation ID. The digest detects accidental reuse of an old ID for a changed request.
- Reserve the operation before executing the effect. Recording the ID only after the tool finishes leaves a crash window in which a retry can run the action twice.
- Replay the original result for completed duplicates. Returning a different response such as
already existscan force the caller down a different path even when the original action succeeded. - Treat
pendingas an uncertainty state, not permission to execute again. Reconcile with the tool target, downstream service, or durable job record. - Give parallel tool calls separate operation IDs. A shared ID would collapse distinct actions into one record.
- Propagate the operation ID to downstream services that support their own idempotency mechanism. A local record cannot by itself close a failure gap in an external system.
A minimal durable record can look like this:
{
"operation_id": "run-42-step-3",
"tool_name": "apply_patch",
"arguments_digest": "hash-v1",
"state": "succeeded",
"result_ref": "result-42",
"replayed": false
}
A concrete happy-path workflow is:
- Receive the model’s proposed tool name and arguments.
- Check that the tool is allowed for this run and that any required human approval is present.
- Validate the arguments against the tool schema, normalize them, and compute a digest from the normalized form.
- Create a stable operation ID for this approved action and atomically reserve a record with state
pending. - Execute the tool once, passing the operation ID downstream where possible.
- Store the terminal status and a sanitized result that the orchestrator can replay.
- If the caller retries, compare the digest and return the stored result with
replayed: true.
The error path needs equally explicit behavior:
- If schema or permission validation fails before reservation, do not execute the tool and do not create a misleading success record.
- If the operation ID already exists with a different digest, stop with a conflict. Do not guess which request was intended.
- If the record is
pending, check whether the downstream effect exists or whether the original worker still owns a valid execution lease. - If the effect completed but outcome storage failed, reconstruct the result from the downstream system or an outbox record. Do not blindly execute again.
- If the downstream operation is known not to have started, transition the record through an explicit retry decision before another attempt.
- If the outcome remains uncertain, escalate for reconciliation rather than converting uncertainty into a duplicate write.
Sanitized logs should include event_time, agent_run_id, operation_id, tool_name, arguments_digest, attempt_number, state_before, state_after, replayed, target_class, outcome_code, duration_ms, and an upstream request ID when one is available. Do not log raw prompts, full tool arguments, patches, environment values, or unfiltered tool results. The purpose of the log is to prove identity and state transitions, not to duplicate sensitive payloads.
Sources checked
- Function Calling in the OpenAI API: What It Actually Does and How to Use It Right explains that the model returns a structured tool request while application code executes the function. It also supports schema validation, careful handling of action tools, and confirmation before consequential writes.
- Making retries safe with idempotent APIs explains why retries become dangerous around side effects, why a caller-provided request identifier is preferable to inferring intent from identical parameters, and why duplicate requests should receive semantically equivalent outcomes.
- Idempotent requests provides a concrete implementation pattern: retain the first executed result, return it for the same identifier, and reject reuse when the request parameters differ.
Together, these sources support the central contract: the application owns tool execution, retry identity must represent caller intent, and repeat requests need deterministic handling rather than another uncontrolled write.
Contract details to verify
Start by extending your existing tool-call contract tests with execution-state cases. Schema validity is necessary, but it does not prove that a valid tool request will run only once.
Verify these details before enabling automatic retries:
- Operation identity ownership: The orchestrator, not the model, should own the durable operation ID. If your client exposes a model-generated tool-call ID, document whether it survives response regeneration and worker restarts. Preserve your own ID across retries.
- Intent scope: Define exactly what one ID represents: one approved patch, one CI dispatch, one issue creation, or one deployment action. Never reuse an ID for a later action merely because the payload looks similar.
- Canonical arguments: Normalize ordering, defaults, paths, and equivalent values before calculating the digest. The same intent should not produce false conflicts because object keys arrived in a different order.
- Atomic reservation: Use a uniqueness constraint or compare-and-set operation so two workers cannot both reserve the same ID. A read followed by an unprotected insert is race-prone.
- State machine: Document allowed transitions such as
pendingtosucceeded,pendingtofailed_terminal, andpendingtoneeds_reconciliation. Avoid a genericfailedstate that hides whether an effect occurred. - Effect boundary: For local transactional work, record the operation and mutation atomically when possible. For external work, pass the same operation ID downstream, use an outbox, or provide a reconciliation query.
- Response replay: Store enough of the original normalized result to give duplicates the same operational meaning. A replay should not ask the model to infer success from a new, ambiguous message.
- Retention window: Keep records longer than the maximum retry and delayed-delivery horizon. If a record expires while an old request can still arrive, the action can run again as if it were new.
- Concurrency: Test simultaneous duplicates, not just sequential retries. Only one worker should acquire execution ownership.
- Human approval: Bind approval to the operation ID and digest. If arguments change after approval, require a new approval instead of treating the old decision as transferable.
Operational metrics should count first executions, replayed outcomes, argument conflicts, unresolved pending records, reconciliation duration, and duplicate attempts by tool. A sudden rise in replay counts can reveal an upstream retry loop even when duplicate effects are successfully suppressed.
Failure modes
A retry receives a new operation ID. The store sees a new action and executes it. Generate and persist the ID before the first attempt, then carry it through worker restarts and manual retries.
The same ID is accepted with changed arguments. An old approval or execution record can be applied to a different target. Compare a canonical digest and fail closed on mismatch.
The effect happens before the reservation is durable. A crash between those steps leaves no record, so the next attempt repeats the action. Reserve first and close the remaining external-effect gap with downstream idempotency or reconciliation.
A timeout is recorded as a definite failure. The original tool may still finish. Mark the result uncertain or pending until the target state is checked.
A pending record never expires or reconciles. Future attempts remain blocked indefinitely. Use execution leases, ownership metadata, and an operator-visible reconciliation queue, but never let lease expiry alone authorize a destructive replay.
Only the status code is replayed. The agent may receive different context than it received after the first execution and choose another action. Store a sanitized, stable result contract as well as the status.
The payload hash is treated as the operation identity. Two legitimate requests to create identical resources can be incorrectly merged. The AWS guidance favors an explicit caller-provided identifier because identical parameters do not always mean duplicate intent.
Records are deleted too early. A delayed request can arrive after retention ends and execute again. Set retention from measured retry, queue, and incident-recovery horizons rather than an arbitrary short interval.
Parallel calls share an identifier. One result can overwrite or suppress another. Assign one ID per logical effect, even when the model proposes several tools in one response.
Logs capture the full arguments. Idempotency debugging becomes a data-exposure problem. Log identifiers, digests, states, and outcome classes while keeping payloads in appropriately protected stores.
FAQ
Is the model’s tool-call ID enough?
Not automatically. It may identify a call within one model response, but your system must verify whether it remains stable when a request is regenerated, resumed, or reconstructed by another worker. A durable orchestrator-owned operation ID provides a contract you control.
Does strict JSON Schema enforcement prevent duplicate execution?
No. Strict schemas help ensure that tool arguments have the expected shape. They do not determine whether a valid request has already run. Schema validation and idempotent execution solve different problems and should both be tested.
Can I use the arguments digest as the operation ID?
Use the digest to detect changed arguments, not as the sole identity. Two intentionally separate actions can have identical inputs. Pair an explicit operation ID with the digest.
What should happen when the first attempt returns an error?
Distinguish validation errors, known terminal failures, and unknown-effect failures. Validation should happen before execution. A known terminal outcome can be stored and replayed. A timeout or transport error after execution may require reconciliation because the effect could have happened.
Are read-only tools safe to retry freely?
They are safer with respect to state changes, but duplicate reads can still consume capacity, hit rate limits, or observe different data. You may use a lighter version of the same identity and caching pattern when consistency matters.
Can a distributed tool workflow guarantee exactly-once execution?
Only when every effect boundary participates in the contract or can be reconciled. In practice, design for repeated delivery while making each effect idempotent, durably recording intent, and detecting uncertain outcomes.
How long should operation records be retained?
Longer than any legitimate retry, queue delay, worker recovery, or manual incident replay. Measure those horizons and document the policy. If downstream systems retain their idempotency records for a different period, account for the shortest effective window.
Reader next step
Inventory every tool that can change state and rank it by duplicate-effect severity. Choose one low-risk write tool, add a stable operation ID and canonical argument digest, and implement three tests: same ID with the same arguments replays the original result; same ID with changed arguments is rejected; and a timeout after the effect enters reconciliation without rerunning the tool.
Once those tests pass, add sanitized state-transition logs and expand the contract to CI dispatches, repository writes, and external integrations. Keep destructive actions behind explicit approval tied to the same operation ID and digest.
Start with CometAPI and apply the idempotency boundary in your own tool executor before enabling automatic retries for write-capable agent actions.