Skip to content

Documentation navigation

Timeouts, Retries & Reliability

Bound calls with timeouts and retry transient failures gracefully.

#Timeouts, Retries & Reliability

Set HTTP timeouts explicitly and retry only operations that are safe to repeat. A network timeout does not prove that remote work stopped or that a tool had no side effects.

#Per-call and per-tool timeouts

Client(timeout_seconds=...) controls the HTTPX timeout used for SDK requests. The default is 30 seconds. timeout= is an alias; supplying both with different values raises ValueError.

Python
from maivn import Agent, Client

client = Client(api_key='...', timeout_seconds=60)
agent = Agent(name='reporter', client=client)

For one invocation, use RunOptions(timeout=...):

Python
from maivn import RunOptions

response = agent.invoke('Summarize today.', options=RunOptions(timeout=45))

HTTPX distinguishes connection, read, write, and pool timeouts. A read timeout is an inactivity limit while waiting for data, not a wall-clock deadline for the whole run. Set deadlines on external services called by your tools as well.

#The four bounds

Only the HTTP setting below controls requests. The three execution settings are retained compatibility values; they do not impose tool or server-run deadlines.

OptionDefaultCurrent behavior
timeout_seconds / timeout30sHTTP request timeout.
tool_execution_timeout900sCompatibility value; no per-tool deadline is enforced by this setting.
dependency_wait_timeout300sCompatibility value; no dependency deadline is enforced by this setting.
total_execution_timeout7200sCompatibility value; no total-run deadline is enforced by this setting.

#Resolution order

Per-call RunOptions.timeout overrides the client's HTTP timeout. Explicit constructor timeout values override corresponding ClientConfig fields; omitted values use the supplied configuration or SDK defaults. The active compatibility configuration context is not consulted automatically.

ClientBuilder.from_environment() reads credentials and plane URLs, but does not read timeout environment variables. See Configuration.

#When a timeout fires

Python
import httpx
from maivn import Agent, Client

agent = Agent(name='reporter', client=Client(api_key='...', timeout_seconds=30))
try:
    response = agent.invoke('Summarize today.')
except httpx.TimeoutException:
    # Check the existing run before starting replacement work.
    raise

Use client.cancel(session_id) or await client.acancel(session_id) to request cancellation when you have the session ID. Catching a timeout or closing a local stream is not itself confirmation that remote execution was cancelled.

#Retries and backoff for scheduled invocations

Retries in mAIvn are a property of a scheduled invocation, not of a one-off agent.invoke(...) call. The idea is simple: when you've asked an agent to run on a schedule, a single flaky fire shouldn't lose the whole slot. You attach a Retry policy and the scheduler reruns the fire — with a growing delay between attempts — before it gives up.

Pass a Retry to any schedule entry point (cron, interval, at):

Python
from datetime import timedelta
import httpx
from maivn import Agent, Retry
from maivn.messages import HumanMessage

briefer = Agent(name='Daily briefing', api_key='...')

job = briefer.cron(
    '23 9 * * MON-FRI',              # weekdays at 09:23
    tz='America/New_York',
    mode='local',
    retry=Retry(
        max_attempts=4,              # initial attempt + 3 retries
        backoff='exponential',
        base=timedelta(seconds=2),
        factor=2.0,
        max_delay=timedelta(seconds=30),
        retry_on=(httpx.TimeoutException, httpx.ConnectError),
    ),
).invoke('Compose the daily ops briefing.')

#The Retry policy

FieldDefaultMeaning
max_attempts1Total attempts including the first. 1 disables retries.
backoff'constant'How the delay grows: 'constant', 'linear', or 'exponential'.
base5sThe base delay unit the backoff is built from.
factor2.0Multiplier for exponential backoff.
max_delay10 minCaps the computed delay so it can't grow without bound.
retry_on(Exception,)Which exception types trigger a retry; anything else surfaces immediately.

The backoff field is typed by the public RetryBackoff alias (one of 'constant', 'linear', 'exponential'). The delay before attempt n (1-indexed, so the first attempt has no delay) is:

  • constant — always base
  • linear — base * (n - 1)
  • exponential — base * factor ** (n - 2)

In every case the result is clamped to max_delay.

Tip: Prefer exponential backoff for anything that talks to a shared downstream service. A constant retry every two seconds against a service that's struggling tends to make the struggle worse; an exponential delay backs off out of its way and is far more likely to let it recover.

#Retry only what's worth retrying

