← Blog

Engineering · · 4 min read

A tool timed out. Did the change still happen?

Recover an ambiguous write by checking the target operation before retrying, compensating, or declaring failure.

Deixic

The agent submits a change request. The client waits, times out, and offers to try again, while the first request can still be processing in the target system. Retrying creates another request, and declaring failure leaves a real change unreported.

A timeout records what the caller observed, not the final state of the target system. A public MCP Python SDK timeout discussion shows why teams should inspect configured behavior instead of assuming SDK defaults.

Preserve uncertainty as a real state#

Give a write three outcomes: confirmed success, confirmed failure, and unknown. Unknown means the caller lacks evidence to decide. It is not a softer failure, and it must not trigger the same automatic retry policy.

For the protocol revision discussed here, the MCP lifecycle specification recommends request timeouts and cancellation when a response does not arrive. That controls waiting; your application still has to determine what happened in the connected system.

A useful action record includes:

  • The target and the intended change.
  • A logical operation identifier, stable across retries.
  • An attempt identifier, new each time the transport sends a request.
  • The submission time and any provider request identifier.

Follow one ambiguous request#

Take an agent submitting a purchase review request. The provider creates review R-208, but the response is lost and the client sees only a timeout.

  • If the provider supports idempotency for the operation, retry within its documented key lifetime and parameter rules.
  • If it supports lookup by a client reference, query that reference first.
  • If neither exists, inspect the target using the strongest identifiers available, and record the outcome as unknown when no unique match exists.

Searching for the title "Purchase review" is weak evidence, since another person can create a similar request, and a timestamp window narrows candidates without establishing identity. Use an operation-specific correlation field if the provider supports one; not every API accepts arbitrary metadata.

The durable action lifecycle treats submission and result as separate events. Recovery reconnects those events before the agent continues downstream work.

Choose recovery from evidence#

Evidence after timeout Recovery
Provider returns the original operation Continue tracking that operation
Idempotency key confirms the existing result Reuse the result
Provider proves the write was rejected Correct the cause and consider a new attempt
No unique target can be identified Escalate the uncertainty with the attempted change
Write completed with an unwanted effect Consider an explicit compensating action

A compensating action is another write. It needs authorization and does not always reverse the original effect: canceling a review request does not erase a delivered notification, and restoring a field does not erase an external system's audit history.

Bound the recovery window#

Set a reconciliation deadline and a polling schedule that respects provider limits, and record what you checked and when. When the deadline expires, give the operator the unresolved operation and the last observed state.

Do not leave the user in an endless "retrying" state. Say: "The request was submitted, but its final result could not be confirmed. The target and reference are attached; another submission could duplicate it." Operators can act on that.

When a human resolves the uncertainty, attach the provider object identifier and the evidence used. Later continuation uses that resolution instead of repeating the write because its local attempt still says timeout.

Test the gap between effect and response#

A success test never exercises the dangerous interval. Add a controlled failure after the provider accepts the operation and before the client receives the response, and verify that recovery finds the original object or stops with an unknown outcome.

Keep your current agent's conversation and planning flow, and put the reconciliation rule in the execution adapter, where provider identifiers and retry settings live, and document the provider's lookup and key-lifetime rules beside it so operators can resolve an unknown result without reading source. Deixic's evidence guidance and SDK recovery documentation explain how to inspect routed action state.