Helpful first: Tool design.
The idea
A timeout limits how long a caller waits for an attempt. An overall deadline limits the whole operation, including retry delays. Neither proves that remote work was cancelled or rolled back. gRPC explicitly makes the server application responsible for stopping work it spawned. Treat a missing response as missing evidence, not evidence that nothing happened.
Where it helps
An engineer can notice a stuck request and stop clicking. An unattended agent needs that stopping rule in executable tool code. Retries can recover transient failures, but they also consume capacity; independent retry loops at multiple layers multiply downstream attempts. Review the runtime, client library, and service policy together.
Teams delegating remote tool use need a reliable stopping rule before leaving agents unattended. This chapter makes existing reliability knowledge actionable through a small runtime policy and a fault-injection exercise.
A release assistant reads CI status before drafting a release summary. In an illustrative policy, the runtime allows two total attempts, up to five seconds each, within a twelve-second tool budget. The first times out; a sampled two-second backoff leaves at most five seconds for the retry, less any runtime overhead. If that also expires, return status_unknown with the attempt history. A tool timeout must not become a claim that the build failed.
What you will be able to check
- Distinguish an attempt timeout from a shared operation deadline in a trace.
- Implement one bounded retry policy for a read-only tool and demonstrate its stop condition.
- Explain why a timed-out write needs reconciliation or documented idempotency before retrying.
What changes in the team
- Today
- An engineer refreshes CI, checks an outage dashboard, and decides whether to try again.
- With an agent
- An agent calls a typed status tool; runtime code owns the timer, retry classification, attempt counter, and terminal result.
- Still your responsibility
- Engineers choose safe operations and limits, inspect failure traces, and decide how to handle unresolved outcomes.
Work through an example
- Define the operation. Read CI status for a specific build ID. Keep that identity and the budget across repeated agent requests belonging to the same operation. Classify the endpoint by its documented semantics: RFC 9110 cautions against automatically retrying non-idempotent requests unless safety or non-application is known. Define an operation as this task’s bounded read, not every future read of the build forever. A later scheduled poll or explicit new task can receive a new budget; a model rephrasing the exhausted request cannot grant one itself.
- Set the deadline once using a monotonic clock. Before each attempt, compute remaining time; cap the attempt timeout by that remainder. Include connection setup, response reading, and sleeps in the budget. Check what your client’s timeout actually covers, especially streaming responses and connection establishment.
- Retry only allowed transient failures. Sleep with a capped backoff and jitter, meaning randomized delay; identical delays can synchronize clients again. Check remaining time after sleeping and before sending. Honour server retry guidance when applicable; stop if waiting would exhaust the budget. A deadline stops caller waiting; propagate cancellation where supported and make spawned work cooperate, without treating that signal as proof of rollback.
- Finish with evidence. On success, return the observed status and build ID. On exhaustion, preserve the unknown outcome, deadline, and attempts in the trace. For a write, use the provider’s documented idempotency contract or reconcile the effect first. Stripe API v1, for example, can replay saved errors and permits pruning keys after at least 24 hours; a key is not a universal, permanent duplicate shield.
The engineering decision
Choose limits from measured dependency latency and the task’s tolerance for waiting. Identify one owner for application retries and inspect lower-layer behaviour: gRPC can transparently retry some failures even without an explicit retry policy. Count actual downstream calls, not only model-visible tool calls. These example numbers are teaching values, not production defaults.
- Resetting the budget when the model rephrases the same tool request bypasses the intended limit.
- Treating every error as transient retries invalid arguments or unchanged credentials without fixing the cause.
- Assuming a timeout stopped server work can cause duplicate writes when a retry follows a lost acknowledgement.
Try it with your team
Make a slow CI read stop predictably
- Stub a read-only CI endpoint with success, delayed response, retryable persistent failure, and invalid-argument modes; log each arrival and completion. Disable hidden client retries for the two-call test, then separately inspect the real client’s retry settings.
- Implement the illustrative two-attempt, twelve-second policy. Inject a clock and a fixed two-second delay for a deterministic test.
- Run all four modes and record operation ID, attempt number, remaining budget, and terminal result.
- Ask for the same logical read after exhaustion; verify the runtime rejects it without another downstream call.
- Let a delayed stub finish after the caller times out; verify the result stays unknown at the caller, while the completion log proves remote work continued. Verify an invalid argument is rejected without a retry.
You have an answer when…
- Success returns a real stub status; persistent failure returns an explicit unknown outcome within the budget plus a measured scheduling tolerance.
- The failure trace shows at most two application attempts, one shared deadline, and no downstream call after closure.
- A non-retryable error causes one downstream call, and a late remote completion does not become an accepted result for the expired attempt.
Read further
Can a model request the same logical operation again and accidentally receive a fresh deadline or attempt allowance?
Original video comparisons