vgi-rpcFrom Query.Farm

Arrow-native RPC.
From subprocess
to service.

Define services in code. Exchange Apache Arrow record batches across languages over subprocesses, sockets, or HTTP. Call your HTTP service directly from the browser.

At the core of VGI and Grainlift

Different routes. The same wire protocol.

Your code is
the contract.

vgi-rpc is an RPC framework that carries method calls and results as Apache Arrow record batches: chunks of tabular data stored as typed columns with the same number of rows. Define application services in your language and exchange columnar data across process and network boundaries.

In Python, a Protocol class is the contract: type annotations become Arrow schemas. Other SDKs use native interfaces, service macros, or explicit schema declarations. There are no separate .proto files to maintain. The wire protocol supports pipes, subprocesses, Unix and TCP sockets, HTTP, and authenticated Iroh/QUIC, with a shared-memory side channel for local batches. Transport support varies by SDK.

Calls are unary, producer streams (the server pushes batches), or exchange streams, where each turn can process a vector of inputs in one batch instead of requiring a separate RPC per item. Errors, logs, and OpenTelemetry traces travel with them.

Built with vgi-rpc

Our products.
A shared foundation.

We built vgi-rpc into the core of VGI and Grainlift. Two Query.Farm products that put the protocol to work.

VGI

For DuckDB and Haybarn

External data and functions, inside DuckDB.

The Vector Gateway Interface exposes APIs, services, models, and custom code as catalogs, tables, and functions that DuckDB can query and join.

vgi-rpc carries the calls and Arrow record batches between DuckDB’s VGI extension and workers running locally or over the network.

Explore VGI

Grainlift

For ADBC applications

Developer preview

Your data service, behind an ADBC interface.

Proxy existing databases or build custom data services in Rust, Go, Python, or TypeScript. Applications connect through Arrow Database Connectivity (ADBC).

vgi-rpc connects the Grainlift client driver to its services, carrying requests and Arrow results across the same RPC foundation.

Explore Grainlift

Define. Serve.
Call.

The same addition, in your language. Define a method, run a worker, and make your first RPC.

protocol.pyDefine the interface
from typing import Protocol

class Calculator(Protocol):
    def add(self, a: float, b: float) -> float: ...
worker.pyImplement and serve
from protocol import Calculator
from vgi_rpc import run_server

class CalculatorImpl:
    def add(self, a: float, b: float) -> float:
        return a + b

run_server(Calculator, CalculatorImpl())
client.pyCall over a subprocess
from protocol import Calculator
from vgi_rpc import connect

with connect(Calculator, ["python", "worker.py"]) as calc:
    print(calc.add(a=2.0, b=3.0))  # 5.0

Install vgi-rpc, save these three files together, then run python client.py.

Python SDK guide

Built for the work between services.

Typed RPC across seven languages on one Arrow-based wire protocol.

Services defined in code

Define methods with native types or explicit Arrow schemas, depending on your SDK. There is no separate .proto service definition to maintain.

Transport-Agnostic

Built-in transports run from in-process pipes to authenticated Iroh/QUIC across a network, and the transport layer accepts custom implementations.

Apache Arrow Native

Arguments and results travel as Arrow record batches. Large DataFrames cross without row-by-row conversion, and the shared-memory side channel skips the copy entirely.

A vector of work per exchange

Send a batch of inputs to an exchange method and receive a batch of results. Amortize request overhead across many items, instead of making a separate RPC for every item.

Direct from the browser

The TypeScript HTTP client uses browser fetch to send Arrow IPC directly to your service. Cross-origin deployments need CORS configuration; a gRPC-Web proxy is not required.

Built for HTTP load balancers

Externalize stream state so successive requests can reach different workers. Opt into sticky sessions when a cursor, model, or other resource needs worker affinity.

A batch of work. One exchange.

A method that scores 1,000 inputs can accept all 1,000 rows in one Arrow record batch and return a batch of scores. Open the exchange once, then send vectors of inputs through the same method, one batch per turn. This spreads serialization, dispatch, and network overhead across the batch and lets the handler use vectorized operations.

1,000 inputs → one batch → one exchange turn

Each turn still has a request and response; over HTTP, each continuation is an HTTP request. The saving comes from doing many items of work in that turn. Batch size and processing logic are up to your method.

How exchange streams work →

State that can move between workers.

HTTP stream state can be externalized into encrypted, authenticated continuation tokens that the client returns with its next request. With serializable state, shared token keys, and access to the same backing resources, another worker can resume the stream behind your HTTP load balancer.

