On this page
- Start with a request timeline
- Use a deadline, not only per-attempt timers
- Connect timeout protects the transport setup
- TLS failure is not a timeout
- First-byte timeout measures server responsiveness
- Time to first token is not a network timeout
- Idle-stream timeout is different from overall timeout
- Decide what counts as stream activity
- Do not reset idle timers on arbitrary local work
- Overall request deadlines bound user waiting
- Queue timeout belongs before network execution
- Tool timeouts need their own policy
- Tool timeout does not automatically end the whole agent turn
- Multi-round loops need cumulative limits
- Background provider jobs use a different clock
- App backgrounding is not a provider timeout
- Avoid the “one URLSession timeout” trap
- A timeout should produce a phase-specific error
- Retry policy depends on timeout phase
- Connect timeout
- First-response timeout
- Idle stream after partial output
- Tool timeout
- Background poll timeout
- Add jitter to scheduled retries, not to correctness deadlines
- User cancellation should preempt all timers
- Use monotonic time for elapsed durations
- Persist absolute/declarative deadlines for durable work
- Timeouts should be capability-aware
- Do not dynamically stretch deadlines forever
- Record timeout phase in observability
- A deadline hierarchy
- Test with deterministic clocks
- A timeout checklist
- Where BYOKchat fits
- Further reading
A single timeout such as:
request timeout = 30 seconds
is usually too crude for an AI application.
An AI request can spend time in several distinct phases:
queue
DNS / connection / TLS
provider queue
prompt processing
first token
streaming output
tool execution
additional model rounds
background completion
A healthy reasoning model may legitimately need a long time before the first visible token. A broken stream may stay open forever without producing another event. A tool may hang after the model already completed its call. A background provider job may be expected to run for minutes.
The right design is multiple bounded deadlines with different semantics.
Start with a request timeline
A streaming request can be modeled as:
Different clocks answer different questions:
queue wait: how long before execution began?
connect: could we establish the transport?
first byte: did the server start responding?
first token: did useful generation begin?
idle stream: has an active stream stopped making progress?
overall: has the operation exceeded its total budget?
Use a deadline, not only per-attempt timers
Retries can accidentally reset a timeout forever.
Suppose:
attempt timeout = 30s
retries = unlimited
The user can wait far longer than intended.
Prefer a logical deadline:
logical request starts at T0
overall deadline = T0 + budget
Every retry, fallback, and tool round consumes from the same overall budget unless the product intentionally defines otherwise.
Connect timeout protects the transport setup
The connect phase can include:
DNS resolution
TCP connection
TLS handshake
proxy negotiation
A connect timeout should be short enough to detect unreachable endpoints but not so short that normal mobile-network transitions fail constantly.
Do not confuse it with generation latency.
If the connection succeeds quickly and the model then thinks for 45 seconds, the connect timer should already be finished.
TLS failure is not a timeout
Certificate rejection, hostname mismatch, or trust failure should be classified immediately as a security/transport error.
Do not wait for the generic timeout and show:
The server took too long
when the real issue is:
TLS trust failed
See Certificate Validation for Custom AI Endpoints.
First-byte timeout measures server responsiveness
After the request is sent, the client may wait for HTTP response headers or the first response bytes.
This timer answers:
Did the remote endpoint begin responding at all?
For non-streaming APIs, first byte may arrive near completion.
For streaming APIs, it may arrive before any user-visible token because providers can emit metadata, role, reasoning, or stream-start events first.
Time to first token is not a network timeout
TTFT is a performance metric:
request accepted -> first visible/generated token
It may include:
provider queue
prompt ingestion
cache lookup
reasoning/planning
model startup
A high TTFT does not automatically mean the connection is unhealthy.
Do not use a universal tiny TTFT threshold as a correctness rule.
See Measuring Time to First Token Correctly.
Idle-stream timeout is different from overall timeout
Once useful stream activity begins, a client can monitor the time since the last meaningful event.
Example:
lastEventAt = now
onEvent -> lastEventAt = now
if now - lastEventAt > idleBudget -> stream stalled
This detects a connection that remains technically open but stops making progress.
An overall timeout cannot do this well because a long answer may legitimately exceed the same total duration while continuously producing output.
Decide what counts as stream activity
Possible activity includes:
text delta
reasoning delta
tool argument delta
usage event
provider heartbeat
semantic progress event
A provider heartbeat proves transport activity but may not prove model progress.
Your adapter can distinguish:
transport alive
semantic generation progressing
This is useful when debugging long gaps.
Do not reset idle timers on arbitrary local work
UI rendering, database writes, or animation should not make a dead network stream look alive.
Reset the remote-progress timer only on remote events that the adapter recognizes as activity.
Overall request deadlines bound user waiting
A logical request can contain:
model round 1
tool call
tool execution
model round 2
retry
fallback
Without an overall deadline, individually bounded phases can add up to an unreasonable session.
A deadline budget can be represented as:
interface DeadlineBudget {
startedAt: Instant;
expiresAt: Instant;
remaining(now: Instant): Duration;
}
Every child operation receives a timeout no greater than the remaining budget.
Queue timeout belongs before network execution
If a client limits concurrency, a request can wait in a local queue.
That wait should not disappear from UX or metrics.
Distinguish:
queued too long
from:
provider too slow
A queue timeout can also prevent stale operations from executing after the user’s context has changed.
See How to Build an AI Request Queue.
Tool timeouts need their own policy
A model may call:
search_web
read_file
create_issue
run_database_query
Each tool can have different expected duration and side-effect risk.
A tool timeout should answer two questions:
How long will the host wait?
What does timeout mean for execution certainty?
For a local pure computation, cancellation may reliably stop the work.
For a remote side-effecting API, timeout may mean:
client stopped waiting, outcome unknown
That state must not be automatically retried without idempotency or reconciliation.
Tool timeout does not automatically end the whole agent turn
Depending on the workflow, a timed-out tool can be returned to the model as a structured failure:
{
"ok": false,
"error": "timeout",
"retryable": true
}
The model may choose another path, ask the user, or stop.
The host still owns the total round/deadline limit.
Multi-round loops need cumulative limits
Suppose every model round can take 60 seconds and every tool can take 30 seconds.
Ten rounds can still produce a very long operation.
Bound at least:
maximum rounds
maximum tool calls
maximum elapsed time
maximum retry attempts
maximum estimated cost when practical
See How Multi-Round AI Tool Loops Work.
Background provider jobs use a different clock
A background API can intentionally outlive a foreground HTTP request.
The application may create a job, persist its provider ID, and reconcile later through polling or webhooks.
In that model, distinguish:
create-request timeout
job runtime deadline
status-poll timeout
webhook reconciliation window
Timing out one status request should not recreate the job.
See How Long-Running AI Tasks Work.
App backgrounding is not a provider timeout
On mobile platforms, an app can move to the background and later be suspended.
If an in-process stream stops because the operating system suspended the app, report the lifecycle cause rather than marking the provider as unhealthy.
Persist enough state before suspension to recover:
logical request ID
provider/model
conversation position
partial output
remote job ID if available
start time
last known phase
See How to Handle App Backgrounding During AI Generation.
Avoid the “one URLSession timeout” trap
Networking frameworks often expose generic request/resource timeout settings.
They are useful, but they do not replace application semantics for:
TTFT
stream idle
multi-round tools
logical request budget
background jobs
Use lower-level transport timeouts as safety nets and higher-level clocks for the behavior users actually care about.
A timeout should produce a phase-specific error
Instead of:
TimeoutError
prefer:
connect_timeout
first_response_timeout
stream_idle_timeout
overall_deadline_exceeded
tool_timeout
queue_timeout
background_job_deadline_exceeded
That improves user messaging and observability.
Retry policy depends on timeout phase
Examples:
Connect timeout
Often safe to retry if the request was never transmitted.
First-response timeout
Execution may already have started remotely. Retry safety depends on API semantics.
Idle stream after partial output
Retrying creates a second generation unless the provider supports continuation/recovery.
Tool timeout
Side-effect outcome may be unknown.
Background poll timeout
Retry the status lookup, not the background job creation.
This is why timeout classification and idempotency belong together.
See Designing Reliable AI Retries.
Add jitter to scheduled retries, not to correctness deadlines
Retry backoff can include randomized jitter to avoid synchronized clients.
The overall deadline itself should remain deterministic from the logical request start.
For example:
overall deadline: 120s
retry delay: exponential + jitter
Before sleeping for a retry, check that enough deadline budget remains for another useful attempt.
User cancellation should preempt all timers
When the user presses Stop:
cancel network task
cancel queued retry
cancel pending tool where safe
mark logical request cancelled
ignore later retry timers
Do not let a timer fire after cancellation and restart work the user explicitly stopped.
Use monotonic time for elapsed durations
Wall-clock time can jump because of:
clock synchronization
manual clock change
time-zone changes
Elapsed-time measurements such as timeout durations should use a monotonic clock when the platform exposes one.
Wall clock remains useful for persisted timestamps and user-visible dates.
Persist absolute/declarative deadlines for durable work
In-memory monotonic timers do not survive app termination.
For a durable background operation, persist enough information to reconstruct policy:
created_at
maximum_runtime
last_status_at
provider_job_id
On relaunch, derive whether the operation is still inside the allowed window.
Timeouts should be capability-aware
A local model loading from disk may have a different startup profile from a warm cloud model.
A reasoning model may spend much longer before answer tokens than a lightweight chat model.
A provider-hosted image/video job can intentionally take far longer than text streaming.
Avoid one global value pretending all workloads are equivalent.
Use defaults by operation class, with hard upper bounds and clear product behavior.
Do not dynamically stretch deadlines forever
Adaptive policies can account for known workload size, but they need caps.
This is dangerous:
model is slow -> add 30s
still slow -> add 30s
still slow -> add 30s
The deadline no longer protects the user.
Prefer a bounded initial budget based on known operation characteristics.
Record timeout phase in observability
Useful metrics include:
queue_wait_ms
connect_ms
time_to_headers_ms
ttft_ms
stream_duration_ms
max_idle_gap_ms
tool_duration_ms
overall_duration_ms
timeout_phase
These help answer very different performance questions.
A high connect timeout rate points toward networking/endpoints.
A high TTFT with healthy connections points toward provider/model processing.
A high idle-stream timeout rate points toward stream stability, proxies, or provider event gaps.
A deadline hierarchy
A useful conceptual structure is:
No child should outlive the parent logical deadline unless it becomes a deliberately detached durable background operation.
Test with deterministic clocks
Timeout tests should not sleep for real minutes.
Inject a clock/scheduler and simulate:
connect never completes
headers arrive at boundary
first token delayed
heartbeat only
stream stalls after 10 tokens
tool never completes
retry waits past overall deadline
user cancels just before timeout
app backgrounds during stream
background job remains pending past deadline
Assert both the phase classification and cleanup behavior.
A timeout checklist
Before shipping:
- Connect time is bounded separately from model generation.
- TTFT is measured separately from transport failure.
- Active streams have an idle-progress policy.
- Logical requests have an overall deadline.
- Queue wait is visible and bounded where appropriate.
- Tool calls have operation-specific limits.
- Unknown side-effect outcomes are not blindly retried.
- Multi-round loops share the logical deadline.
- Background jobs persist their own lifecycle/deadline state.
- User cancellation invalidates pending timers/retries.
- Metrics record the timeout phase rather than only
timeout=true.
Where BYOKchat fits
A multi-provider client needs timeout semantics above any individual SDK or networking library. Provider adapters can report transport and event boundaries, while a shared request coordinator owns logical deadlines, cancellation, queue wait, retries, tools, and recovery.
That keeps a slow reasoning model from being mistaken for a dead socket, prevents a broken stream from hanging indefinitely, and makes mobile background interruptions distinguishable from provider outages.