Brainby arc-labs/docs

Retries

Retry policies for both clients — the HTTP client's built-in HttpRetryPolicy for idempotent verbs, the wire client's with_retry combinator over RetryPolicy, and how deadlines cancel a call.

HTTP client

BrainHttpClient retries only the idempotent verbsencode (stable server-side request id), whoami, and capabilities — and only when the failure is a transport error (status: 0) or an HTTP 503. Every other status, including all 4xx, is terminal. Backoff grows exponentially from the base delay, capped at the max; a server Retry-After header (integer seconds) overrides the schedule, still capped at the max.

ParameterTypeRequired
max_attemptsu32optional

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

base_delayDurationoptional

First backoff; doubles each attempt. Default 100 ms.

max_delayDurationoptional

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

Configure it with the constructor builder:

use std::time::Duration;
use brain_db_sdk::{BrainHttpClient, HttpRetryPolicy};

// Default is HttpRetryPolicy::new(3, 100ms, 2s).
let client = BrainHttpClient::new("https://api.arc-labs.ai", "my-api-key")
    .with_retry_policy(HttpRetryPolicy::new(
        5,                             // max attempts
        Duration::from_millis(200),    // base backoff
        Duration::from_secs(4),        // cap
    ));

// Opt out entirely:
let once = BrainHttpClient::localhost("my-api-key")
    .with_retry_policy(HttpRetryPolicy::none());

recall, forget, link, unlink, plan, and reason are not retried by the HTTP client — they make a single attempt. Retry them yourself only when you know the operation is safe to repeat.

Wire client — with_retry

The wire client does not bake retry into each verb. Wrap a call in with_retry and it re-runs while the error is retryable (is_retryable) and attempts remain. Because each builder mints a stable request_id, re-sending the same request is idempotent server-side (24-hour idempotency window), so a retried verb does not double-apply.

use brain_db_sdk::{with_retry, RetryPolicy};

// RetryPolicy::default() = 3 attempts, 100ms base backoff, 5s cap.
let policy = RetryPolicy::new(4, std::time::Duration::from_millis(100), std::time::Duration::from_secs(5));

// Build the request once so its request_id stays stable across attempts.
let req = EncodeBuilder::new("part of a retried write").build();
let resp = with_retry(&policy, || client.encode(&req)).await?;

with_default_retry(|| ...) is shorthand for RetryPolicy::default(). Build a custom schedule with RetryPolicy::new(max_attempts, base_delay, max_delay), or RetryPolicy::none() for a single attempt.

Pass a closure that rebuilds the future (|| client.encode(&req)), not a single future — the combinator re-invokes it from scratch on each attempt. Reuse the same req so the request_id stays stable; rebuilding the request each try mints a fresh id and defeats server-side idempotency.

The backoff honors a server-supplied retry_after (surfaced by err.retry_after()) when present, otherwise grows exponentially from the base delay (capped at the max), then applies full jitter so a herd of clients that failed together don't retry in lockstep.

A transport drop (BrainError::Closed, an Io error) is reported retryable, but a single BrainClient does not transparently reconnect a dead socket — a retry over a closed connection re-fails fast. For resilience against a dropped socket, front the client with a brain_db_sdk::Pool and borrow a fresh connection.

Cancellation and deadlines

Neither client takes an external cancellation token; a call is bounded by its configured deadline instead. Because both clients are async-native on Tokio (there is no blocking/sync variant), you can also drop the future or wrap it in tokio::time::timeout for ad-hoc cancellation.

Each request is bounded by the client timeout (default 30 s), set with .with_timeout(..). On expiry the request is aborted and surfaces as a BrainHttpError with status: 0 (a transport failure), never a panic.

use std::time::Duration;
use brain_db_sdk::BrainHttpClient;

let client = BrainHttpClient::localhost("my-api-key")
    .with_timeout(Duration::from_secs(5));

connect_timeout bounds the TCP connect and request_timeout bounds each response read (defaults 10 s / 30 s), set on ClientConfig. A missed deadline surfaces as BrainError::Timeout(Duration), which is_retryable treats as retryable.

use std::net::SocketAddr;
use std::time::Duration;
use brain_db_sdk::{Auth, BrainClient, ClientConfig};

let addr: SocketAddr = "127.0.0.1:9090".parse()?;

let mut config = ClientConfig::new(Auth::Token(b"my-token".to_vec()));
config.request_timeout = Some(Duration::from_secs(5));

let client = BrainClient::connect_with(addr, config).await?;

Was this page helpful?

On this page