When state needs to stay in memory on a particular worker, opt into sticky sessions. Session tokens and echoed routing headers support affinity for resources such as open database cursors or loaded models; configure your load balancer to route those requests to the owning worker.

HTTP state tokens →
Sticky sessions and routing →

Start local. Go further.

Pick a transport by how far the caller sits from the worker: the same process, the same machine, or across a network.

Iroh connects peers using QUIC, an encrypted transport that carries multiple independent streams over UDP. Rust can host Iroh directly; workers in every language can use the standalone bridge. Native clients require the optional binding or provider for their language. Iroh modes, setup, and identity →

Pipe

In-process

In-process, bidirectional byte stream. Lowest latency — ideal for tests and embedded use.

Testing, demos

Subprocess

Process IPC

Spawn a worker process, communicate over stdin/stdout. Process isolation with low overhead.

Isolated workers, CLI tools

Unix Socket

Local IPC

Kernel-mediated local IPC. Long-lived connections with per-client threading support.

Local services, daemons

TCP

Network

Lean Arrow IPC framing over a persistent socket. Intended for trusted networks; use HTTP with TLS or Iroh for authenticated network connections.

Trusted network services

Shared Memory

Zero-copy side channel

Zero-copy Arrow batch transfer via memory-mapped segments. Only pointers cross the pipe.

Large batches, co-located

HTTP

Network + HTTP

Externalized stream state lets requests move between workers behind an HTTP load balancer. Optional sticky sessions support worker affinity when state needs to stay local.

Network services, browsers

Iroh

Peer-to-peer network

iroh:// carries stateful Arrow RPC over authenticated QUIC. Independent logical streams share a connection while each stream keeps its worker state in memory.

Persistent streaming services

HTTP over Iroh

HTTP semantics + QUIC

httpi:// carries HTTP request/response semantics over Iroh. Retains capability discovery, sealed continuation tokens, compression, and external payloads.

HTTP workers behind a bridge

Where vgi-rpc fits.

Arrow-native application RPC, from local workers to browser clients. Here is how the architecture differs from gRPC and Arrow Flight.

Alongside gRPC

gRPC provides cross-language service calls and generated, typed clients. vgi-rpc makes Arrow batches the native payload and lets the protocol run over a subprocess pipe as well as an HTTP connection.

gRPC architecture ↗

Alongside Arrow Flight

Flight already transfers Arrow batches efficiently over gRPC. vgi-rpc exposes your own application methods over multiple transports, including an HTTP API that a browser client can call directly.

Arrow Flight architecture ↗
Service definitions, payloads, transports, browser and WebAssembly access, and streaming models.
Architecturevgi-rpcgRPCArrow Flight
Service modelApplication-defined methods; native types or Arrow schemas in codeApplication-defined methods; .proto definitions and generated stubs by defaultStandard data-service API: discovery, get, put, exchange, and custom actions
Payload formatApache Arrow IPC record batchesProtocol Buffers by default; custom serializers are possibleArrow IPC data with Protobuf-defined Flight messages
TransportPipes, subprocesses, Unix/TCP, HTTP, Iroh; shared-memory side channelHTTP/2-based gRPCgRPC by default; the specification also allows alternative transports
Browser accessTypeScript client calls the HTTP endpoint directly with fetchgRPC-Web client plus a compatible endpoint or translating proxyThe standard gRPC endpoint needs a browser-compatible client and gateway or adapter
WebAssemblyRust HTTP client supports a host-provided executor, including Emscripten integrationDepends on the client and runtime; browser WASM still needs browser-compatible networkingDepends on the client and runtime; compiling to WASM alone does not adapt gRPC for browsers
Streaming modelProducer streams and lockstep exchange; each input batch can carry a vector of workUnary, client, server, and bidirectional streaming; gRPC-Web has a smaller supported subsetDoGet, DoPut, and DoExchange; browser support depends on the adapter

An HTTP endpoint your browser can call.

The vgi-rpc TypeScript client sends Arrow IPC using browser HTTP APIs. Point it at your service and configure CORS for cross-origin requests. There is no gRPC-Web translation layer to deploy.

gRPC and Flight can be reached from browsers through suitable adapters. The distinction is the direct HTTP path: compiling a native gRPC client to WASM does not give it access to networking features the browser does not expose.

How gRPC-Web connects browser clients ↗
browser.tsvgi-rpc over HTTPS
import { httpConnect } from "@query-farm/vgi-rpc";

const client = httpConnect("https://api.example.com", {
  protocol: "Calculator",
});
try {
  const result = await client.call("add", { a: 2, b: 3 });
  console.log(result); // { result: 5 }
} finally {
  client.close();
}

