You have a function running on your laptop. A colleague wants to call it from a SQL query in their browser. The function is ready. Sharing it can mean finding somewhere to deploy it, giving it a hostname, and setting up HTTPS. That’s a fair amount of work for “can you try this?”

Iroh support in vgi-rpc gives us another way to make that connection. Your worker can stay on your machine. A client connects to its cryptographic endpoint ID, and Iroh handles finding a network path between them. You don’t need to put the worker behind a public HTTP endpoint or configure port forwarding on your router.

VGI exposes tables, functions, and application logic to Haybarn and DuckDB. Grainlift exposes databases through ADBC. Both use vgi-rpc to carry calls and Apache Arrow data, and both can use Iroh as their transport.

A browser can call your functions or query a database through a driver installed on your computer. The code, driver, and database connection stay where they already work. We’ll use Python for the example below; VGI has SDKs for several languages.

Connect to an identity

An ordinary service URL tells a client where to go. An Iroh endpoint ID identifies the peer it wants to reach. The ID is a public key; the peer proves that it holds the corresponding secret key when the connection is established.

This also takes TLS certificate management off your plate. For the worker’s Iroh connection, there’s no domain certificate to obtain, install, or renew. Iroh still uses TLS to encrypt traffic and authenticates peers using their endpoint keys.

Iroh uses address discovery and relays to help peers find each other. Native clients can establish a direct connection when the network allows it, with a relay available when it doesn’t. Keeping the same secret key preserves the endpoint’s identity across restarts and changes of address.

For vgi-rpc, the address looks like this:

iroh://<endpoint-id>

There is also an HTTP-over-Iroh form:

httpi://<endpoint-id>

That second form lets an existing HTTP worker use Iroh for the connection. The worker listens on loopback, while the native vgi-iroh-bridge accepts Iroh connections and forwards requests to it. The bridge works independently of the worker’s language. We’ll use that arrangement below.

The machine still needs to be running and able to reach the network. An endpoint ID doesn’t make a sleeping laptop answer queries. It does let us separate the worker’s identity from a public hostname and a particular IP address.

The browser takes the relay path

Iroh connections from the browser currently go through a relay. Browsers don’t expose the UDP sockets needed for Iroh’s direct connections, as the Iroh browser documentation explains.

The connection is encrypted between the browser and the remote Iroh endpoint. The relay cannot read the calls or their results. In the bridge setup, encryption ends at the bridge; the separate hop from the bridge to the worker stays on loopback.

A browser client always reaches the remote Iroh endpoint through a relay. A native client can connect directly, or fall back to a relay. In both cases, application traffic is encrypted between the client and the endpoint.
Two ways to reach the same endpoint. A relay forwards the encrypted traffic; it doesn't execute the function or query.

The browser can reach a worker behind a home router without that worker becoming a public HTTPS service. The worker still has access to its installed packages, native libraries, local files, and database drivers.

Relay location and network round trips still affect latency, especially for operations that make many small calls. Native connections can avoid the relay when a direct path works.

Call a Python function from SQL

Let’s expose a shipping-price function through VGI. It takes a column of parcel weights and returns a column of prices in cents. We’ll use a simple example rate: $3.99 plus $1.25 per kilogram.

Save this as shipping.py. This is the whole worker:

from typing import Annotated
import pyarrow as pa
import pyarrow.compute as pc
from vgi import Param, Returns, ScalarFunction, Worker
from vgi.catalog import Catalog, Schema
class ShippingQuote(ScalarFunction):
@classmethod
def compute(
cls, weight_kg: Annotated[pa.Int64Array, Param()]
) -> Annotated[pa.Int64Array, Returns()]:
# An example rate: $3.99 plus $1.25 per kilogram, in cents.
return pc.add(399, pc.multiply(weight_kg, 125))
class ShippingWorker(Worker):
catalog = Catalog(
name="shipping",
schemas=[Schema(path=["main"], functions=[ShippingQuote])],
)

Start it with uv, which supplies Python and the required package:

uvx --python 3.13 --from 'vgi-python[http]==0.43.1' \
vgi-serve shipping.py --http --host 127.0.0.1 --port 9401 \
--iroh-issuer blog-demo

The function runs in a normal Python process. Its arguments and results are Arrow arrays, so it can process a batch of weights at once. You could replace the example formula with your existing pricing library, a model, or an API client. The VGI scalar-function tutorial covers how to define these functions.

Next, download the vgi-iroh-bridge archive for your operating system from the v0.31.2 release. Extract it and put its bin/vgi-iroh-bridge executable on your PATH. In another terminal, run:

vgi-iroh-bridge --ephemeral --http-upstream http://127.0.0.1:9401

