2026-10-28·por Sijie Wang#standmeet#architecture#design#events

model-call-retry

The model call gets exactly one retry: the httpx transport retries transient failures at most twice from a 400ms base, with a 10s public-tier cap.

The model call's retry: the one synchronous retry left

Parent: events

As-built record in backend/internal/conversation/inference/http_retry.go, backend/internal/infra/httpx/retry_transport.go, backend/internal/infra/httpx/retry_budget.go, backend/internal/conversation/inference/errors.go.

The model call IS retried inside the request: by the httpx transport only, at most 2 retries. This is not a contradiction of retry-has-one-owner: that node's "sent once" rule bans a second retry loop around a synchronous call; the transport's own retry is the one retry allowed. Do not generalize the calendar example in that node: calendar free/busy/insert/delete run with NoRetry, but the model call runs WITH the transport retry.

What retries

  • At most 2 retries (1 initial send + 2 resends), base delay 400 ms.
  • Retried: transport errors except the caller's own context cancel/timeout; responses with status 429 or 5xx.
  • Never retried: once response headers arrive and streaming starts (already-streamed tokens are never re-read); the call's own cancellation or timeout.
  • Wait before each retry: the larger of the own exponential backoff (400 ms, then 800 ms) and the provider's Retry-After header. Retrying earlier than the provider asked makes a ban worse, so the header always wins when larger.
  • Cannot afford the wait: if the wait exceeds the turn's remaining budget, the transport hands the response back at once instead of sleeping the budget away.

Public tier cap

  • Public turns cap total retry waiting at 10 s; past it the turn fails fast with "The AI is busy right now — give it a minute and ask again." Coded / BYOAI turns keep waiting as before.
  • Why: the public tier runs on the owner's free provider; on 2026-09-25 every step came back 429 with a 31–43 s retry-after, and the visitor sat in silence for minutes.
  • Rate-limited means the provider's 429 after retries, or a retry-after longer than the turn will wait; both map to the same busy message.

Visible while retrying

Every backoff logs "llm transient failure — retrying" with attempt, max, status, wait_ms, and wait_from (backoff vs retry-after); the frontend throbber shows "retrying". wait_from is what proves the wait respected the provider rather than happening to be long enough.

Not this mechanism

The write path's 2 s wait + receipt (async-response-contract) is a different path: it covers writes landing in the search index, not the model call. The two must not be mixed: a model-call timeout has no receipt and no index_job_id.

sobre esta entrada

Una de las entradas de wiki de sijie. La IA en este sitio está anclada en el mismo corpus y responde en la voz de sijie, con citas que vuelven a entradas como esta — responder le cuesta dinero a sijie, así que espera detrás de un código: ingrese código de acceso →

model-call-retry — Sijie Wang