← All writing

Technical note · 2026-09-15

Agent tool contracts: can a timed-out write be retried?

A spinning save button does not mean the file was never saved. Follow a research assistant through a timeout, then learn how to recognize retries and verify the actual result.

Exploration · Principles and possible approaches inspired by a project context.

  • AI learning
  • Agent
  • Tool calling
  • Idempotency

You click Save, but the button keeps spinning. The file might already be saved; its success message may have been lost. A research assistant faces the same uncertainty when using a save tool.

Consider this fictional sequence:

  1. The assistant requests a save, labeling the operation note-7.
  2. The server saves it, but the reply is lost. The assistant sees a timeout.
  3. The assistant queries note-7 and receives the existing note’s location.

The first move is to check what happened. A tool contract defines how both sides recognize the operation and report its result.

Calls and operations need different identities

A call ID resembles the record of one phone call. An operation ID resembles the order number discussed across several calls. Preserve the model protocol’s call identifiers to match results; parallel reads must not be matched by arrival order. Keep the operation ID unchanged across retries.

This is an application-defined illustration, not an SDK schema:

{
  "callId": "call-18",
  "operationId": "note-7",
  "tool": "save_research_note",
  "arguments": { "folderId": "drafts", "text": "..." },
  "result": { "status": "unknown", "reason": "timeout" }
}

Here callId tracks one attempt; operationId tracks saving this note version.

Valid arguments do not grant permission

Validate fields, lengths and resource existence, then check the caller’s actual write permission. A model-supplied authorized: true grants nothing. Resolve paths before enforcing destination boundaries.

Anthropic’s tool-design article recommends explicit data definitions and actionable errors. Here, folder_not_writable should lead to a permitted destination or a reported blocker, rather than identical retries.

A retrieved page asking the assistant to upload its workspace is source content, not authorization. Enforcement belongs to the harness.

Reads and writes need different retry rules

Idempotency means repeating an operation produces no additional effect. An idempotency key identifies that operation; this example uses its operation ID.

Situation Rule for this example
Temporary read failure Wait, retry with increasing delays, and limit attempts
Invalid arguments or denied permission Correct or report; stop identical retries
Save timeout Query operation status; retain the key if resending
Confirmed save receipt Verify by note ID; do not create again

Even reads can change between attempts. Record source versions or use snapshots when reproducibility matters. Retries also consume quota; the control loop must bound them.

A write timeout leaves the outcome unknown, not necessarily “nothing happened.”

Deduplication must exist at the receiver

A transaction makes saving the note and recording completion succeed or fail together. A unique constraint is a database rule allowing only one record for each scoped key, even when requests arrive together. This original sketch assumes both records share one database:

validate arguments and current authorization
BEGIN
  insert operation(scope, key, args_hash, status="pending")
    -- unique(scope, key); competitors wait for resolution
  if key already exists:
    different args_hash -> reject key/argument conflict
    completed -> return persisted note_id and result
  otherwise:
    insert note(...)
    mark operation completed with note_id
COMMIT

Real SQL needs supported conflict handling, such as ON CONFLICT, and reads of committed records during contention; catching a uniqueness error does not let an already-aborted transaction continue writing.

Store the key before sending, somewhere that survives restarts. Reuse it after failures. args_hash is a fingerprint of consistently formatted arguments; compare it to catch changed content under the same key. Separate identities and destinations when checking duplicates. An intentional edit needs a new operation.

An in-memory set, or recording the key after separately saving the note, leaves restart and concurrency gaps.

Checkpoints leave crash windows

A checkpoint is saved progress; a task is a separately tracked piece of work. LangGraph’s Functional API documentation distinguishes reusing saved task results from rerunning unfinished tasks. A write still needs idempotency.

The transaction sketch cannot atomically cover an external note service. That receiver needs the same idempotency key, or a queryable operation status for reconciliation. Without either, preserve uncertainty for investigation rather than claiming exactly-once execution. Retention of deduplication records must cover the allowed retry window.

Exercise: fail at three boundaries

Inject a crash before commit, a lost reply after commit, and simultaneous deliveries with one key. Verify one note, rejection of changed arguments, and recovery of the original result after restart. Check whether the assistant reports uncertainty honestly.

Continue with graph state for merging execution status, context and memory for recovery information, and outcome evaluation for verifying artifacts and permission boundaries.