Retries
Retry policies for both clients — the HTTP client's built-in policy for idempotent verbs, the wire client's withRetry combinator, and how deadlines cancel a call.
HTTP client
BrainHttpClient retries only the idempotent verbs — encode, whoami,
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 baseDelayMs, capped at maxDelayMs; a server
Retry-After header overrides the schedule.
interface HttpRetryPolicy {
maxAttempts: number; // total attempts including the first; 1 disables retry
baseDelayMs: number; // first backoff; doubles each attempt
maxDelayMs: number; // cap on any single backoff (also caps Retry-After)
}Configure it at construction:
import { BrainHttpClient, DEFAULT_HTTP_RETRY, NO_HTTP_RETRY } from "@brain-db/sdk";
// DEFAULT_HTTP_RETRY = { maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 2000 }
const brain = new BrainHttpClient({
apiKey: process.env.BRAIN_API_KEY!,
retry: { maxAttempts: 5 }, // merged over the default
});
// Opt out entirely:
const once = new BrainHttpClient({ apiKey: "…", retry: NO_HTTP_RETRY });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 — withRetry
The wire client does not bake retry into each verb. Wrap a call in withRetry
and it re-runs while the error is retryable (isRetryable) and attempts remain.
Because each request builder mints a stable requestId, re-sending the same
request is idempotent server-side (24h idempotency window), so a retried verb
does not double-apply.
import { withRetry, DEFAULT_RETRY_POLICY, NO_RETRY } from "@brain-db/sdk";
// DEFAULT_RETRY_POLICY = { maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 5000 }
const req = new ForgetBuilder(memoryId).build(); // build once → stable requestId
const res = await withRetry(() => client.forget(req), DEFAULT_RETRY_POLICY);Pass a thunk that reuses the same request object (() => client.forget(req)),
not one that rebuilds it. Rebuilding mints a fresh requestId on each try, which
defeats server-side idempotency.
The backoff honors a server-supplied retryAfterMs when present, otherwise grows
exponentially from baseDelayMs (capped at maxDelayMs), then applies full
jitter so a herd of clients that failed together don't retry in lockstep.
A transport drop is reported retryable, but the SDK does not transparently
reconnect a dead socket — a retry over a closed connection re-fails fast. Recover
by opening a fresh BrainClient.connect(...).
Cancellation and deadlines
Neither client takes an external AbortSignal; a call is bounded by its
configured deadline instead.
Each request is bounded by timeoutMs (default 30_000), enforced with an
internal AbortController. On expiry the request is aborted and surfaces as a
BrainHttpError with status: 0.
const brain = new BrainHttpClient({ apiKey: "…", timeoutMs: 5_000 });connectTimeoutMs bounds the TCP connect and requestTimeoutMs bounds each
response read (defaults 10_000 / 30_000). A missed deadline rejects with
BrainTimeout (carrying timeoutMs), which isRetryable treats as retryable.
const client = await BrainClient.connect("127.0.0.1", 9090, {
auth: { kind: "token", token: tokenBytes },
requestTimeoutMs: 5_000,
});Was this page helpful?