The bridge prints its endpoint ID on the first line. Keep both processes running. --ephemeral creates a temporary identity, so restarting the bridge gives you a new ID. For an endpoint you intend to keep using, use the bridge’s --secret-key-file option to persist its identity.

Now open Haybarn with uvx haybarn-cli. Replace <endpoint-id> with the ID printed by the bridge and run:

INSTALL vgi FROM community;
LOAD vgi;
-- Attach the Python worker's shipping catalog over Iroh.
ATTACH 'shipping'
(TYPE vgi, LOCATION 'httpi://<endpoint-id>');
-- These input rows belong to this query. Python calculates the prices.
SELECT weight_kg,
shipping.shipping_quote(weight_kg) AS price_cents
FROM (VALUES (1::BIGINT), (2::BIGINT), (5::BIGINT)) AS parcels(weight_kg);
weight_kg price_cents
1 524
2 649
5 1024

Haybarn discovers the function through VGI, sends its input as Arrow data, and receives Arrow results. Iroh carries the requests to the bridge, which forwards them to the worker. You can run the client on another machine using the same endpoint ID.

This example accepts callers without an allowlist and only calculates prices from the values they supply. The endpoint ID is public. Before exposing private data or functions with side effects, add an authorization policy. Stop the demo with Ctrl-C in each server terminal.

Use the same function from WebAssembly

The same SQL works in an Iroh-enabled WebAssembly build of Haybarn. The Python function stays on the worker machine; the browser supplies its input and receives the result through vgi-rpc.

The browser integration is available in @query-farm/vgi-rpc-iroh-browser. Its Haybarn adapter connects the database worker to the browser’s Iroh transport. The repository includes a complete browser example, including the worker, bridge, and page setup.

For application developers: install the Iroh adapter in the application and use Haybarn 1.5.5-rc4 or later. The adapter uses SharedArrayBuffer, so the page must be cross-origin isolated with appropriate COOP and COEP headers. The page’s resolveIrohTarget callback controls which endpoints SQL may contact. See the browser integration guide for the setup; a standard DuckDB-Wasm editor won’t have this transport installed.

You could use this function to add shipping prices to a CSV loaded in the browser. The same arrangement could let a query call a model on your workstation without compiling its dependencies to WebAssembly. Or a VGI table function could turn an existing API into rows you can join with other data.

VGI defines what the client can call. Iroh makes the worker reachable.

Grainlift uses it too

The previous article showed how Grainlift lets a browser use an existing native ADBC driver. Iroh gives that gateway another way to accept connections.

The gateway still loads the driver and connects to the database. Your database credentials stay there. The browser’s Grainlift extension sends ADBC operations through vgi-rpc, now using an Iroh connection:

INSTALL grainlift FROM community;
LOAD grainlift;
-- The gateway must already have an analytics target and authorize this peer.
ATTACH 'grainlift+iroh://<gateway-endpoint-id>'
AS analytics (TYPE grainlift, target 'analytics');

Grainlift’s Iroh configuration maps client endpoint IDs to principals and principals to the targets they can access. An existing ADBC connection profile can still supply the target’s driver and database settings.

Cupola already accepts grainlift+iroh:// service URLs. Its Settings show the browser’s endpoint ID, which you can authorize on the gateway. Native applications can use the same gateway through the Grainlift ADBC driver; Haybarn or DuckDB on the command line loads that driver with adbc_scanner.

Cupola’s current Iroh integration is for Grainlift. For VGI’s httpi:// connections, use the Haybarn integration described above, including its adapter setup and endpoint authorization.

Decide what each peer can do

Iroh authenticates the peer’s endpoint key. Your application still decides what that peer may do.

In the Python example, --iroh-issuer blog-demo puts the identity verified by the loopback bridge into the worker’s call context. The worker can then check an allowlist or apply an application policy. Grainlift uses its target-permission mapping to decide which databases the peer may access.

The bridge-facing worker should stay on loopback, or on a separately protected connection with explicit trusted-proxy configuration. The VGI Iroh guide covers that trust boundary. A public request header claiming an endpoint ID is not proof of identity.

Iroh’s public relays are shared and rate-limited, with no uptime guarantee. For production, consider a managed or self-hosted relay. The relay documentation covers those choices.

What this makes possible

A colleague can try the functions you already have running before you decide where to host them. They use SQL from their own machine or an Iroh-enabled browser application; you keep developing in the environment where your dependencies work.

The same approach works for a VGI worker beside a large local dataset, a process using a workstation’s GPU, or a Grainlift gateway on a machine with access to a database. You choose the functions, tables, and database targets to expose, along with who may use them. The client receives the results it asks for without needing a copy of the worker’s environment.

That’s the part I’m excited about. The function can stay on the laptop. The database driver can stay next to the database. And a browser can work with both.