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).
max_attemptsintoptionalTotal attempts including the first. Default 3. 1 disables retry.
base_delayfloatoptionalFirst backoff in seconds; doubles each attempt. Default 0.1.
max_delayfloatoptionalCap 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 4xx — 400, 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.
with_retry(op: Callable[[], T], policy: RetryPolicy | None = None) -> Tfrom 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))max_attemptsintoptionalTotal attempts including the first. Default 3. 1 disables retry.
base_delayfloatoptionalFirst backoff in seconds; doubles each attempt. Default 0.1.
max_delayfloatoptionalCap 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?