Calls the Calculator service from the example above once it is hosted over HTTPS. Bundle with Vite, esbuild, or another browser bundler.

TypeScript browser setup ↗

For WASM hosts, the Rust client accepts a custom HTTP executor; the application supplies its runtime’s networking integration. WASM support depends on the selected SDK and host runtime. Rust HTTP executor integration ↗

Transport and feature availability varies by implementation. See the SDK capability matrix and the broader framework comparison ↗.

Measured across implementations

A fixed Rust client compares the release-pinned servers over real subprocess, socket, HTTP, and shared-memory transports.

Mean unary latency

0.016 ms

Add two float64 values over subprocess stdio

Median of 6 stable release-pinned servers

Large-payload throughput

3.35 GiB/s

16 MiB C++ shared memory echo, bidirectional payload

Peak single-server-core result on AWS Graviton5; one outstanding call

16-client throughput

348,655.4 calls/sec

Concurrent unary calls over Unix sockets

Median of 5 stable release-pinned servers

Choose your language.

One wire protocol, multiple languages. All implementations interoperate via subprocess transport.

Python logo

Python

Reference Implementation

The Python reference with typed proxies, streaming, reflection, shared memory, worker pooling, and OpenTelemetry. Optional iroh extra provides native raw Iroh and HTTP-over-Iroh clients.

pip install vgi-rpc
TypeScript logo

TypeScript

TypeScript clients and workers for Bun, Node.js, and Deno. Includes HTTP, raw Iroh, and HTTP over Iroh, external storage, and OpenTelemetry. Native Iroh uses Node/Bun bindings; browser integration needs a separate adapter.

npm install @query-farm/vgi-rpc
Go logo

Go

Go workers and schema-first clients for HTTP and streaming, with zstd request compression and external payloads. Raw Iroh and HTTP over Iroh use an explicitly supplied native or community provider.

go get github.com/Query-farm/vgi-rpc-go/vgirpc
Rust logo

Rust

Rust clients and servers with derive macros, streaming, shared memory, and HTTP. Optional crates add native Iroh hosting and clients, HTTP over Iroh, and a standalone bridge for other language workers.

cargo add vgi-rpc
Java logo

Java

Java 21+ typed unary and streaming clients over subprocess, Unix, TCP, and HTTP with compression and external payloads. The optional vgirpc-iroh module adds both Iroh modes; worker shared memory requires JDK 22+.

implementation("farm.query:vgirpc:0.27.1")
C# logo

C#

.NET 10 typed and schema-first clients with unary and streaming calls, subprocess pooling, shared memory, HTTP compression, external payloads, and Iroh connectors. Client, HTTP client, and OAuth integrations ship as separate packages.

dotnet add package QueryFarm.VgiRpc
C++ logo

C++

C++20 clients and workers with unary and streaming RPCs, shared memory, HTTP compression, and external storage. Build with VGI_RPC_WITH_IROH_CABI for native raw Iroh and HTTP-over-Iroh clients.

What Each SDK Supports

Workers are close to complete in every language; clients differ by API style and transport. These are declarations reviewed against each SDK’s documentation, not a fresh cross-language conformance run.

Workers

Every worker SDK supports 19 of the 22 worker capabilities we track. The exceptions:

  • Shared Memory transport not in TypeScript
  • Annotated Types not declared for TypeScript, Go
  • OpenTelemetry not in C++

Supported by all seven

  • In-process Pipe
  • Stdio Worker
  • Unix Socket
  • TCP Socket
  • HTTP
  • Unary
  • Unary (void)
  • Producer
  • Producer + Header
  • Exchange
  • Exchange + Header
  • Introspection
  • Client-directed Logs
  • Error Propagation
  • Complex Types
  • Optional Types
  • Dataclass Types
  • Authentication
  • External Storage

Clients

Clients are where the ports differ. The table shows only the capabilities that vary.

Supported by every client

  • HTTP
  • HTTP zstd
  • Unary Calls
  • Producer Streams
  • Exchange Streams
  • Authentication Headers
  • Client Log Callback
  • External Payload Resolution
LanguageAPI styleSubprocessUnix socketTCPShared memoryIroh + httpiWorker poolIntrospection
PythonTyped proxy✓✓✓✓Optional✓✓
TypeScriptDynamic✓—✓—Optional—✓
GoSchema-first————Provider——
RustSchema-first✓✓✓✓Optional—✓
JavaTyped proxy✓✓✓—Optional—✓
C#Typed + schema-first✓✓✓✓Optional✓Schema-first
C++Schema-first✓✓✓✓Optional—✓

