Brainby arc-labs/docs

Errors

The error types each client returns, how to branch on them, retryability, and the built-in retry policies.

Wire client: BrainError

BrainClient verbs return brain_db_sdk::Result<T>, an alias for Result<T, BrainError>. The enum separates the layers a caller reacts to differently:

pub enum BrainError {
    Io(std::io::Error),                 // local socket I/O failed
    Frame(FrameError),                  // a frame off the wire failed to decode
    Cbor(CborError),                    // a payload failed to encode/decode
    Protocol(String),                   // the peer broke the protocol sequence
    Server {                            // the server returned a structured ERROR frame
        code: ErrorCodeWire,
        category: ErrorCategoryWire,
        message: String,
        retry_after_ms: Option<u32>,
        response: Box<ErrorResponse>,
    },
    VersionMismatch { chosen: u8, supported: Vec<u8> },
    Closed,                             // the peer closed the connection
    Timeout(Duration),                  // an operation missed its deadline
}

Branch on a server-returned error by its stable code and category:

use brain_db_sdk::BrainError;
use brain_db_sdk::wire::types::{ErrorCategoryWire, ErrorCodeWire};

match client.encode(&req).await {
    Ok(resp) => println!("stored {}", resp.memory_id),
    Err(BrainError::Server { category: ErrorCategoryWire::ResourceExhausted, .. }) => {
        // back off and retry
    }
    Err(BrainError::Server { code: ErrorCodeWire::TextEmpty, .. }) => {
        // fix the input — not retryable
    }
    Err(other) => return Err(other.into()),
}

Categories

ErrorCategoryWire is the retry axis — nine categories:

CategoryRetryable
Protocolno
Authenticationno
Authorizationno
Validationno
NotFoundno
Conflictno
ResourceExhaustedyes
Internalno
Unavailableyes

ErrorCodeWire is the finer-grained u16 handle (e.g. TextEmpty, PermissionDenied, IdempotencyConflict, PredicateNotInSchema, RateLimited) — the stable programmatic identifier within a category.

Retryability

BrainError exposes two helpers the retry combinator consumes:

err.is_retryable();  // true for Io / Closed / Timeout and ResourceExhausted / Unavailable server verdicts
err.retry_after();   // Some(Duration) when the server sent a retry_after_ms hint

Wire retries

Retrying is opt-in and composable — wrap any verb call in with_retry. Because each builder mints a stable request_id, re-sending the same request is idempotent server-side (24-hour window), so a retried write does not double-apply:

use brain_db_sdk::{with_retry, RetryPolicy};

let policy = RetryPolicy::default(); // 3 attempts, 100ms base backoff, 5s cap
let resp = with_retry(&policy, || client.encode(&req)).await?;

with_default_retry(|| ...) is shorthand for the default policy. Build a custom schedule with RetryPolicy::new(max_attempts, base_delay, max_delay), or RetryPolicy::none() for a single attempt. The backoff honors a server-supplied retry_after, grows exponentially otherwise, and applies full jitter so a herd of clients does not retry in lockstep.

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 and the retry is idempotent.

HTTP client: BrainHttpError

BrainHttpClient verbs return Result<T, BrainHttpError>:

pub struct BrainHttpError {
    pub status: u16,     // HTTP status, or 0 for a transport failure
    pub code: String,    // stable error code ("transport" for network failures)
    pub message: String, // human-readable message
}

status: 0 means the request never reached the server (DNS, connection refused, timeout). Any other value is the HTTP status the edge returned; the code and message are parsed from the edge's { "error": { code, message } } envelope.

use brain_db_sdk::BrainHttpError;

match client.recall(&input).await {
    Ok(answer) => { /* ... */ }
    Err(BrainHttpError { status: 0, .. }) => { /* transport failure — unreachable */ }
    Err(BrainHttpError { status: 401, .. }) => { /* bad or expired key */ }
    Err(e) => eprintln!("HTTP {} [{}] {}", e.status, e.code, e.message),
}

HTTP retries

The HTTP client applies its retry policy automatically — but only to idempotent verbs (encode, whoami, capabilities) and only for transient failures (HTTP 503 and transport/timeout errors, i.e. status == 0). Every 4xx is a terminal client verdict and is never retried. Configure it with with_retry_policy:

use brain_db_sdk::{BrainHttpClient, HttpRetryPolicy};

let client = BrainHttpClient::new("https://api.arc-labs.ai", "my-api-key")
    .with_retry_policy(HttpRetryPolicy::new(
        5,                                  // max attempts
        std::time::Duration::from_millis(200), // base backoff
        std::time::Duration::from_secs(5),  // cap
    ));

// Disable retry entirely:
let no_retry = BrainHttpClient::localhost("my-api-key")
    .with_retry_policy(HttpRetryPolicy::none());

A server Retry-After header (integer seconds) overrides the computed backoff, still capped at max_delay.

Was this page helpful?

On this page