Brainby arc-labs/docs

Wire client

BrainClient — the native BRN0 wire client. Connect, handshake, authenticate, and reach the full Brain surface over one multiplexed TCP connection.

Connect

BrainClient::connect takes a SocketAddr and an Auth credential. It opens the TCP connection, runs the BRN0 handshake, and returns a live client bound to the session the server granted:

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr: SocketAddr = "127.0.0.1:9090".parse()?;
    let client = BrainClient::connect(addr, Auth::Token(b"my-token".to_vec())).await?;

    println!("connected to shard {}", client.session().bound_shard_id);
    Ok(())
}

The address is a std::net::SocketAddr, not a URL — parse a host:port string (or construct it directly). Resolve a hostname to an address yourself before calling connect.

Authentication

Auth is the connection's whole identity — there is no anonymous mode. The server resolves (namespace, agent, permissions) from the credential and refuses any connection it cannot resolve:

use brain_db_sdk::Auth;
use brain_db_sdk::wire::types::MtlsClaim;

// A shared bearer token (raw bytes):
let token = Auth::Token(b"my-token".to_vec());

// Or an mTLS subject claim:
let mtls = Auth::Mtls(MtlsClaim {
    cert_fingerprint: [0u8; 32],
    asserted_subject: "spiffe://arc/agent/ada".to_string(),
});

The credential is sent in an AUTH frame after the server's WELCOME. The client never claims an identity — it presents the credential and the server assigns the agent and namespace.

Custom configuration

connect uses default transport settings. For explicit control, build a ClientConfig and call connect_with:

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.client_id = "my-service".to_string();
config.connect_timeout = Some(Duration::from_secs(5));
config.request_timeout = Some(Duration::from_secs(20));

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

ClientConfig::new(auth) takes the mandatory credential and fills the rest with defaults. There is no Default impl — a connection without a credential cannot exist. Its public fields:

ParameterTypeRequired
client_idStringoptional

Free-form identifier sent in HELLO. Defaults to "brain-db-sdk-rust".

supported_versionsVec<u8>optional

Wire versions the client accepts, in preference order. Defaults to [1].

capabilitiesHelloCapabilitiesoptional

Capabilities advertised in HELLO (streaming, zstd, server push). Defaults to streaming on.

authAuthoptional

The credential. Mandatory — set by ClientConfig::new.

connect_timeoutOption<Duration>optional

Deadline for the TCP connect. None waits indefinitely. Defaults to 10 seconds.

request_timeoutOption<Duration>optional

Per-response read deadline after connecting. Defaults to 30 seconds.

The session

After the handshake, session() returns the SessionInfo the server granted. Convenience accessors surface the two identity fields directly:

let session = client.session();
println!("server={} version={}", session.server_id, session.chosen_version);
println!("shard={}", session.bound_shard_id);

// Convenience accessors:
let agent: [u8; 16] = client.agent_id();
let namespace: &str = client.namespace();

SessionInfo carries agent_id, server_id, chosen_version, session_id, bound_shard_id, permissions, namespace, and server_features. The namespace is empty when the connection resolves to the reserved brain system namespace — the client only reads it, it never sends a namespace.

Concurrency

Every verb takes &self, and a background reader task demultiplexes responses by stream id. Share one client across tasks behind an Arc and call freely — many requests run in flight over the single connection:

use std::sync::Arc;

let client = Arc::new(client);
let c1 = client.clone();
let c2 = client.clone();
// c1 and c2 issue requests concurrently over the one connection.

For socket-level parallelism (multiple connections), see brain_db_sdk::Pool.

When to reach for the wire client

Use BrainClient when you need anything beyond the HTTP subset:

  • Typed graph — entities, statements, relations, schema upload (Typed graph).
  • Transactionstxn_begin / txn_commit / txn_abort.
  • Subscriptions — long-lived SUBSCRIBE change feeds.
  • Streaming readsmemory_list, graph_fetch, traverse_relations, and the *_frames streaming variants.
  • Self-hosting where you connect straight to a shard's TCP listener.

Closing

Send BYE and drop the connection cleanly:

client.close().await?;

Was this page helpful?

On this page