Optional Iroh support requires the language’s Iroh binding or build feature; TypeScript’s targets Node/Bun, and browsers need a separate adapter. Go requires an explicit provider; none is bundled. Schema-first clients call methods and reflection through a generic RPC API rather than generated or typed proxies.

Full capability matrix(every declared capability, by language)

Client Implementations

All maintained ports ship a native client surface, but their API scope and supported call patterns differ.

Client SurfacePythonTypeScriptGoRustJavaC#C++
Client Library✓✓✓✓✓✓✓
ScopeFull RPC surfaceFull RPC surfaceHTTP + Iroh providerFull RPC surfaceFull RPC surfaceFull RPC surfaceFull RPC surface
API ModelTyped proxyDynamicSchema-firstSchema-firstTyped proxyTyped + schema-firstSchema-first
Unary Calls✓✓✓✓✓✓✓
Producer Streams✓✓✓✓✓✓✓
Exchange Streams✓✓✓✓✓✓✓

Client Connections & Orchestration

Client CapabilityPythonTypeScriptGoRustJavaC#C++
Subprocess Launcher✓✓—✓✓✓✓
Unix Socket✓——✓✓✓✓
TCP Socket✓✓—✓✓✓✓
HTTP✓✓✓✓✓✓✓
HTTP zstd✓✓✓✓✓✓✓
Iroh (iroh://)Optional nativeOptional nativeProvider requiredOptional nativeOptional nativeOptional nativeOptional native
HTTP over Iroh (httpi://)Optional nativeOptional nativeProvider requiredOptional nativeOptional nativeOptional nativeOptional native
Shared Memory✓——✓—✓✓
Worker Pool✓————✓—

Client Services

Client CapabilityPythonTypeScriptGoRustJavaC#C++
Service Introspection✓✓—✓✓Schema-first✓
Authentication Headers✓✓✓✓✓✓✓
Client Log Callback✓✓✓✓✓✓✓
External Payload Resolution✓✓✓✓✓✓✓

Optional native requires the language’s Iroh binding or build feature. Go requires an explicit provider; no native Go provider is bundled. TypeScript native Iroh targets Node/Bun; browsers require a separate adapter. Schema-first means callers invoke reflection through the generic RPC API. Java’s shared-memory entry here describes the native client, separately from its worker-side support.

Worker Transports

Ways each implementation can host a worker. All seven can expose raw or HTTP workers through the standalone Iroh bridge; Rust also offers an embedded native Iroh server. Stdio availability does not imply a native subprocess-launching client.

Worker TransportPythonTypeScriptGoRustJavaC#C++
In-process Pipe✓✓✓✓✓✓✓
Stdio Worker✓✓✓✓✓✓✓
Unix Socket✓✓✓✓✓✓✓
TCP Socket✓✓✓✓✓✓✓
Shared Memory✓—✓✓✓✓✓
HTTP✓✓✓✓✓✓✓

Worker RPC Patterns

PatternPythonTypeScriptGoRustJavaC#C++
Unary✓✓✓✓✓✓✓
Unary (void)✓✓✓✓✓✓✓
Producer✓✓✓✓✓✓✓
Producer + Header✓✓✓✓✓✓✓
Exchange✓✓✓✓✓✓✓
Exchange + Header✓✓✓✓✓✓✓

Worker Features

FeaturePythonTypeScriptGoRustJavaC#C++
Introspection✓✓✓✓✓✓✓
Client-directed Logs✓✓✓✓✓✓✓
Error Propagation✓✓✓✓✓✓✓
Complex Types✓✓✓✓✓✓✓
Optional Types✓✓✓✓✓✓✓
Dataclass Types✓✓✓✓✓✓✓
Annotated Types✓——✓✓✓✓
Authentication✓✓✓✓✓✓✓
External Storage✓✓✓✓✓✓✓
OpenTelemetry✓✓✓✓✓✓—

SDK sources reviewed 2026-10-01. Benchmark results retain the versions used for their measured runs.

Last updated: October 1, 2026

Put it to work.

Install the package for your language, then adapt the example above.

Python logoPythonDocs →
pip install vgi-rpc
TypeScript logoTypeScriptDocs →
npm install @query-farm/vgi-rpc
go get github.com/Query-farm/vgi-rpc-go/vgirpc
cargo add vgi-rpc
implementation("farm.query:vgirpc:0.27.1")
dotnet add package QueryFarm.VgiRpc
git clone https://github.com/Query-farm/vgi-rpc-cpp.git

Python Iroh clients: pip install 'vgi-rpc[iroh]'. C# callers also install QueryFarm.VgiRpc.Client or QueryFarm.VgiRpc.Client.Http.
See Iroh setup for each language →