Rust SDK
⏱ Running in 5 minutes — new here? Jump to the quickstart for install → first query → first agent memory, or copy the Quick start block below.
cargo add relata-sdkand you’re ready.
The native Rust SDK for RelataDB. Async RelataClient over gRPC, with Arrow RecordBatch results and true zero-copy Arrow Flight IPC streaming. Ships a high-level Memory client (mirrors Python relata.Memory) over the governed /memory/* REST verbs.
- Source:
crates/relata-sdk-rust/ - Crate:
relata-sdk-rust(workspace member) - Async runtime: Tokio
- Wire protocol: gRPC (
tonic+prost) — ADR-073 - Arrow:
arrow,arrow-array,arrow-schema,arrow-flight
Unlike the Python / TypeScript / Go SDKs which speak HTTP, the native Rust SDK speaks gRPC for query/ingest/admin, and uses Arrow Flight for zero-copy columnar streaming. The high-level Memory client is HTTP (the /memory/* verbs are HTTP-only).
Quick start
use relata_sdk_rust::{RelataClient, QueryRequest};
#[tokio::main]
async fn main() {
let mut client = RelataClient::connect("http://localhost:50051").await.unwrap();
let resp = client.query(QueryRequest {
purpose: "investigation".to_owned(),
sql: "SELECT * FROM Person LIMIT 5".to_owned(),
}).await.unwrap();
println!("{} rows", resp.row_count);
for row in &resp.rows {
println!("{:?}", row);
}
}RelataClient
A connected client holds three tonic sub-clients (query, ingest, admin) sharing one HTTP/2 channel. All methods are async.
pub struct RelataClient { /* ... */ }
impl RelataClient {
pub async fn connect(endpoint: impl Into<String>) -> Result<Self, tonic::transport::Error>;
pub fn endpoint(&self) -> &str;
pub async fn query(&mut self, req: QueryRequest) -> Result<QueryResponse, tonic::Status>;
pub async fn ingest(&mut self, batch: IngestBatch) -> Result<IngestResponse, tonic::Status>;
pub async fn query_arrow(&mut self, sql: &str, purpose: &str)
-> Result<Vec<RecordBatch>, SdkError>;
pub async fn query_flight(
flight_endpoint: impl Into<String>,
sql: impl Into<String>,
bearer: &str,
) -> Result<Vec<RecordBatch>, SdkError>;
pub async fn health(&mut self) -> Result<HealthStatus, tonic::Status>;
}Public types
QueryRequest
Every query must declare a purpose registered in the tenant’s PurposeRegistry (SPECS §5.22.4):
pub struct QueryRequest {
pub purpose: String,
pub sql: String,
}QueryResponse
pub struct QueryResponse {
pub columns: Vec<String>,
pub row_count: usize,
pub rows: Vec<HashMap<String, String>>, // JSON-decoded
}IngestBatch / IngestResponse
pub struct IngestBatch {
pub object_type: String,
pub purpose: String, // ingest also requires a purpose
pub rows_json: Vec<String>, // one JSON object per row
}
pub struct IngestResponse {
pub rows_accepted: i64,
pub errors: Vec<String>,
}HealthStatus
pub struct HealthStatus {
pub status: String, // "ok" or error
pub profile: String, // "lite" | "server" | "cluster"
pub node_id: String,
}Arrow result paths
Two ways to get Arrow RecordBatches back — pick the one that matches your throughput needs:
1. query_arrow — gRPC stream → RecordBatch
Calls the ExecuteStream RPC. The server chunks rows as RowBatch messages (up to 1000 rows each); each batch is converted to an Arrow RecordBatch with one Utf8 column per field. All column values are serialised to strings (same as the JSON path).
let batches = client.query_arrow("SELECT * FROM Person", "analytics").await?;
for batch in &batches {
println!("{} rows, {} cols", batch.num_rows(), batch.num_columns());
}2. query_flight — zero-copy Arrow Flight DoGet (#958)
True zero-copy Arrow Flight IPC streaming — no JSON intermediate. The Flight door is distinct from the gRPC endpoint; enable server-side with RELATA_FLIGHT_ENABLE=true (default port 8815).
use relata_sdk_rust::RelataClient;
let batches = RelataClient::query_flight(
"http://localhost:8815", // Flight door
"PURPOSE 'analytics' SELECT * FROM Person",
"", // bearer, "" in open dev mode
).await?;High-level Memory client
relata_sdk_rust::memory::Memory is a thin convenience layer over the governed /memory/* REST verbs — mirrors the Python relata.Memory. Mem0-style add / search / forget. Governance stays on: a non-empty purpose is required on every call, and forget is a retention-policy retract, not a hard delete.
use relata_sdk_rust::memory::Memory;
#[tokio::main]
async fn main() {
let m = Memory::new("http://localhost:9090", "agent-notes").unwrap();
let id = m.add("Alice prefers dark mode").await.unwrap();
let hits = m.search("ui preferences", 5).await.unwrap();
m.forget(&id).await.unwrap();
}The native
RelataClientspeaks gRPC; the memory verbs are HTTP, soMemorycarries its ownreqwesttransport.
Errors
pub enum SdkError {
Grpc(tonic::Status),
Arrow(arrow_schema::ArrowError),
MalformedResponse(String),
}Implements Display + std::error::Error. Conversions provided from tonic::Status and arrow_schema::ArrowError.
Examples
See crates/relata-sdk-rust/examples/ for runnable workflows.
License
AGPL-3.0-only — see the root LICENSE file.