Brainby arc-labs/docs
Python SDK

Retries

Retry the two clients — the HTTP HttpRetryPolicy (built in, idempotent-only) and the wire with_retry helper over RetryPolicy.

HTTP: HttpRetryPolicy

BrainHttpClient retries idempotent verbs automatically. Pass an HttpRetryPolicy to the constructor (or accept the default).

ParameterTypeRequired
max_attemptsintoptional

Total attempts including the first. Default 3. 1 disables retry.

base_delayfloatoptional

First backoff in seconds; doubles each attempt. Default 0.1.

max_delayfloatoptional

Cap on any single backoff (and on a server Retry-After). Default 2.0.

from brain_db_sdk import BrainHttpClient
from brain_db_sdk.http import HttpRetryPolicy

client = BrainHttpClient(
    "sk-...",
    retry=HttpRetryPolicy(max_attempts=5, base_delay=0.2, max_delay=4.0),
)

# Disable retry entirely:
client = BrainHttpClient("sk-...", retry=HttpRetryPolicy.none())

What retries. Only the idempotent verbs — encode (stable server-side request id), whoami, and capabilities (GETs). recall, forget, link, unlink, plan, and reason run exactly once.

What is retryable. HTTP 503 and transport/timeout failures (which surface as BrainHttpError with status == 0). Every 4xx400, 401, 404, 409 — is a terminal client verdict and never retried. The backoff grows exponentially from base_delay, capped at max_delay; a server Retry-After header (integer seconds) overrides the computed schedule.

Wire: with_retry

The wire client does not retry inside its verbs. Wrap a call in with_retry, which re-runs the operation while the error is retryable (is_retryable) and attempts remain.

stable
with_retry(op: Callable[[], T], policy: RetryPolicy | None = None) -> T
from brain_db_sdk import with_retry, RetryPolicy

# Pass a thunk that rebuilds the call. Reuse the SAME request object so the
# request_id stays stable and the retry is idempotent server-side.
req = RecallBuilder("Where does Ada live?").build()
answer = with_retry(lambda: client.recall(req), RetryPolicy(max_attempts=4))
ParameterTypeRequired
max_attemptsintoptional

Total attempts including the first. Default 3. 1 disables retry.

base_delayfloatoptional

First backoff in seconds; doubles each attempt. Default 0.1.

max_delayfloatoptional

Cap on any single backoff (and a server retry_after). Default 5.0.

The backoff honors a server-supplied retry_after_ms when present, else grows exponentially from base_delay (capped at max_delay). The actual sleep then gets full jitter — a random point in [floor, delay], with the server hint as the floor — so clients that failed together don't retry in lockstep.

Reuse the same request object across retries. Every builder mints a stable request_id, and the server's 24h idempotency window means re-sending the identical request does not double-apply. Rebuilding the request each try would mint a new id and defeat that.

A transport drop on the wire

is_retryable reports a transport drop or ConnectionClosed as retryable, but a single non-reconnecting BrainClient connection re-fails fast on a dropped socket — transparent reconnect is later work. For resilience against a dead socket today, front the client with a Pool and borrow a fresh connection.

Was this page helpful?

On this page