Your data stays on the device by default.
The compute seam runs inference on-device by default; the privacy gate makes a hosted call an explicit opt-in, not a default you discover in a network trace. Vector search, sync, and license verification all run against local files, nothing round-trips to a vendor unless you allow it in writing.
import { TenantFieldCrypto, } from "@caisson/field-crypto" const env = await fc.encryptField( tenant, "record", "notes") // v1 · aes-256-gcm · // per-tenant key · // AAD-bound ← sealed // at rest await fc.decryptField( otherTenant, env, "notes") // cross-tenant key — // open refused ← isolation // proof
On-device by default, hosted by opt-in.
The Local-first bundle composes @caisson/kernel, @caisson/local-store, @caisson/license-verify, @caisson/field-crypto, @caisson/local-privacy, @caisson/local-inference, and @caisson/local-sync. @caisson/local-inference provides the InferenceBackend port and runs on-device by default: a MiniLM-class ONNX model via transformers.js, fetched on first use and SHA-256 hash-verified before it touches your data. Hosted inference is explicit opt-in. The same interface offers metered, egress-guarded transports for OpenRouter, Azure OpenAI, and AWS Bedrock, all disabled until configured.
Seven composed packages.
Each member is a real workspace dependency. The commercial ones also carry a standalone price; the Apache-2.0 base ships free with every bundle.
Hybrid retrieval: sqlite-vec ANN plus FTS5, merged by Reciprocal-Rank-Fusion, with an FTS-only fallback if the vector leg fails.
Offline Ed25519 license verification: checks the signature on the device, fails safe to the community tier if it cannot verify.
Per-tenant field encryption: HKDF key derivation plus AES-256-GCM, sealed at rest under a key a different tenant's file cannot open.
The InferenceBackend seam over a MiniLM-class ONNX model via transformers.js, SHA-256 hash-verified before use, on-device by default.
A default-deny egress boundary every payload crosses before it can leave the process, an empty allowlist means zero egress.
Two-way offline sync: changesets, tombstones, a logical clock, and a reconcile pass with a convergence test.
The governance kernel underneath every bundle: typed config, the shared error model, and security primitives.
Four pieces. All on the device.
Each piece does its job without a network. Compose them, or take a single module, the data path never widens past the disk.
One interface over on-device and hosted inference. Local is the default you ship; hosted only turns on when you flip it in config, on purpose.
A policy boundary every payload crosses before it can leave the process. Default-deny by host: nothing egresses unless you allowed that destination in writing.
Embeddings indexed with sqlite-vec ANN, queried on disk. Semantic recall with zero round-trips to a vector cloud and nothing to leak from one.
License verification that works air-gapped: signature-checked on the device, no phone-home, no remote kill switch. The library keeps running when the network does not.
The only mode is local-only.
The privacy policy is a closed schema, not a toggle: the only mode is local-only, and there is no hosted mode to accidentally flip, because the enum does not have one (widening it takes a deliberate code change, not a config edit). An allowlist is the sole way a host becomes reachable, and only two sink kinds are sanctioned: the model-download host for first-run ONNX fetches, and the rented-backend host for the opt-in hosted lane. Leave the allowlist empty and egress is zero, the air-gap default.
Fail-closed is how the base already behaves.
Default-deny is not a promise, it is how the base substrate behaves today. A cross-tenant read is refused at the database, fail-closed by construction. The privacy gate extends that same posture to network egress: hosts are deny-listed by default, allowed only in a typed config.
-- session scoped to tenant A; reach for tenant B's rows SELECT * FROM records WHERE tenant_id = 'tenant_b'; ERROR: permission denied for table records DETAIL: RLS policy "tenant_isolation" forbids the read
All local.
@caisson/local-store gives you hybrid retrieval: sqlite-vec ANN and FTS5 merged by Reciprocal-Rank-Fusion, degrading to an FTS-only path if the vector leg fails, semantic search with nothing indexed by a vector cloud vendor. Isolation is file-per-tenant: the resolved file path is the tenant boundary. On top, the bundle ships a built two-way sync engine (changesets, tombstones, a logical clock, and a reconcile pass with a convergence test) for when a device needs to catch up, plus offline Ed25519 license verification that checks the signature locally with no phone-home and no remote kill switch.
Semantic recall that never round-trips.
sqlite-vec holds the ANN index next to your rows. A query is a statement against a local file, no API key, no vector vendor, no embeddings shipped off the box to be indexed by someone else. The shape below is illustrative.
-- ANN over the local store; the index lives next to your rows SELECT id, distance FROM cs_cards WHERE embedding MATCH :query_vec ORDER BY distance LIMIT 8; -- index on disk · nothing shipped to a vector cloud
Own the source. Run it on your machine.
This bundle is for teams that cannot send data off the device: regulated data kept local, air-gapped deployments, embedded and edge tooling, or a product that should not need a network call to work at all. It ships the way every Caisson bundle ships: bunx @caisson-sh/cli@latest scaffolds the base, then you add Local-first AI. Two of its composed packages (kernel and license-verify) are Apache-2.0; local-store, field-crypto, local-inference, local-privacy, and local-sync are the commercial layer the bundle license covers.
Questions procurement asks first.
How many developers does the license cover?
Does "own the source" rule out hosted inference?
What does the on-device model need to run?
Can I buy just the vector store instead of the whole bundle?
Prove fit in week one.
Don't take the fit on faith, scaffold the audited base and run it on your own stack before you commit.
The deadliest question in a build-vs-buy call is whether it fits the stack you already run. Answer it with code, not a sales call: scaffold the audited base in one command and deploy from the template the generator emits for your host. You own the source from the first line, so a week-one spike is a real evaluation on your own infrastructure — not a demo that disappears.
$ bunx @caisson-sh/cli@latest my-app $ cd my-app $ bun install # ready to run — deploy from the Railway, Fly, or Vercel template