26  The unified client surface — one connect, one Session, two honest transports

Recipe: pip install jammi-ai[embedded] · jammi.connect(target) -> a transport-agnostic Session (file:// -> an in-process EmbeddedBackend, direct FFI; grpc:// -> a RemoteDatabase) · supports(Capability.X) · the JammiError taxonomy (InvalidArgument / NotSupportedOnBackend / TrainingError / BackendError) · credentials= on the front door. · Theory: the Liskov-substitutable transport (Liskov and Wing 1994) — an embedded engine and a remote channel are two implementations of one behavioural contract, so a program written against the contract runs against either; and the leaky-abstraction principle (Spolsky 2002) — where the two transports genuinely diverge, the seam says so out loud (a typed capability, a finite cap), it does not paper over it. · Rail: measurement (the shared verb vocabulary, every capability boolean, every raised class, and the receive-cap edge asserted against the frozen golden — names and booleans exact, not transcribed).

jammi-ai (import jammi) is the base: one lean Python package that speaks to a Jammi engine, local or remote. Out of the box it carries the remote arm — connect("grpc://…") over the jammi.v1 wire. The in-process engine is an opt-in extra: pip install jammi-ai[embedded] pulls the compiled jammi-ai-native dist, and with it connect("file://…") resolves to an EmbeddedBackend that drives the engine in-process by direct FFI — no server, no socket. One package, two transports, selected by target alone.

The earlier chapters reach the engine through the same jammi client with a file:// target and the [embedded] extra installed, so connect always resolves the in-process engine. This chapter measures that surface itself: one connect(target) returns a Session; the embedded jammi.EmbeddedBackend and the remote jammi.RemoteDatabase both satisfy it, so a program written against the Session surface runs in-process or behind a server by target alone.

That substitution — local for remote without touching a call — is a claim, and this chapter measures it in four movements. First the foundation: the [embedded] install seam and the shared Session vocabulary both transports carry. Then the three seams that make the substitution truthful rather than merely convenient — because two transports are not identical, and the honest thing is to name where they diverge:

  1. the capability contract — the handful of features that exist on only one transport, answered by a predicate before the call and by a typed error if you skip the predicate;
  2. the JammiError taxonomy — one exception family both backends raise, so one except JammiError holds whether the engine ran in-process or over the wire, and a bad argument is the same class on both sides;
  3. the remote honest edge — the one place the remote transport carries a finite bound the embedded engine does not, surfaced loudly rather than silently truncated.
from jammi_cookbook import contracts

rec = contracts.load_artifact("unified_client.record")
inst = rec["install"]
par = rec["parity"]
cap = rec["capability"]
print(f"[embedded] extra pins:     {inst['embedded_extra_pins']}")
print(f"file:// -> backend:        {inst['file_uri_backend']}  ({inst['file_uri_backend_module']})")
print(f"grpc:// -> backend:        {inst['grpc_uri_backend']}")
print(f"shared Session verbs:      {par['verb_count']} — present on both backends: {par['both_complete']}")
print(f"capability enum (CLOSED):  {cap['enum_values']}")
[embedded] extra pins:     ['jammi-ai-native==0.47.0']
file:// -> backend:        EmbeddedBackend  (jammi._embedded)
grpc:// -> backend:        RemoteDatabase
shared Session verbs:      50 — present on both backends: True
capability enum (CLOSED):  ['audit', 'ephemeral_session', 'preload_model', 'close', 'session_id']

26.1 Installing the engine — the [embedded] extra

The base package is native-free. pip install jammi-ai gives you the remote arm alone — lean, dependency-light, exactly what a deploy image wants. The compiled engine ships as its own dist, jammi-ai-native (importable as jammi_native), and rides in as the opt-in [embedded] extra:

pip install jammi-ai            # remote only — connect("grpc://…")
pip install jammi-ai[embedded]  # + the in-process engine — connect("file://…")

The extra is a real, declared edge, not folklore: jammi-ai’s package metadata advertises embedded and pins the engine dist at the exact lockstep version (the embedded backend drives the engine’s wire seam by direct PyO3, so a version skew would desynchronise that seam — the pin is ==, not a floor). We read the extra straight off the installed dist’s metadata rather than transcribe it, and confirm the engine it names is importable in this book’s environment (the [embedded] extra is present, which is why every file:// chapter has resolved).

import importlib.metadata as importlib_metadata
from importlib.util import find_spec

md = importlib_metadata.metadata("jammi-ai")
extras = sorted(md.get_all("Provides-Extra") or [])
reqs = md.get_all("Requires-Dist") or []
embedded_pins = sorted(
    r.split(";", 1)[0].strip() for r in reqs if "extra ==" in r and "'embedded'" in r
)
native_here = find_spec("jammi_native") is not None

print(f"jammi-ai extras:        {extras}")
print(f"[embedded] pins:            {embedded_pins}")
print(f"jammi_native importable:    {native_here}  (the extra is installed)")

assert "embedded" in extras
assert embedded_pins == inst["embedded_extra_pins"]
assert any(p.startswith("jammi-ai-native==") for p in embedded_pins), "the extra pins the engine"
assert native_here is inst["native_importable"] is True
jammi-ai extras:        ['dev', 'embedded']
[embedded] pins:            ['jammi-ai-native==0.47.0']
jammi_native importable:    True  (the extra is installed)

Without the extra, the base client is honest about the absence: a file:// target raises a typed NoEmbeddedEngineError pointing at exactly this extra — the runtime echo of the Rust #[cfg] gate — rather than importing a phantom engine. The lean build and the embed build are one package, differing only by whether [embedded] is present.

26.2 Two arms, one Session — the direct-FFI backend and its remote peer

With the extra installed, the single front door resolves each target to its transport. A file:// URL returns an EmbeddedBackend — the compiled engine driven in-process by direct FFI, no server stood up, no socket opened. A grpc:// URL returns a RemoteDatabase over the wire. Both come off the same base jammi client, and both are a Session.

import tempfile

import jammi
from jammi import Session

embedded = jammi.connect(f"file://{tempfile.mkdtemp(prefix='jammi_uc_')}")
remote = jammi.connect("grpc://127.0.0.1:8081")  # lazy — opens no socket

print(f"connect('file://…') -> {type(embedded).__module__}.{type(embedded).__name__}")
print(f"connect('grpc://…') -> {type(remote).__module__}.{type(remote).__name__}")
print(f"both are a Session:  embedded={isinstance(embedded, Session)}  remote={isinstance(remote, Session)}")

assert type(embedded).__name__ == inst["file_uri_backend"] == "EmbeddedBackend"
assert type(embedded).__module__ == inst["file_uri_backend_module"]  # the base client, direct FFI
assert type(remote).__name__ == inst["grpc_uri_backend"] == "RemoteDatabase"
assert isinstance(embedded, Session) and isinstance(remote, Session)
connect('file://…') -> jammi._embedded.EmbeddedBackend
connect('grpc://…') -> jammi._database.RemoteDatabase
both are a Session:  embedded=True  remote=True

“Both are a Session” is what makes the substitution a type, not a hope. The Session Protocol enumerates the shared verb vocabulary — sources, embeddings, search, inference, training, the engine-state pipeline, evaluation, channels, mutable tables, topics, the model lifecycle, tenant scope, SQL, the stateless numerics — and it is runtime_checkable, so the isinstance above is a structural check that both concrete classes carry every member the Protocol names. We read the Protocol’s member list off the Protocol itself and assert both backends are complete against it: the parity is measured member-for-member, not asserted.

attrs = getattr(Session, "__protocol_attrs__", None)
if attrs is None:  # <3.12 fallback
    from typing import _get_protocol_attrs

    attrs = _get_protocol_attrs(Session)
verbs = sorted(a for a in attrs if not a.startswith("_"))

emb_missing = sorted(v for v in verbs if not hasattr(type(embedded), v))
rem_missing = sorted(v for v in verbs if not hasattr(type(remote), v))

print(f"Session Protocol names {len(verbs)} shared verbs")
print(f"  missing on EmbeddedBackend: {emb_missing or 'none'}")
print(f"  missing on RemoteDatabase:  {rem_missing or 'none'}")

assert verbs == par["session_protocol_verbs"]
assert len(verbs) == par["verb_count"]
assert emb_missing == rem_missing == [], "both backends carry the whole shared vocabulary"
assert par["both_complete"] is True
Session Protocol names 50 shared verbs
  missing on EmbeddedBackend: none
  missing on RemoteDatabase:  none

Every shared verb is present on both backends, no drift. A caller writes one program against the Session surface; the target picks the transport. What the Protocol deliberately leaves off this shared list — the one-sided, capability-gated members — is the subject of the next seam.

26.3 The capability contract — a predicate, not a guess

Most of the verb vocabulary is shared: sources, embeddings, search, inference, training, evaluation, channels, mutable tables, SQL — every one present on both transports, which is what lets the earlier chapters swap file:// for grpc:// freely. A closed set of five features genuinely diverge. The embedded engine carries the in-process primitives — the per-query audit log, an ephemeral_session storage context, preload_model — because they only mean something in-process. The remote client carries the two that only mean something across a channel — an explicit close (the embedded engine releases on drop, RAII) and an opaque per-connection session_id.

supports(Capability.X) answers “does this backend carry that feature?” — so a program that must run on either transport asks first, rather than guessing and catching. The two sets are exactly complementary across the closed five: every capability is carried by precisely one transport.

from jammi import Capability

# `embedded` and `remote` are the two Sessions opened in the parity movement above.
print(f"{'capability':<20}{'embedded':>10}{'remote':>10}")
for c in Capability:
    print(f"{c.value:<20}{str(embedded.supports(c)):>10}{str(remote.supports(c)):>10}")

emb_live = {c.value: embedded.supports(c) for c in Capability}
rem_live = {c.value: remote.supports(c) for c in Capability}
assert emb_live == cap["embedded_supports"], "embedded capability set drifted from the golden"
assert rem_live == cap["remote_supports"], "remote capability set drifted from the golden"
assert all(emb_live[k] != rem_live[k] for k in emb_live), "the two sets must be complementary"
capability            embedded    remote
audit                     True     False
ephemeral_session         True     False
preload_model             True     False
close                    False      True
session_id               False      True

Skip the predicate and invoke a one-sided feature on the backend that lacks it, and the seam still refuses to lie: it raises the typed NotSupportedOnBackend — naming the capability you reached for — rather than a bare AttributeError that would leave you guessing whether the method was misspelled or merely absent here.

from jammi.errors import NotSupportedOnBackend

# session_id is remote-only; reach for it on the embedded engine.
try:
    embedded.session_id
    embedded_wrong = None
except NotSupportedOnBackend as exc:
    embedded_wrong = (type(exc).__name__, str(exc.capability))

# audit is embedded-only; reach for it on the remote client.
try:
    remote.audit
    remote_wrong = None
except NotSupportedOnBackend as exc:
    remote_wrong = (type(exc).__name__, str(exc.capability))

print(f"embedded.session_id -> {embedded_wrong[0]} (capability: {embedded_wrong[1]})")
print(f"remote.audit        -> {remote_wrong[0]} (capability: {remote_wrong[1]})")

assert embedded_wrong[1] == cap["embedded_wrong_side"]["capability"]
assert remote_wrong[1] == cap["remote_wrong_side"]["capability"]
embedded.session_id -> NotSupportedOnBackend (capability: session_id)
remote.audit        -> NotSupportedOnBackend (capability: audit)

26.4 The JammiError taxonomy — one family, two transports

A program that runs on either transport must handle failure on either transport. If the embedded engine raised one exception family and the remote client another, a caller would write two error-handling paths and the substitution would leak. It does not: every failure a session can raise descends from JammiError, and both backends raise that one family — the pure-Python client raises these classes directly, and the compiled engine imports the very same module and raises the very same classes. So one except JammiError holds regardless of where the engine ran.

The taxonomy also refines the closest honest built-in, so an existing clause keeps working: a bad argument is a ValueError (InvalidArgument), a transport or runtime fault is a RuntimeError (BackendError). The extra base is never a compatibility shim — it states what kind of error each class is.

from jammi.errors import BackendError, InvalidArgument, JammiError, TrainingError

for cls in (JammiError, InvalidArgument, NotSupportedOnBackend, TrainingError, BackendError):
    bases = ", ".join(b.__name__ for b in cls.__bases__)
    print(f"{cls.__name__:<24} <- {bases}")

assert issubclass(InvalidArgument, ValueError)   # a bad argument IS a ValueError
assert issubclass(BackendError, RuntimeError)    # a backend fault IS a RuntimeError
assert issubclass(InvalidArgument, JammiError) and issubclass(BackendError, JammiError)
JammiError               <- Exception
InvalidArgument          <- JammiError, ValueError
NotSupportedOnBackend    <- JammiError
TrainingError            <- JammiError, RuntimeError
BackendError             <- JammiError, RuntimeError

The sharp test is two-sided parity: a bad argument must surface as the same InvalidArgument class whether the embedded engine rejected it in-process or a server rejected it over the wire. We provoke both under one except JammiError clause. The embedded side rejects a malformed tenant id in-process. The remote side receives a server INVALID_ARGUMENT status — injected at the transport boundary exactly as a live server’s status would arrive (the same hermetic technique the engine’s own conformance suite uses), so the verb still routes through the real client mapping without a socket being dialed.

import grpc


class _FakeRpcError(grpc.RpcError):
    """A grpc.RpcError carrying a chosen status code, as a server would set it."""
    def __init__(self, code, details):
        self._code, self._details = code, details
    def code(self):
        return self._code
    def details(self):
        return self._details


class _RaisingStub:
    """A transport stub whose every verb raises a fixed status — the wire hop,
    stubbed, so no server socket is opened."""
    def __init__(self, err):
        self._err = err
    def __getattr__(self, _name):
        def _raise(*_a, **_k):
            raise self._err
        return _raise


caught = []

# Embedded transport — the in-process engine rejects a malformed tenant id.
try:
    embedded.set_tenant("not-a-uuid")
except JammiError as exc:
    caught.append(("embedded", exc))

# Remote transport — a server INVALID_ARGUMENT status, mapped by the client.
remote._catalog = _RaisingStub(_FakeRpcError(grpc.StatusCode.INVALID_ARGUMENT, "bad arg"))
try:
    remote.list_sources()
except JammiError as exc:  # the SAME clause catches the remote side
    caught.append(("remote", exc))

for where, exc in caught:
    print(f"{where:<9} -> {type(exc).__name__:<16} "
          f"JammiError={isinstance(exc, JammiError)} ValueError={isinstance(exc, ValueError)}")

(_, emb_exc), (_, rem_exc) = caught
assert type(emb_exc) is type(rem_exc) is InvalidArgument, "same class on both transports"
assert len(caught) == 2, "one `except JammiError` caught both transports"
embedded  -> InvalidArgument  JammiError=True ValueError=True
remote    -> InvalidArgument  JammiError=True ValueError=True

Both transports, one clause, one class. The develop→deploy substitution holds for the error-handling path, not only the happy path.

26.5 The remote honest edge — a finite cap, said out loud

There is one place the two transports genuinely differ in what they can return. The embedded engine hands a result back in-process — a Python object, unbounded by any wire budget. The remote transport carries every result as a gRPC message, and gRPC caps the receive size. The client raises that cap off gRPC’s stock 4 MB default to a generous but finite 64 MiB, sized for a realistic unary result table while still bounding a pathological one. The honest thing is that this bound is finite and named, not infinite and hidden: a result exceeding it does not silently truncate — it raises BackendError, mapped from gRPC’s RESOURCE_EXHAUSTED.

from jammi._database import MAX_RECEIVE_MESSAGE_LENGTH, _rpc_to_jammi

edge = rec["honest_edge"]
print(f"remote receive cap (production default): "
      f"{MAX_RECEIVE_MESSAGE_LENGTH:,} bytes = {MAX_RECEIVE_MESSAGE_LENGTH // (1024*1024)} MiB")
print(f"embedded in-process return:              unbounded (no wire message)")
assert MAX_RECEIVE_MESSAGE_LENGTH == edge["production_cap_bytes"] == 64 * 1024 * 1024
remote receive cap (production default): 67,108,864 bytes = 64 MiB
embedded in-process return:              unbounded (no wire message)

We exercise the mechanism for real. A 64 MiB payload is impractical to move inside the book’s CPU harness, so the reject arm is shown at a scaled-down configured cap — the mechanism is the channel’s receive cap whatever its value — while the generous arm runs at the real 64 MiB production default. A minimal in-process gRPC server returns a 256 KiB payload; a channel whose cap is 64 KiB rejects it with a real RESOURCE_EXHAUSTED, which the real client mapping turns into BackendError; a channel at the production 64 MiB returns it whole.

import concurrent.futures

_ECHO = "/jammi.probe.Echo/Get"


def echo_server(payload):
    def handler(_req, _ctx):
        return payload
    rpc = grpc.unary_unary_rpc_method_handler(
        handler, request_deserializer=lambda b: b, response_serializer=lambda b: b)
    generic = grpc.method_handlers_generic_handler("jammi.probe.Echo", {"Get": rpc})
    server = grpc.server(concurrent.futures.ThreadPoolExecutor(max_workers=2), handlers=[generic])
    port = server.add_insecure_port("127.0.0.1:0")
    server.start()
    return server, port


def receive_at_cap(port, cap):
    channel = grpc.insecure_channel(
        f"127.0.0.1:{port}", options=[("grpc.max_receive_message_length", cap)])
    stub = channel.unary_unary(
        _ECHO, request_serializer=lambda b: b, response_deserializer=lambda b: b)
    try:
        return len(stub(b"go")), None
    except grpc.RpcError as exc:
        return None, exc
    finally:
        channel.close()


payload = b"x" * edge["payload_bytes"]
server, port = echo_server(payload)
try:
    generous_bytes, generous_err = receive_at_cap(port, MAX_RECEIVE_MESSAGE_LENGTH)
    capped_bytes, capped_err = receive_at_cap(port, edge["demo_cap_bytes"])
finally:
    server.stop(0)

mapped = _rpc_to_jammi(capped_err)
print(f"payload:                    {len(payload):,} bytes")
print(f"channel @ 64 MiB (prod):    returned {generous_bytes:,} bytes  (mirrors embedded)")
print(f"channel @ {edge['demo_cap_bytes']//1024} KiB (demo cap):  grpc {capped_err.code().name} "
      f"-> {type(mapped).__name__}")

assert generous_err is None and generous_bytes == len(payload)
assert capped_err.code() is grpc.StatusCode.RESOURCE_EXHAUSTED
assert type(mapped) is BackendError and isinstance(mapped, JammiError)
payload:                    262,144 bytes
channel @ 64 MiB (prod):    returned 262,144 bytes  (mirrors embedded)
channel @ 64 KiB (demo cap):  grpc RESOURCE_EXHAUSTED -> BackendError

The same payload the capped channel refused, the embedded engine returns in-process — no message, no cap. We build a same-scale result and read it back through the embedded Session to show the unbounded counterpart directly.

import pyarrow as pa
import pyarrow.parquet as pq

with tempfile.TemporaryDirectory() as d:
    table = pa.table({"id": pa.array(range(edge["embedded_returned_rows"]), type=pa.int32())})
    path = f"{d}/big.parquet"
    pq.write_table(table, path)
    embedded.add_source("big", url=path, format="parquet")
    got = embedded.sql("SELECT id FROM big.public.big")

print(f"embedded returned:          {got.num_rows:,} rows = {got.nbytes:,} bytes in-process")
print(f"  exceeds the {edge['demo_cap_bytes']//1024} KiB remote cap: {got.nbytes > edge['demo_cap_bytes']}")
assert got.num_rows == edge["embedded_returned_rows"]
assert got.nbytes == edge["embedded_returned_bytes"]
assert got.nbytes > edge["demo_cap_bytes"]
embedded returned:          40,000 rows = 160,000 bytes in-process
  exceeds the 64 KiB remote cap: True

26.6 The verdict

tax = rec["taxonomy"]
print("unified client surface — measured:")
print(f"  capability sets complementary across the closed five:  {cap['complementary']}")
print(f"  wrong-side capability -> NotSupportedOnBackend:         "
      f"{cap['embedded_wrong_side']['raised'] == cap['remote_wrong_side']['raised'] == 'NotSupportedOnBackend'}")
print(f"  bad argument -> same InvalidArgument on both transports: {tax['two_sided_same_class']}")
print(f"  one except JammiError catches both transports:          {tax['one_except_catches_both']}")
print(f"  remote receive cap finite (64 MiB) + loud on exceed:    "
      f"{edge['capped_channel_mapped'] == 'BackendError'}")
print(f"  embedded returns the same-scale payload unbounded:      {edge['embedded_exceeds_demo_cap']}")

remote.close()
unified client surface — measured:
  capability sets complementary across the closed five:  True
  wrong-side capability -> NotSupportedOnBackend:         True
  bad argument -> same InvalidArgument on both transports: True
  one except JammiError catches both transports:          True
  remote receive cap finite (64 MiB) + loud on exceed:    True
  embedded returns the same-scale payload unbounded:      True

The substitution the whole book leans on — write once against Session, run in-process or behind a server by target alone — is not a convenience that quietly breaks at the edges. It is a contract with named seams: a capability predicate where features diverge, one error family across both transports, and a finite receive bound stated out loud rather than hidden. The develop→deploy journey holds because the seams are honest, not because the transports are secretly the same.

Liskov, Barbara H., and Jeannette M. Wing. 1994. “A Behavioral Notion of Subtyping.” ACM Transactions on Programming Languages and Systems 16 (6): 1811–41.
Spolsky, Joel. 2002. “The Law of Leaky Abstractions.” Joel on Software (essay).