0.1.1 — early. The protocol is version 2 as of 0.1.0; the software is still early. These packages run one hosted service — byollm.cloud — and a small number of integrations; beyond that they have little mileage. Team routing: share a model on your device with people you name. Each job they send carries a signed, single-use grant your device verifies first. What that means →
Bring Your Own LLM

Stop renting the compute
your users already own.

AI features bill you per token, per user, forever, while the hardware already in your users' hands sits idle. byollm lets your app run its LLM jobs there instead: their Ollama box, their Mac running MLX, their claude CLI. No tunnels. No inbound ports. No shared keys.

$npx byollm@latest connect https://your-app.com

Open source · MIT · built by Of Tomorrow

The shape

Compute comes to the app, outbound.

Your web app stays hosted. The user runs a daemon that connects out to your backend, claims only their own jobs, and runs them on whatever model they've got. There's nothing to open on their network — and jobs are typed data, never code.

   your web app  ──enqueue──▶  your backend  (@byollm/server)
                                      │
                                      │   your job queue — Supabase, Postgres, memory…
                                      ▼
   user's device  ──outbound poll──▶  claim ─▶ run locally ─▶ result
     (byollm)                                    │
                                          Ollama · MLX · claude CLI
  

For developers

A three-line integration.

Mount one route, point it at a store, and enqueue. You never see the user's model or credentials — you get a result back.

// app/api/byollm/[...route]/route.ts
import { createHandler } from "@byollm/server/next";
import { store } from "@/lib/byollm";

export const { POST } = createHandler({
  store,
  verificationUrl: "https://your-app.com/settings/runners",
});
// enqueue from anywhere in your app
const job = await app.enqueue({
  kind: "llm.generate",
  audience: "private",   // their device only
  owner: userId,
  payload: { prompt },
});

const { outcome } = await job.result({
  onNoRunner: promptUserToConnect,  // never hangs
});

A server is byollm-compatible when the conformance kit passes against it. That's the whole versioning story — no framework version to chase.

For users

One command. Your models. Your rules.

You decide what runs, and where. A local model means the prompt never leaves your machine; a service you already pay for means the job goes where you already send it, on your account. And byollm never holds your key.

1

Connect

npx byollm@latest connect https://the-app.com — opens your browser to pair with one click. Nothing to configure on your network.

2

Route your models

Name a service for whatever you’ve got installed, and say which job kinds it answers: "ollama-local": { "type": "ollama", "model": "gemma3:12b", "kinds": ["llm.generate"] }. The daemon only advertises models you approve. And only ones it can actually run.

3

Stay in control

byollm log shows every prompt that ever ran on your device. byollm stop stops it. The daemon is your meter — that's the point.

Providers

Nineteen providers. Three ways to bring your own.

A provider is where a request goes; a model is which weights answer it. Providers are registry entries over one audited transport — adding one is a single line — and every one arrives with a cost class your config can't override.

ProviderWhat it isCost
Your hardware — local compute, costs electricity · shareable up to team
OllamaThe easiest way to run models locallyfree
MLXApple-silicon native, via mlx_lm.serverfree
llama.cppThe C++ engine behind much of local AIfree
vLLMProduction-grade GPU servingfree
LM StudioLocal models with a desktop appfree
JanOpen-source desktop runnerfree
LocalAISelf-hosted OpenAI-compatible serverfree
Your API key — per-token billing on your account · shareable with an acknowledgment and a daily ceiling
AnthropicClaude models on your platform keymetered
OpenAIGPT models on your platform keymetered
GeminiGoogle's models on your API keymetered
GrokxAI's models on your API keymetered
GroqOpen models served fast on custom siliconmetered
OpenRouterOne key, many providersmetered
TogetherOpen-model cloudmetered
DeepSeekDeepSeek's models on your API keymetered
MistralMistral's models on your API keymetered
Your subscription — a personal plan · never shared, enforced by the protocol
claude CLIYour Claude plan, on your computer, yours alonesubscription
codex CLIYour ChatGPT plan, on your computer, yours alonesubscription

Anthropic appears twice, in two cost classes — a platform key bills per token, a Claude plan covers one person's work. The axis asks who pays and under what terms, not which company answers.

Not listed? openai-http reaches anything OpenAI-compatible — its cost class is detected from where it points, and remote is never free.

The audience model

Share your GPU — safely, and only if you want to.

Every job carries an audience and every backend an offer scope. A job runs on a device only when both agree. Cost class comes from the protocol, not your config — point a backend at a remote endpoint and it is metered whatever you call it, because "free" is derived from where the request goes.

BackendCostCan offerWhy
Ollama · MLX · llama.cpp · vLLM · LM Studio
local models
free privateteam Local compute. Costs electricity, not money — the folding@home posture. Lend your computer to your team.
OpenAI · Gemini · Grok · Groq · OpenRouter
your API key
metered private unless you say otherwise Your money, per token. Sharing is legitimate — and ruinous by accident — so widening needs an explicit acknowledgment and a daily ceiling the daemon enforces.
claude CLI & other
subscription accounts
subscription private · locked One account runs one person's work. Enforced by the protocol, not a setting — your subscription is never part of shared compute.

Security

Breakout isn't detected. It's impossible.

The daemon runs prompts on the owner's computer, so every payload is treated as hostile input. The design makes escape structurally impossible rather than trying to spot it.

No command line, ever

HTTP backends receive the prompt as a request body; process backends (the claude CLI) receive it on stdin with a fixed argv. Either way, --flags, $(…) and shell metacharacters are just characters the model reads — no code path turns a payload into a command.

Config is local-only

Model, backend, and flags come from the owner's config. A job can never name a model, a path, a URL, or a flag. Unknown job kind is refused, not guessed.

Sandboxed children

Process backends spawn with a stripped environment (no API key), an empty scratch directory, no inherited file descriptors, and hard timeout and output caps. A job cannot become a process of its own choosing.

An adversarial gate

A named corpus of command-injection, argv-injection, path-traversal and exfiltration payloads runs as a blocking CI check. Every backend ships its own hostile-payload suite before it can be added.

We're precise about the boundary: BYOLLM makes breakout impossible; prompt injection — steering the model's words — is the model's problem, bounded here because the model has no tools and the output is inert. We don't promise more than we can keep.

Packages

Six small pieces.

@byollm/protocol

The wire contract — types, schemas, and the normative spec. Six endpoints, lease semantics, typed job kinds.

byollm

What users run. Pluggable backends, model routing, and the append-only trust log.

@byollm/server

Drop-in handlers plus a first-party Supabase adapter for your backend.

@byollm/conformance

The compatibility contract. Certify any server implementation with one command.

@byollm/relay

The reference relay. Routes sealed jobs between sites and devices, holding no key that opens either.

@byollm/control-plane

The reference control plane. Decides which device service answers and signs one grant per job.