retry_on is the most important field for correctness. Retrying a transient failure (a dropped connection, a timeout) is sensible. Retrying a deterministic failure (bad input, a misconfiguration) just wastes attempts and delays the inevitable — the second attempt will fail exactly like the first.

Scope retry_on to the transient cases:

Python
import httpx
from maivn import Retry

retry = Retry(
    max_attempts=3,
    backoff='exponential',
    retry_on=(httpx.TimeoutException, httpx.ConnectError),  # transient only
)

A ConfigurationError, ValueError, TypeError, or Pydantic validation failure is deterministic; fixing the config or data is the only path forward. The SDK does not expose an automatic is_retryable() helper. Classify the concrete failure and the safety of the operation in your application:

Python
import httpx

if isinstance(error, (TimeoutError, ConnectionError, httpx.TimeoutException)):
    schedule_a_retry()
else:
    report_to_operator(error)

#How retries interact with a single fire

Retries happen within a single fire of a scheduled job. A flaky run exhausts its attempts inside its own slot, while overlap policy controls how another scheduled fire is handled. Only the final outcome is recorded on the job's RunRecord. If every attempt fails, the job's on_error callback fires once, with the last exception:

Python
job.on_error(lambda record: alert(record.error))
Warning: A scheduled fire that exhausts its retries does not raise back into your code — the scheduler swallows it so it can keep running future fires. If you want to know about exhausted retries, you must wire on_error. Inspect job records and counters even if you do not install a callback.

Retry is a dataclass and does not validate these limits at construction. Supply a positive attempt count, non-negative delay, and positive backoff factor. Check configuration values before constructing the policy.

For the full scheduling surface — cron expressions, jitter, misfire and overlap policies, and the ScheduledJob lifecycle — see the Scheduled Invocation guide and the Scheduling reference.

#Handling transient failures gracefully

Timeouts and retries reduce how often failures reach your code, but they don't eliminate them. The final piece of reliability is catching what does get through and responding deliberately.

The mechanics of which exceptions the SDK raises, where to import them from, and how to build a layered try/except around invoke / stream live in one place — the Errors & Exceptions reference. This guide doesn't repeat that surface; instead, here is the reliability lens on it.

Two facts carry most of the weight for reliability decisions:

  • Catch the public hierarchy. Use MaivnHTTPError for typed API responses and MaivnSDKError for other SDK-owned failures.
  • Retry only concrete transient failures. Configuration, input, and authorization failures do not improve on a second attempt. Connection and timeout failures may be retryable when the operation is safe.
Python
import httpx

if isinstance(error, (TimeoutError, ConnectionError, httpx.TimeoutException)):
    schedule_a_retry()
else:
    report_to_operator(error)

For typed API failures, log MaivnHTTPError.status_code, .code, .reason, and .detail. For other SDK failures, log the exception type and string. See Errors & Exceptions.

Note: If you stream agent activity to a browser, transient failures should reach the UI too, not just your logs — the same lifecycle stream that carries phase updates also carries error events. See the Frontend Events guide for the pattern.

#A reliability checklist

  • Set HTTP and downstream timeouts. Do not treat compatibility execution settings as enforced run deadlines.
  • Avoid duplicate effects. Confirm whether existing work completed before retrying a timed-out operation.
  • Retry only the transient. Scope retry_on to timeouts and connection errors; never retry ConfigurationError or ValidationError.
  • Back off exponentially against any shared downstream service.
  • Wire on_error on every scheduled job — exhausted retries never raise back into your code.
  • Catch MaivnHTTPError and MaivnSDKError before broader transport exceptions so recovery can use the public fields.
  • Log typed HTTP fields (status_code, code, reason, detail) and the concrete exception type.

#Next steps

  • Scheduled Invocation — the full retry, jitter, misfire, and overlap model for cron / interval / one-shot jobs.
  • Dependencies — how tool chaining works, which is how upstream results reach downstream tools.
  • Errors & Exceptions — the canonical exception surface: every error type, where to import it, and the layered try/except patterns this guide builds on.
  • Frontend Events — stream lifecycle and error events to a browser via mount_events.
  • Troubleshooting — symptom-first fixes for the most common errors, including timeouts and connection failures.
  • Configuration reference — every timeout field and its environment variable.
  • Client reference — the constructor signature and timeout resolution methods.
  • Scheduling reference — the Retry, JitterSpec, ScheduledJob, and RunRecord API.
  • Core Concepts and the Overview — the mental model the rest of the SDK builds on.